Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

488 rindas
26 KiB

  1. import type { APIRequestContext, APIResponse, Page } from '@playwright/test'
  2. import { attachJson, captureScreenshot, expect, runPrefix, test } from './fixtures'
  3. import {
  4. adminAccessToken,
  5. chooseSelectOption,
  6. confirmMessageBox,
  7. envelopeData,
  8. fillFormItem,
  9. loginAsAdmin,
  10. tableRowByText,
  11. visibleDialog,
  12. } from './helpers'
  13. test.describe.configure({ mode: 'serial' })
  14. test.use({ trace: 'off', video: 'off' })
  15. const suffix = runPrefix.replace(/[^A-Za-z0-9]/g, '').slice(-12).toLowerCase()
  16. const data = {
  17. parentName: `${runPrefix}-组织父部门`,
  18. editedParentName: `${runPrefix}-已编辑父部门`,
  19. parentCode: `APE2E_P_${suffix}`.toUpperCase().slice(0, 32),
  20. childName: `${runPrefix}-组织子部门`,
  21. childCode: `APE2E_C_${suffix}`.toUpperCase().slice(0, 32),
  22. grandchildName: `${runPrefix}-组织孙部门`,
  23. grandchildCode: `APE2E_G_${suffix}`.toUpperCase().slice(0, 32),
  24. roleName: `${runPrefix}-范围角色`,
  25. roleCode: `ape2e_scope_${suffix}`.slice(0, 48),
  26. username: `ape2e_state_${suffix}`.slice(0, 32),
  27. displayName: `${runPrefix}-状态学员`,
  28. editedDisplayName: `${runPrefix}-已编辑状态学员`,
  29. initialPassword: `Init@${suffix.slice(-6)}Aa1`,
  30. }
  31. type ApiRecord = Record<string, unknown>
  32. const auth = (token: string) => ({ Authorization: `Bearer ${token}` })
  33. const record = (value: unknown): ApiRecord => value && typeof value === 'object' ? value as ApiRecord : {}
  34. const versionOf = (value: ApiRecord) => Number(value.version ?? value.dataVersion ?? 0)
  35. async function responseJson(response: APIResponse) {
  36. return response.json().catch(() => ({}))
  37. }
  38. function listRecords(value: unknown): ApiRecord[] {
  39. const payload = envelopeData(value)
  40. if (Array.isArray(payload)) return payload.map(record)
  41. const root = record(payload)
  42. const source = root.records ?? root.items ?? root.departments ?? root.roles ?? root.users
  43. return Array.isArray(source) ? source.map(record) : []
  44. }
  45. async function apiDepartments(request: APIRequestContext, token: string) {
  46. const response = await request.get('/api/auth/v1/departments', { headers: auth(token) })
  47. expect(response.ok(), `读取部门失败:HTTP ${response.status()}`).toBeTruthy()
  48. return listRecords(await responseJson(response))
  49. }
  50. async function apiDepartment(request: APIRequestContext, token: string, code: string) {
  51. return (await apiDepartments(request, token)).find((item) => String(item.code) === code)
  52. }
  53. async function apiRole(request: APIRequestContext, token: string) {
  54. const response = await request.get('/api/auth/v1/roles', {
  55. headers: auth(token),
  56. params: { keyword: data.roleCode, page: 1, size: 100 },
  57. })
  58. expect(response.ok(), `读取角色失败:HTTP ${response.status()}`).toBeTruthy()
  59. return listRecords(await responseJson(response)).find((item) => String(item.code) === data.roleCode)
  60. }
  61. async function apiUser(request: APIRequestContext, token: string) {
  62. const response = await request.get('/api/auth/v1/users', {
  63. headers: auth(token),
  64. params: { keyword: data.username, page: 1, size: 100 },
  65. })
  66. expect(response.ok(), `读取用户失败:HTTP ${response.status()}`).toBeTruthy()
  67. return listRecords(await responseJson(response)).find((item) => String(item.username) === data.username)
  68. }
  69. async function apiRolePermissions(request: APIRequestContext, token: string, roleId: string) {
  70. const response = await request.get(`/api/auth/v1/permissions/roles/${roleId}`, { headers: auth(token) })
  71. expect(response.ok(), `读取角色权限失败:HTTP ${response.status()}`).toBeTruthy()
  72. const payload = record(envelopeData(await responseJson(response)))
  73. const source = payload.permissions ?? payload.permissionCodes ?? payload.codes
  74. return Array.isArray(source) ? source.map(String) : []
  75. }
  76. async function cleanupCreatedData(request: APIRequestContext, token: string) {
  77. const result: Record<string, string> = {}
  78. const user = await apiUser(request, token)
  79. if (user) {
  80. const response = await request.delete(`/api/auth/v1/users/${String(user.id)}`, {
  81. headers: auth(token), data: { version: versionOf(user) },
  82. })
  83. result.user = String(response.status())
  84. } else result.user = 'absent'
  85. const role = await apiRole(request, token)
  86. if (role) {
  87. const response = await request.delete(`/api/auth/v1/roles/${String(role.id)}`, {
  88. headers: auth(token), data: { version: versionOf(role) },
  89. })
  90. result.role = String(response.status())
  91. } else result.role = 'absent'
  92. for (const [label, code] of [
  93. ['grandchild', data.grandchildCode],
  94. ['child', data.childCode],
  95. ['parent', data.parentCode],
  96. ] as const) {
  97. const department = await apiDepartment(request, token, code)
  98. if (!department) {
  99. result[label] = 'absent'
  100. continue
  101. }
  102. const response = await request.post(`/api/auth/v1/departments/${String(department.id)}/migrate-and-delete`, {
  103. headers: auth(token), data: { version: versionOf(department) },
  104. })
  105. result[label] = String(response.status())
  106. }
  107. return result
  108. }
  109. async function search(page: Page, placeholder: string, keyword: string) {
  110. const input = page.getByPlaceholder(placeholder)
  111. await expect(input).toBeVisible()
  112. await input.fill(keyword)
  113. await page.getByRole('button', { name: '搜索', exact: true }).click()
  114. }
  115. async function openDepartment(page: Page, code: string) {
  116. await search(page, '请输入部门、编码或负责人', code)
  117. const row = tableRowByText(page, code)
  118. await expect(row).toBeVisible()
  119. return row
  120. }
  121. async function createDepartment(page: Page, parentCode: string | null, name: string, code: string) {
  122. if (parentCode) {
  123. const parentRow = await openDepartment(page, parentCode)
  124. await parentRow.getByRole('button', { name: '新增下级', exact: true }).click()
  125. } else {
  126. await page.getByRole('button', { name: '新增部门', exact: true }).click()
  127. }
  128. const dialog = visibleDialog(page)
  129. await fillFormItem(dialog, '部门名称', name)
  130. await fillFormItem(dialog, '部门编码', code)
  131. await fillFormItem(dialog, '部门职责', `${runPrefix} 页面提交的组织层级测试数据`)
  132. await dialog.getByRole('button', { name: '创建部门' }).click()
  133. await expect(dialog).toBeHidden()
  134. await expect(await openDepartment(page, code)).toContainText(name)
  135. }
  136. async function logout(page: Page) {
  137. await page.locator('.user-menu-trigger').click()
  138. await page.getByRole('menuitem', { name: /退出登录/ }).click()
  139. await confirmMessageBox(page, '退出登录')
  140. await expect(page).toHaveURL(/\/login(?:\?|$)/)
  141. }
  142. async function selectStudentIdentity(page: Page) {
  143. await page.locator('[data-role-code="student"]').click()
  144. const picker = page.locator('.login-identity-picker')
  145. await expect(picker).toBeVisible()
  146. await picker.click()
  147. const option = page.locator('.login-identity-popper .el-select-dropdown__item')
  148. .filter({ hasText: data.editedDisplayName }).first()
  149. await expect(option).toBeVisible()
  150. await option.click()
  151. }
  152. async function clearBrowserSession(page: Page) {
  153. await page.evaluate(() => {
  154. window.localStorage.clear()
  155. window.sessionStorage.clear()
  156. })
  157. await page.context().clearCookies()
  158. }
  159. test('IAM 扩展页面闭环:层级迁移、范围授权、停启登录与页面删除', async ({ page, request }, testInfo) => {
  160. test.slow()
  161. const token = await adminAccessToken(request)
  162. await cleanupCreatedData(request, token)
  163. await loginAsAdmin(page)
  164. let primaryError: unknown
  165. try {
  166. await test.step('父子孙部门全部页面创建,父部门编辑后刷新与 API 均持久化', async () => {
  167. await page.goto('/organization/departments')
  168. await createDepartment(page, null, data.parentName, data.parentCode)
  169. await createDepartment(page, data.parentCode, data.childName, data.childCode)
  170. await createDepartment(page, data.childCode, data.grandchildName, data.grandchildCode)
  171. const parentRow = await openDepartment(page, data.parentCode)
  172. await parentRow.getByRole('button', { name: '编辑', exact: true }).click()
  173. const dialog = visibleDialog(page)
  174. await fillFormItem(dialog, '部门名称', data.editedParentName)
  175. await fillFormItem(dialog, '部门职责', `${runPrefix} 已通过页面编辑并保存`)
  176. await dialog.getByRole('button', { name: '保存修改' }).click()
  177. await expect(dialog).toBeHidden()
  178. await page.reload()
  179. await expect(await openDepartment(page, data.parentCode)).toContainText(data.editedParentName)
  180. const parent = await apiDepartment(request, token, data.parentCode)
  181. const child = await apiDepartment(request, token, data.childCode)
  182. const grandchild = await apiDepartment(request, token, data.grandchildCode)
  183. expect(parent?.name).toBe(data.editedParentName)
  184. expect(String(child?.parentId)).toBe(String(parent?.id))
  185. expect(String(grandchild?.parentId)).toBe(String(child?.id))
  186. await captureScreenshot(page, testInfo, '01-department-hierarchy-edited-and-reloaded')
  187. })
  188. await test.step('含启用孙部门的子部门页面级联停用,并逐级页面启用', async () => {
  189. const childRow = await openDepartment(page, data.childCode)
  190. await childRow.getByRole('button', { name: '停用', exact: true }).click()
  191. await confirmMessageBox(page, '级联停用')
  192. await expect(await openDepartment(page, data.childCode)).toContainText('已停用')
  193. expect((await apiDepartment(request, token, data.childCode))?.enabled).toBe(false)
  194. expect((await apiDepartment(request, token, data.grandchildCode))?.enabled).toBe(false)
  195. await (await openDepartment(page, data.childCode)).getByRole('button', { name: '启用', exact: true }).click()
  196. await expect(await openDepartment(page, data.childCode)).toContainText('已启用')
  197. await (await openDepartment(page, data.grandchildCode)).getByRole('button', { name: '启用', exact: true }).click()
  198. await expect(await openDepartment(page, data.grandchildCode)).toContainText('已启用')
  199. expect((await apiDepartment(request, token, data.childCode))?.enabled).toBe(true)
  200. expect((await apiDepartment(request, token, data.grandchildCode))?.enabled).toBe(true)
  201. await page.reload()
  202. await expect(await openDepartment(page, data.grandchildCode)).toContainText('已启用')
  203. await captureScreenshot(page, testInfo, '02-department-cascade-disabled-and-reenabled')
  204. })
  205. await test.step('角色通过页面创建后改为自定义数据范围并选择父部门,刷新与 API 持久化', async () => {
  206. await page.goto('/organization/roles')
  207. await page.getByRole('button', { name: '新增角色', exact: true }).click()
  208. let dialog = visibleDialog(page)
  209. await fillFormItem(dialog, '角色名称', data.roleName)
  210. await fillFormItem(dialog, '角色编码', data.roleCode)
  211. await fillFormItem(dialog, '界面简称', '范围')
  212. await fillFormItem(dialog, '角色说明', `${runPrefix} 自定义范围测试角色`)
  213. await dialog.getByRole('button', { name: '创建角色' }).click()
  214. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  215. let row = tableRowByText(page, data.roleCode)
  216. await expect(row).toBeVisible()
  217. await row.getByRole('button', { name: '编辑', exact: true }).click()
  218. dialog = visibleDialog(page)
  219. await chooseSelectOption(page, dialog, '数据范围', '自定义范围')
  220. await chooseSelectOption(page, dialog, '自定义部门范围', data.childName)
  221. await fillFormItem(dialog, '角色说明', `${runPrefix} CUSTOM 范围已保存`)
  222. await dialog.getByRole('button', { name: '保存修改' }).click()
  223. await page.reload()
  224. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  225. row = tableRowByText(page, data.roleCode)
  226. await expect(row).toContainText('自定义范围')
  227. await expect(row).toContainText('CUSTOM 范围已保存')
  228. const role = await apiRole(request, token)
  229. const child = await apiDepartment(request, token, data.childCode)
  230. expect(role?.dataScope).toBe('CUSTOM')
  231. expect((role?.departmentScopeIds as unknown[]).map(String)).toEqual([String(child?.id)])
  232. await captureScreenshot(page, testInfo, '03-role-custom-scope-saved-and-reloaded')
  233. })
  234. await test.step('权限页完成整组全选、跨组多选和整组取消,两次保存刷新均与 API 一致', async () => {
  235. const row = tableRowByText(page, data.roleCode)
  236. await row.getByRole('button', { name: '配置权限', exact: true }).click()
  237. await expect(page).toHaveURL(/\/organization\/permissions\?role=/)
  238. const groups = page.locator('.permission-group')
  239. await expect(groups.first()).toBeVisible()
  240. expect(await groups.count()).toBeGreaterThan(1)
  241. const firstGroup = groups.nth(0)
  242. const secondGroup = groups.nth(1)
  243. const firstCodes = (await firstGroup.locator('.permission-item code').allTextContents()).map((item) => item.trim())
  244. const extraItem = secondGroup.locator('.permission-item').first()
  245. const extraCode = (await extraItem.locator('code').innerText()).trim()
  246. await firstGroup.getByRole('button', { name: '全选本组' }).click()
  247. await extraItem.click()
  248. await page.getByRole('button', { name: '保存权限配置' }).click()
  249. await expect(page.getByText(/权限已保存并立即生效/).last()).toBeVisible()
  250. const role = await apiRole(request, token)
  251. const firstSaved = await apiRolePermissions(request, token, String(role?.id))
  252. expect(firstSaved).toEqual(expect.arrayContaining([...firstCodes, extraCode]))
  253. await page.reload()
  254. await expect(page.locator('.role-selector > button.active')).toContainText(data.roleName)
  255. for (const code of [...firstCodes, extraCode]) {
  256. await expect(page.locator('.permission-item').filter({ hasText: code }).first()).toHaveAttribute('aria-checked', 'true')
  257. }
  258. await page.locator('.permission-group').first().getByRole('button', { name: '清空' }).click()
  259. await page.getByRole('button', { name: '保存权限配置' }).click()
  260. await expect(page.getByText(/权限已保存并立即生效/).last()).toBeVisible()
  261. const secondSaved = await apiRolePermissions(request, token, String(role?.id))
  262. expect(secondSaved).toContain(extraCode)
  263. firstCodes.forEach((code) => expect(secondSaved).not.toContain(code))
  264. await page.reload()
  265. await expect(page.locator('.permission-item').filter({ hasText: extraCode }).first()).toHaveAttribute('aria-checked', 'true')
  266. for (const code of firstCodes) {
  267. await expect(page.locator('.permission-item').filter({ hasText: code }).first()).toHaveAttribute('aria-checked', 'false')
  268. }
  269. await captureScreenshot(page, testInfo, '04-permission-group-save-clear-and-reload')
  270. })
  271. await test.step('用户页面创建并分配学员与自定义角色,编辑后刷新和 API 持久化', async () => {
  272. await page.goto('/organization/users')
  273. await page.getByRole('button', { name: '新增用户', exact: true }).click()
  274. let dialog = visibleDialog(page)
  275. await fillFormItem(dialog, '姓名', data.displayName)
  276. await fillFormItem(dialog, '登录账号', data.username)
  277. await chooseSelectOption(page, dialog, '所属部门', data.childName)
  278. await fillFormItem(dialog, '初始密码', data.initialPassword)
  279. await dialog.locator('.el-checkbox').filter({ hasText: '学员' }).first().click()
  280. await dialog.locator('.el-checkbox').filter({ hasText: data.roleName }).first().click()
  281. await chooseSelectOption(page, dialog, '登录默认身份', '学员')
  282. await dialog.getByRole('button', { name: '创建用户' }).click()
  283. await search(page, '请输入姓名、账号、部门或角色', data.username)
  284. let row = tableRowByText(page, data.username)
  285. await expect(row).toBeVisible()
  286. await row.getByRole('button', { name: '编辑', exact: true }).click()
  287. dialog = visibleDialog(page)
  288. await fillFormItem(dialog, '姓名', data.editedDisplayName)
  289. await dialog.getByRole('button', { name: '保存修改' }).click()
  290. await page.reload()
  291. await search(page, '请输入姓名、账号、部门或角色', data.username)
  292. row = tableRowByText(page, data.username)
  293. await expect(row).toContainText(data.editedDisplayName)
  294. const user = await apiUser(request, token)
  295. const role = await apiRole(request, token)
  296. expect(user?.displayName).toBe(data.editedDisplayName)
  297. expect((user?.roleIds as unknown[]).map(String)).toContain(String(role?.id))
  298. await captureScreenshot(page, testInfo, '05-user-created-edited-and-reloaded')
  299. })
  300. await test.step('有关联用户时角色页面删除禁用且正式 API 拒绝,角色页面停启可持久化', async () => {
  301. await page.goto('/organization/roles')
  302. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  303. let row = tableRowByText(page, data.roleCode)
  304. await expect(row.getByRole('button', { name: '删除', exact: true })).toBeDisabled()
  305. let role = await apiRole(request, token)
  306. const blockedDelete = await request.delete(`/api/auth/v1/roles/${String(role?.id)}`, {
  307. headers: auth(token), data: { version: versionOf(role ?? {}) },
  308. })
  309. expect(blockedDelete.status()).toBe(409)
  310. await row.getByRole('button', { name: '停用', exact: true }).click()
  311. await confirmMessageBox(page, '确认')
  312. await expect(tableRowByText(page, data.roleCode)).toContainText('已停用')
  313. role = await apiRole(request, token)
  314. expect(role?.enabled).toBe(false)
  315. await page.reload()
  316. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  317. row = tableRowByText(page, data.roleCode)
  318. await expect(row).toContainText('已停用')
  319. await row.getByRole('button', { name: '启用', exact: true }).click()
  320. await confirmMessageBox(page, '确认')
  321. await expect(tableRowByText(page, data.roleCode)).toContainText('已启用')
  322. expect((await apiRole(request, token))?.enabled).toBe(true)
  323. await page.reload()
  324. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  325. await expect(tableRowByText(page, data.roleCode)).toContainText('已启用')
  326. await captureScreenshot(page, testInfo, '06-linked-role-delete-blocked-and-status-restored')
  327. })
  328. await test.step('删除子部门时页面迁移关联用户与直属孙部门,刷新树和 API 均无悬空引用', async () => {
  329. await page.goto('/organization/departments')
  330. const childRow = await openDepartment(page, data.childCode)
  331. await expect(childRow).toContainText('1')
  332. await childRow.getByRole('button', { name: '删除', exact: true }).click()
  333. const dialog = visibleDialog(page)
  334. await expect(dialog).toContainText('关联用户')
  335. await expect(dialog).toContainText('直属下级')
  336. const migrationSelects = dialog.locator('.el-form-item').filter({ hasText: /迁入部门|新上级/ }).locator('.el-select')
  337. await expect(migrationSelects).toHaveCount(2)
  338. await expect(migrationSelects.nth(0)).toContainText(data.editedParentName)
  339. await expect(migrationSelects.nth(1)).toContainText(data.editedParentName)
  340. await captureScreenshot(page, testInfo, '07-department-delete-migration-confirmation')
  341. await dialog.getByRole('button', { name: '确认删除并迁移' }).click()
  342. await expect(dialog).toBeHidden()
  343. await page.reload()
  344. await search(page, '请输入部门、编码或负责人', data.childCode)
  345. await expect(tableRowByText(page, data.childCode)).toHaveCount(0)
  346. await expect(page.locator('.department-scope-tree')).not.toContainText(data.childName)
  347. await expect(page.locator('.department-scope-tree')).toContainText(data.grandchildName)
  348. const parent = await apiDepartment(request, token, data.parentCode)
  349. const child = await apiDepartment(request, token, data.childCode)
  350. const grandchild = await apiDepartment(request, token, data.grandchildCode)
  351. const user = await apiUser(request, token)
  352. expect(child).toBeUndefined()
  353. expect(String(grandchild?.parentId)).toBe(String(parent?.id))
  354. expect(String(user?.departmentId)).toBe(String(parent?.id))
  355. const role = await apiRole(request, token)
  356. const migratedScopeIds = (role?.departmentScopeIds as unknown[]).map(String)
  357. expect(migratedScopeIds).toEqual([String(parent?.id)])
  358. await page.goto(`/organization/roles`)
  359. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  360. await tableRowByText(page, data.roleCode).getByRole('button', { name: '编辑', exact: true }).click()
  361. const scopeDialog = visibleDialog(page)
  362. await expect(scopeDialog.getByText(data.editedParentName, { exact: true })).toBeVisible()
  363. await expect(scopeDialog.getByText(data.childName, { exact: true })).toHaveCount(0)
  364. await captureScreenshot(page, testInfo, '08-role-scope-migrated-without-deleted-department')
  365. await scopeDialog.getByRole('button', { name: '取消' }).click()
  366. await page.goto('/organization/departments')
  367. await search(page, '请输入部门、编码或负责人', data.grandchildCode)
  368. await expect(tableRowByText(page, data.grandchildCode)).toBeVisible()
  369. await captureScreenshot(page, testInfo, '09-department-migration-tree-and-table-reloaded')
  370. })
  371. await test.step('用户页面停用后登录 API 明确拒绝且身份选择器隐藏,启用后页面登录恢复', async () => {
  372. await page.goto('/organization/users')
  373. await search(page, '请输入姓名、账号、部门或角色', data.username)
  374. let row = tableRowByText(page, data.username)
  375. await row.getByRole('button', { name: '停用', exact: true }).click()
  376. await confirmMessageBox(page, '确认停用')
  377. await expect(tableRowByText(page, data.username)).toContainText('已停用')
  378. expect((await apiUser(request, token))?.enabled).toBe(false)
  379. await page.reload()
  380. await search(page, '请输入姓名、账号、部门或角色', data.username)
  381. await expect(tableRowByText(page, data.username)).toContainText('已停用')
  382. const user = await apiUser(request, token)
  383. await logout(page)
  384. const rejected = await request.post('/api/auth/v1/auth/login', {
  385. data: { roleCode: 'student', userId: String(user?.id), password: data.initialPassword, rememberMe: false },
  386. })
  387. // 公开登录接口故意不泄露“账号已停用”,与账号/密码/身份不匹配统一返回 401。
  388. expect(rejected.status()).toBe(401)
  389. await page.locator('[data-role-code="student"]').click()
  390. const picker = page.locator('.login-identity-picker')
  391. await expect(picker).toBeVisible()
  392. await picker.click()
  393. await expect(page.locator('.login-identity-popper .el-select-dropdown__item').filter({ hasText: data.editedDisplayName })).toHaveCount(0)
  394. await captureScreenshot(page, testInfo, '10-disabled-user-login-rejected-and-hidden')
  395. await loginAsAdmin(page)
  396. await page.goto('/organization/users')
  397. await search(page, '请输入姓名、账号、部门或角色', data.username)
  398. row = tableRowByText(page, data.username)
  399. await row.getByRole('button', { name: '启用', exact: true }).click()
  400. await confirmMessageBox(page, '确认启用')
  401. await expect(tableRowByText(page, data.username)).toContainText('正常')
  402. expect((await apiUser(request, token))?.enabled).toBe(true)
  403. await logout(page)
  404. await selectStudentIdentity(page)
  405. await page.locator('input[name="password"]').fill(data.initialPassword)
  406. await page.getByRole('button', { name: '登录系统' }).click()
  407. await expect(page).toHaveURL(/\/change-password(?:\?|$)/)
  408. await captureScreenshot(page, testInfo, '11-reenabled-user-page-login-restored')
  409. await clearBrowserSession(page)
  410. await loginAsAdmin(page)
  411. })
  412. await test.step('用户页面确认删除后 API 无记录,角色解除关联后通过页面最终删除', async () => {
  413. await page.goto('/organization/users')
  414. await search(page, '请输入姓名、账号、部门或角色', data.username)
  415. let row = tableRowByText(page, data.username)
  416. await row.getByRole('button', { name: '删除', exact: true }).click()
  417. await expect(page.locator('.el-message-box:visible')).toContainText(data.username)
  418. await confirmMessageBox(page, '确认删除')
  419. await expect(tableRowByText(page, data.username)).toHaveCount(0)
  420. expect(await apiUser(request, token)).toBeUndefined()
  421. await captureScreenshot(page, testInfo, '12-user-page-delete-confirmed')
  422. await page.goto('/organization/roles')
  423. await search(page, '请输入角色名称、编码或说明', data.roleCode)
  424. row = tableRowByText(page, data.roleCode)
  425. await expect(row.getByRole('button', { name: '删除', exact: true })).toBeEnabled()
  426. await row.getByRole('button', { name: '删除', exact: true }).click()
  427. await confirmMessageBox(page, '删除角色')
  428. await expect(tableRowByText(page, data.roleCode)).toHaveCount(0)
  429. expect(await apiRole(request, token)).toBeUndefined()
  430. await captureScreenshot(page, testInfo, '13-unlinked-role-page-delete-confirmed')
  431. })
  432. } catch (error) {
  433. primaryError = error
  434. } finally {
  435. const cleanup = await cleanupCreatedData(request, token).catch((error) => ({ cleanupError: String(error) }))
  436. await attachJson(testInfo, 'cleanup-result', cleanup)
  437. }
  438. if (primaryError) throw primaryError
  439. expect(await apiUser(request, token)).toBeUndefined()
  440. expect(await apiRole(request, token)).toBeUndefined()
  441. expect(await apiDepartment(request, token, data.grandchildCode)).toBeUndefined()
  442. expect(await apiDepartment(request, token, data.childCode)).toBeUndefined()
  443. expect(await apiDepartment(request, token, data.parentCode)).toBeUndefined()
  444. })