You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

226 line
10 KiB

  1. import type { Page } from '@playwright/test'
  2. import { captureScreenshot, expect, test } from './fixtures'
  3. import {
  4. chooseSelectOption,
  5. confirmMessageBox,
  6. dismissOverlays,
  7. fillFormItem,
  8. loginAsAdmin,
  9. roleCardByText,
  10. tableRowByText,
  11. visibleDialog,
  12. } from './helpers'
  13. const runToken = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
  14. const upperToken = runToken.replace(/[^a-z0-9]/gi, '').toUpperCase().slice(-14)
  15. const lowerToken = runToken.replace(/[^a-z0-9]/gi, '').toLowerCase().slice(-18)
  16. const data = {
  17. prefix: `UTE2E-${upperToken}`,
  18. departmentName: `UTE2E-${upperToken}-部门`,
  19. departmentCode: `E2E_${upperToken}`.slice(0, 32),
  20. roleName: `UTE2E-${upperToken}-角色`,
  21. roleCode: `e2e_${lowerToken}`.slice(0, 48),
  22. roleShort: '测',
  23. userName: `e2e_${lowerToken}`.slice(0, 32),
  24. displayName: `UTE2E-${upperToken}-用户`,
  25. editedDisplayName: `UTE2E-${upperToken}-已编辑`,
  26. password: `Tmp@${upperToken}a1`,
  27. }
  28. async function createDepartment(page: Page) {
  29. await page.goto('/system/departments')
  30. await page.getByRole('button', { name: '新增部门', exact: true }).click()
  31. const dialog = visibleDialog(page)
  32. await fillFormItem(dialog, '部门名称', data.departmentName)
  33. await fillFormItem(dialog, '部门编码', data.departmentCode)
  34. await fillFormItem(dialog, '负责人', data.prefix)
  35. await fillFormItem(dialog, '部门职责', `${data.prefix} 自动化测试数据`)
  36. await dialog.getByRole('button', { name: '创建部门' }).click()
  37. await expect(tableRowByText(page, data.departmentName)).toBeVisible()
  38. }
  39. async function createRole(page: Page) {
  40. await page.goto('/system/roles')
  41. await page.getByRole('button', { name: '新增角色', exact: true }).click()
  42. const dialog = visibleDialog(page)
  43. await fillFormItem(dialog, '角色名称', data.roleName)
  44. await fillFormItem(dialog, '角色编码', data.roleCode)
  45. await fillFormItem(dialog, '界面简称', data.roleShort)
  46. await fillFormItem(dialog, '角色说明', `${data.prefix} 自动化角色`)
  47. await dialog.getByRole('button', { name: '创建角色' }).click()
  48. await expect(roleCardByText(page, data.roleName)).toBeVisible()
  49. }
  50. async function createUser(page: Page) {
  51. await page.goto('/system/users')
  52. await page.getByRole('button', { name: '新增用户', exact: true }).click()
  53. const dialog = visibleDialog(page)
  54. await fillFormItem(dialog, '姓名', data.displayName)
  55. await fillFormItem(dialog, '登录账号', data.userName)
  56. await chooseSelectOption(page, dialog, '所属部门', data.departmentName)
  57. await fillFormItem(dialog, '初始密码', data.password)
  58. const roleCheckbox = dialog.locator('.el-checkbox').filter({ hasText: data.roleName })
  59. await roleCheckbox.click()
  60. await chooseSelectOption(page, dialog, '登录默认身份', data.roleName)
  61. await dialog.getByRole('button', { name: '创建用户' }).click()
  62. await expect(tableRowByText(page, data.userName)).toBeVisible()
  63. }
  64. async function editAndSearchData(page: Page) {
  65. await page.goto('/system/users')
  66. await page.getByPlaceholder('请输入姓名、账号、部门或角色').fill(data.userName)
  67. await page.getByRole('button', { name: '搜索', exact: true }).click()
  68. const userRow = tableRowByText(page, data.userName)
  69. await expect(userRow).toBeVisible()
  70. await userRow.getByRole('button', { name: '编辑' }).click()
  71. const userDialog = visibleDialog(page)
  72. await fillFormItem(userDialog, '姓名', data.editedDisplayName)
  73. await userDialog.getByRole('button', { name: '保存修改' }).click()
  74. await expect(tableRowByText(page, data.editedDisplayName)).toBeVisible()
  75. await page.goto('/system/roles')
  76. await page.getByPlaceholder('请输入角色名称、编码或说明').fill(data.roleCode)
  77. await page.getByRole('button', { name: '搜索', exact: true }).click()
  78. const roleCard = roleCardByText(page, data.roleName)
  79. await expect(roleCard).toBeVisible()
  80. await roleCard.getByRole('button', { name: '编辑' }).click()
  81. const roleDialog = visibleDialog(page)
  82. await fillFormItem(roleDialog, '角色说明', `${data.prefix} 已编辑`)
  83. await roleDialog.getByRole('button', { name: '保存修改' }).click()
  84. await expect(roleCardByText(page, `${data.prefix} 已编辑`)).toBeVisible()
  85. await page.goto('/system/departments')
  86. await page.getByPlaceholder('请输入部门、编码或负责人').fill(data.departmentCode)
  87. await page.getByRole('button', { name: '搜索', exact: true }).click()
  88. await expect(tableRowByText(page, data.departmentName)).toBeVisible()
  89. }
  90. async function grantPermission(page: Page) {
  91. await page.goto('/system/roles')
  92. await page.getByPlaceholder('请输入角色名称、编码或说明').fill(data.roleCode)
  93. await page.getByRole('button', { name: '搜索', exact: true }).click()
  94. const roleRow = roleCardByText(page, data.roleName)
  95. await Promise.all([
  96. page.waitForResponse((response) => response.request().method() === 'GET' && /\/permissions\/roles\/[^/?]+/.test(response.url()) && response.ok()),
  97. roleRow.getByRole('button', { name: '配置权限' }).click(),
  98. ])
  99. const drawer = page.locator('.role-permission-drawer')
  100. await expect(drawer).toBeVisible()
  101. await expect(drawer.locator('.role-permission-summary h3')).toHaveText(data.roleName)
  102. const auditCode = page.locator('code').filter({ hasText: /^system\.audit$/ })
  103. const auditPermission = drawer.locator('.el-tree-node').filter({ has: auditCode }).last()
  104. await auditPermission.locator('.el-checkbox').click()
  105. const saveButton = drawer.getByRole('button', { name: '保存授权' })
  106. await expect(saveButton).toBeEnabled()
  107. await saveButton.click()
  108. await expect(page.getByText(/权限已保存|当前权限配置已保存/).last()).toBeVisible()
  109. }
  110. async function verifyAudit(page: Page) {
  111. await page.goto('/system/audit')
  112. const search = page.getByPlaceholder('请输入用户、模块、操作或地址')
  113. await search.fill(data.prefix)
  114. await page.getByRole('button', { name: '搜索', exact: true }).click()
  115. await expect.poll(async () => page.locator('.el-table__body-wrapper .el-table__row').count(), {
  116. message: '等待与本次唯一前缀相关的审计记录可见',
  117. timeout: 10_000,
  118. }).toBeGreaterThan(0)
  119. }
  120. async function deleteUserIfPresent(page: Page) {
  121. await dismissOverlays(page)
  122. await page.goto('/system/users')
  123. await page.getByPlaceholder('请输入姓名、账号、部门或角色').fill(data.userName)
  124. await page.getByRole('button', { name: '搜索', exact: true }).click()
  125. const row = tableRowByText(page, data.userName)
  126. if (!(await row.waitFor({ state: 'visible', timeout: 5_000 }).then(() => true).catch(() => false))) return false
  127. await row.getByRole('button', { name: '删除' }).click()
  128. await confirmMessageBox(page, '确认删除')
  129. await expect(row).toHaveCount(0)
  130. return true
  131. }
  132. async function deleteRoleIfPresent(page: Page) {
  133. await dismissOverlays(page)
  134. await page.goto('/system/roles')
  135. await page.getByPlaceholder('请输入角色名称、编码或说明').fill(data.roleCode)
  136. await page.getByRole('button', { name: '搜索', exact: true }).click()
  137. const card = roleCardByText(page, data.roleName)
  138. if (!(await card.waitFor({ state: 'visible', timeout: 5_000 }).then(() => true).catch(() => false))) return false
  139. await card.getByRole('button', { name: '删除' }).click()
  140. await confirmMessageBox(page, '删除角色')
  141. await expect(card).toHaveCount(0)
  142. return true
  143. }
  144. async function deleteDepartmentIfPresent(page: Page) {
  145. await dismissOverlays(page)
  146. await page.goto('/system/departments')
  147. await page.getByPlaceholder('请输入部门、编码或负责人').fill(data.departmentCode)
  148. await page.getByRole('button', { name: '搜索', exact: true }).click()
  149. const row = tableRowByText(page, data.departmentName)
  150. if (!(await row.waitFor({ state: 'visible', timeout: 5_000 }).then(() => true).catch(() => false))) return false
  151. await row.getByRole('button', { name: '删除' }).click()
  152. const dialog = visibleDialog(page)
  153. await dialog.getByRole('button', { name: '确认删除并迁移' }).click()
  154. await expect(row).toHaveCount(0)
  155. return true
  156. }
  157. test('系统管理测试数据可创建、编辑、搜索、授权、审计并完整清理', async ({ page }, testInfo) => {
  158. await loginAsAdmin(page)
  159. let testFailure: unknown
  160. try {
  161. await test.step('创建测试部门', async () => {
  162. await createDepartment(page)
  163. await captureScreenshot(page, testInfo, 'crud-department-created')
  164. })
  165. await test.step('创建测试角色', async () => {
  166. await createRole(page)
  167. await captureScreenshot(page, testInfo, 'crud-role-created')
  168. })
  169. await test.step('创建测试用户', async () => {
  170. await createUser(page)
  171. await captureScreenshot(page, testInfo, 'crud-user-created')
  172. })
  173. await test.step('编辑并搜索测试数据', async () => {
  174. await editAndSearchData(page)
  175. await captureScreenshot(page, testInfo, 'crud-edit-search-success')
  176. })
  177. await test.step('配置角色权限', async () => {
  178. await grantPermission(page)
  179. await captureScreenshot(page, testInfo, 'crud-permission-saved')
  180. })
  181. await test.step('审计记录可检索', async () => {
  182. await verifyAudit(page)
  183. await captureScreenshot(page, testInfo, 'crud-audit-found')
  184. })
  185. } catch (error) {
  186. testFailure = error
  187. } finally {
  188. const cleanupErrors: unknown[] = []
  189. for (const [name, screenshotLabel, cleanup] of [
  190. ['用户', 'crud-user-deleted', deleteUserIfPresent],
  191. ['角色', 'crud-role-deleted', deleteRoleIfPresent],
  192. ['部门', 'crud-department-deleted', deleteDepartmentIfPresent],
  193. ] as const) {
  194. try {
  195. const deleted = await cleanup(page)
  196. if (!testFailure && deleted) await captureScreenshot(page, testInfo, screenshotLabel)
  197. } catch (error) {
  198. cleanupErrors.push(new Error(`${name}清理失败`, { cause: error }))
  199. }
  200. }
  201. if (testFailure || cleanupErrors.length) {
  202. throw new AggregateError(
  203. [testFailure, ...cleanupErrors].filter((error) => error !== undefined),
  204. cleanupErrors.length ? '系统管理 E2E 失败,且已执行全部清理步骤;请检查聚合错误。' : '系统管理 E2E 失败。',
  205. )
  206. }
  207. }
  208. })