Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

140 řádky
7.5 KiB

  1. import type { Page, Route } from '@playwright/test'
  2. import { captureScreenshot, expect, test } from './fixtures'
  3. const json = (route: Route, data: unknown) => route.fulfill({
  4. status: 200,
  5. contentType: 'application/json',
  6. body: JSON.stringify({ code: 0, message: '成功', data, timestamp: Date.now(), requestId: 'zentao-system-ui-regression' }),
  7. })
  8. const roles = [
  9. {
  10. id: '10', code: 'teacher', name: '教员', shortName: '教', description: '教员业务角色', enabled: true,
  11. builtIn: true, isSuperAdmin: false, dataScopeCode: 'SYSTEM', linkedUserCount: 3, permissionCount: 4,
  12. sortOrder: 10, version: 1,
  13. },
  14. {
  15. id: '1', code: 'admin', name: '管理员', shortName: '管', description: '系统管理员', enabled: true,
  16. builtIn: true, isSuperAdmin: true, dataScopeCode: 'ALL', linkedUserCount: 1, permissionCount: 40,
  17. sortOrder: 20, version: 1,
  18. },
  19. ]
  20. const departments = [{
  21. id: '100', code: 'ORG-100', name: '某类装备维修实训数字车间', parentId: null, enabled: true,
  22. builtIn: true, isRoot: true, sortOrder: 10, directUserCount: 1, directChildCount: 1,
  23. descendantCount: 1, version: 1, updatedAt: '2026-08-16T10:00:00+08:00',
  24. children: [{
  25. id: '110', code: 'ORG-110', name: '维修教研室', parentId: '100', enabled: true,
  26. builtIn: false, isRoot: false, sortOrder: 10, directUserCount: 3, directChildCount: 0,
  27. descendantCount: 0, version: 1, updatedAt: '2026-08-16T10:00:00+08:00', children: [],
  28. }],
  29. }]
  30. const permissionSections = Array.from({ length: 6 }, (_, sectionIndex) => ({
  31. id: `section-${sectionIndex}`,
  32. label: `权限分组 ${sectionIndex + 1}`,
  33. items: Array.from({ length: 8 }, (_, itemIndex) => ({
  34. code: `mock.section${sectionIndex}.action${itemIndex}`,
  35. label: `业务权限 ${sectionIndex + 1}-${itemIndex + 1}`,
  36. description: '用于验证长权限目录中的底部操作栏',
  37. type: 'ACTION',
  38. })),
  39. }))
  40. async function mockSystemApi(page: Page) {
  41. await page.route(/\/api\/(?:auth|tran)\//, async (route) => {
  42. const path = new URL(route.request().url()).pathname
  43. if (path.endsWith('/auth/me')) return json(route, {
  44. userId: '1', username: 'admin', displayName: '系统管理员', departmentId: '100', departmentName: '数字车间',
  45. activeRoleId: '1', roles: [roles[1]], authorizationMode: 'SINGLE_ACTIVE', mustChangePassword: false,
  46. permissions: ['system.departments', 'system.departments.update', 'system.roles', 'system.permissions', 'system.permissions.update'],
  47. })
  48. if (path.endsWith('/departments/tree')) return json(route, departments)
  49. if (path.endsWith('/departments')) {
  50. const keyword = new URL(route.request().url()).searchParams.get('keyword')?.trim() ?? ''
  51. const records = keyword ? [] : [departments[0], departments[0].children[0]]
  52. return json(route, { records, total: records.length, page: 1, size: 10 })
  53. }
  54. if (path.endsWith('/departments/stats')) return json(route, { total: 2, enabled: 2, maxDepth: 2, assignedUsers: 4 })
  55. if (path.endsWith('/roles/all')) return json(route, roles)
  56. if (path.endsWith('/roles/stats')) return json(route, { total: 2, enabled: 2, builtIn: 2, assignedUsers: 4 })
  57. if (path.endsWith('/users')) return json(route, { records: [{ id: '20', username: 'teacher', displayName: '周教员', departmentId: '110', departmentName: '维修教研室', roleIds: ['10'], enabled: true }], total: 1, page: 1, size: 10 })
  58. if (path.endsWith('/permissions/catalog')) return json(route, { sections: permissionSections })
  59. if (/\/permissions\/roles\/\d+$/.test(path)) return json(route, { permissionCodes: [], version: 1 })
  60. if (path.endsWith('/menus/navigation')) return json(route, [])
  61. if (path.endsWith('/system-config/public')) return json(route, { systemName: '某类装备维修实训数字车间' })
  62. if (path.endsWith('/auth/ping')) return json(route, { service: 'ut-auth', status: 'UP' })
  63. if (path.endsWith('/tran/ping')) return json(route, { service: 'ut-tran', status: 'UP' })
  64. return json(route, {})
  65. })
  66. await page.addInitScript(() => {
  67. window.sessionStorage.setItem('unreal-tran:web:access-token:v1', 'zentao-system-ui-regression-token')
  68. })
  69. }
  70. test('Bug 850:无匹配部门时只保留工具栏重置入口', async ({ page }, testInfo) => {
  71. await mockSystemApi(page)
  72. await page.goto('/system/departments')
  73. await page.getByPlaceholder('请输入部门、编码或负责人').fill('绝对不存在的部门')
  74. await page.getByRole('button', { name: '搜索', exact: true }).click()
  75. const empty = page.locator('.department-table-body .el-empty')
  76. await expect(empty).toBeVisible()
  77. await expect(empty).toContainText('没有符合当前筛选条件的部门')
  78. await expect(empty.getByRole('button', { name: '清除筛选' })).toHaveCount(0)
  79. await expect(page.locator('.department-toolbar').getByRole('button', { name: '重置', exact: true })).toBeVisible()
  80. await captureScreenshot(page, testInfo, 'bug-850-empty-without-duplicate-reset')
  81. })
  82. test('角色配置权限改为固定头尾的授权抽屉', async ({ page }, testInfo) => {
  83. await mockSystemApi(page)
  84. await page.setViewportSize({ width: 1440, height: 760 })
  85. await page.goto('/system/roles')
  86. await page.locator('.roles-table .el-table__row').filter({ hasText: '教员' }).getByRole('button', { name: '配置权限' }).click()
  87. const drawer = page.locator('.role-permission-drawer')
  88. const body = drawer.locator('.el-drawer__body')
  89. const footer = drawer.locator('.el-drawer__footer')
  90. await expect(drawer).toBeVisible()
  91. await expect(drawer.locator('.role-permission-node.is-section')).toHaveCount(permissionSections.length)
  92. await expect(footer).toBeVisible()
  93. const scroll = drawer.locator('.role-permission-tree-scroll')
  94. await scroll.evaluate((element) => { element.scrollTop = element.scrollHeight })
  95. await expect.poll(() => scroll.evaluate((element) => element.scrollTop)).toBeGreaterThan(100)
  96. const [drawerBox, footerBox] = await Promise.all([
  97. drawer.boundingBox(),
  98. footer.boundingBox(),
  99. ])
  100. expect(drawerBox).not.toBeNull()
  101. expect(footerBox).not.toBeNull()
  102. expect(Math.abs((drawerBox!.y + drawerBox!.height) - (footerBox!.y + footerBox!.height))).toBeLessThanOrEqual(2)
  103. await captureScreenshot(page, testInfo, 'role-permission-drawer-fixed-actions')
  104. await page.locator('.el-overlay.is-drawer').filter({ has: drawer }).click({ position: { x: 12, y: 12 } })
  105. await expect(drawer).toBeHidden()
  106. })
  107. test('编辑部门使用统一选人组件', async ({ page }, testInfo) => {
  108. await mockSystemApi(page)
  109. await page.setViewportSize({ width: 1440, height: 800 })
  110. await page.goto('/system/departments')
  111. const row = page.locator('.department-table .el-table__row').filter({ hasText: '维修教研室' })
  112. await row.getByRole('button', { name: '编辑' }).click()
  113. const editor = page.locator('.el-dialog:visible').filter({ hasText: '编辑部门' })
  114. await expect(editor).toBeVisible()
  115. await editor.getByRole('button', { name: '选择用户' }).click()
  116. const picker = page.locator('.el-dialog:visible').filter({ hasText: '选择部门负责人' })
  117. await expect(picker).toBeVisible()
  118. await expect(picker.getByRole('button', { name: '搜索', exact: true })).toBeVisible()
  119. await expect(picker.getByRole('button', { name: '重置筛选', exact: true })).toBeVisible()
  120. expect((await picker.boundingBox())!.width).toBeGreaterThan(1000)
  121. await picker.getByRole('button', { name: '选择', exact: true }).click()
  122. await picker.getByRole('button', { name: '确认选择' }).click()
  123. await expect(editor).toContainText('周教员')
  124. await captureScreenshot(page, testInfo, 'department-editor-user-picker')
  125. })