From 64fa6478aab75ec969df9cf713067c987ba734dc Mon Sep 17 00:00:00 2001 From: leiyun Date: Wed, 26 Aug 2026 00:56:39 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E8=A6=86=E7=9B=96=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=94=B9=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/system-crud.spec.ts | 24 ++++----- tests/system-inline-statistics.spec.ts | 49 ++++++++++++++++++- tests/system-pages.spec.ts | 18 ++----- tests/users-department-filter.spec.ts | 18 +++---- tests/zentao-system-ui-regression.spec.ts | 59 +++++++++++++++++------ 5 files changed, 118 insertions(+), 50 deletions(-) diff --git a/tests/system-crud.spec.ts b/tests/system-crud.spec.ts index 10b49d6..981e13d 100644 --- a/tests/system-crud.spec.ts +++ b/tests/system-crud.spec.ts @@ -93,25 +93,27 @@ async function editAndSearchData(page: Page) { await expect(roleCardByText(page, `${data.prefix} 已编辑`)).toBeVisible() await page.goto('/system/departments') - await page.getByPlaceholder('请输入部门、编码、负责人或层级路径').fill(data.departmentCode) + await page.getByPlaceholder('请输入部门、编码或负责人').fill(data.departmentCode) await page.getByRole('button', { name: '搜索', exact: true }).click() await expect(tableRowByText(page, data.departmentName)).toBeVisible() } async function grantPermission(page: Page) { - await page.goto('/system/permissions') - const roleButton = page.locator('.role-selector > button').filter({ hasText: data.roleName }) + await page.goto('/system/roles') + await page.getByPlaceholder('请输入角色名称、编码或说明').fill(data.roleCode) + await page.getByRole('button', { name: '搜索', exact: true }).click() + const roleRow = roleCardByText(page, data.roleName) await Promise.all([ page.waitForResponse((response) => response.request().method() === 'GET' && /\/permissions\/roles\/[^/?]+/.test(response.url()) && response.ok()), - roleButton.click(), + roleRow.getByRole('button', { name: '配置权限' }).click(), ]) - await expect(roleButton).toHaveClass(/active/) - await expect(page.locator('.selected-role-copy h2')).toHaveText(data.roleName) + const drawer = page.locator('.role-permission-drawer') + await expect(drawer).toBeVisible() + await expect(drawer.locator('.role-permission-summary h3')).toHaveText(data.roleName) const auditCode = page.locator('code').filter({ hasText: /^system\.audit$/ }) - const auditPermission = page.locator('.permission-item').filter({ has: auditCode }) - await expect(auditPermission).toHaveAttribute('aria-checked', 'false') - await auditPermission.click() - const saveButton = page.getByRole('button', { name: '保存权限配置' }) + const auditPermission = drawer.locator('.el-tree-node').filter({ has: auditCode }).last() + await auditPermission.locator('.el-checkbox').click() + const saveButton = drawer.getByRole('button', { name: '保存授权' }) await expect(saveButton).toBeEnabled() await saveButton.click() await expect(page.getByText(/权限已保存|当前权限配置已保存/).last()).toBeVisible() @@ -157,7 +159,7 @@ async function deleteRoleIfPresent(page: Page) { async function deleteDepartmentIfPresent(page: Page) { await dismissOverlays(page) await page.goto('/system/departments') - await page.getByPlaceholder('请输入部门、编码、负责人或层级路径').fill(data.departmentCode) + await page.getByPlaceholder('请输入部门、编码或负责人').fill(data.departmentCode) await page.getByRole('button', { name: '搜索', exact: true }).click() const row = tableRowByText(page, data.departmentName) if (!(await row.waitFor({ state: 'visible', timeout: 5_000 }).then(() => true).catch(() => false))) return false diff --git a/tests/system-inline-statistics.spec.ts b/tests/system-inline-statistics.spec.ts index c4d1dcf..c739749 100644 --- a/tests/system-inline-statistics.spec.ts +++ b/tests/system-inline-statistics.spec.ts @@ -31,19 +31,34 @@ const menus = [{ ], }] +const menuWrites: Array> = [] + async function mockSystemPageApi(page: Page) { await page.route(/\/api\/(?:auth|tran)\//, async (route) => { const path = new URL(route.request().url()).pathname if (path.endsWith('/auth/me')) return json(route, { userId: '1', username: 'admin', displayName: '系统管理员', departmentId: '110', departmentName: '信息中心', activeRoleId: '1', roles, authorizationMode: 'SINGLE_ACTIVE', mustChangePassword: false, - permissions: ['system.departments', 'system.roles', 'system.menus', 'system.permissions', 'system.departments.create', 'system.roles.create', 'system.roles.update', 'system.roles.delete', 'system.menus.update'], + permissions: ['system.departments', 'system.roles', 'system.menus', 'system.permissions', 'system.departments.create', 'system.roles.create', 'system.roles.update', 'system.roles.delete', 'system.menus.update', 'system.interfaces', 'system.config'], }) if (path.endsWith('/departments/tree')) return json(route, departments) + if (path.endsWith('/departments')) return json(route, { records: [departments[0], departments[0].children[0]], total: 2, page: 1, size: 10 }) if (path.endsWith('/departments/stats')) return json(route, { total: 2, enabled: 2, maxDepth: 2, assignedUsers: 4 }) if (path.endsWith('/roles/all')) return json(route, roles) if (path.endsWith('/roles/stats')) return json(route, { total: 2, enabled: 2, builtIn: 2, assignedUsers: 4 }) + if (path.endsWith('/menus/configuration') && route.request().method() === 'PUT') { + const payload = route.request().postDataJSON() as Record + menuWrites.push(payload) + const groups = payload.groups as Array<{ enabled?: boolean; items?: Array<{ enabled?: boolean }> }> + const saved = structuredClone(menus) + saved[0]!.enabled = groups[0]?.enabled ?? true + saved[0]!.children[0]!.enabled = groups[0]?.items?.[0]?.enabled ?? true + return json(route, saved) + } if (path.endsWith('/menus/tree') || path.endsWith('/menus/navigation')) return json(route, menus) + if (path.endsWith('/interfaces/stats')) return json(route, { total: 1, enabled: 1, disabled: 0, up: 1, down: 0 }) + if (path.endsWith('/interfaces')) return json(route, { records: [{ id: '1', code: 'training', name: '训练服务', endpoint: '/api/tran/v1', transport: 'REST', dataMode: 'LOCAL', enabled: true, configStatus: 'READY', healthStatus: 'UP', version: 1 }], total: 1, page: 1, size: 10 }) + if (path.endsWith('/system-config')) return json(route, { systemName: '某类装备维修实训数字车间', systemNameConfigured: true, version: 1 }) if (path.endsWith('/system-config/public')) return json(route, { systemName: '某类装备维修实训数字车间', logoConfigured: false, faviconConfigured: false }) if (path.endsWith('/auth/ping')) return json(route, { service: 'ut-auth', status: 'UP' }) if (path.endsWith('/tran/ping')) return json(route, { service: 'ut-tran', status: 'UP' }) @@ -56,6 +71,7 @@ async function mockSystemPageApi(page: Page) { test('部门、角色、菜单统计收进列表工具栏', async ({ page }, testInfo) => { await mockSystemPageApi(page) + menuWrites.length = 0 const cases = [ { path: '/system/departments', testId: 'department-inline-statistics', removed: '.department-stats', texts: ['共 2 个部门', '启用 2', '2 级组织', '归属 4 人'] }, @@ -69,9 +85,11 @@ test('部门、角色、菜单统计收进列表工具栏', async ({ page }, tes await expect(statistics).toBeVisible() await expect(page.locator(item.removed)).toHaveCount(0) for (const text of item.texts) await expect(statistics).toContainText(text) + await expect(statistics).not.toContainText('当前') await expect(statistics.locator('.is-danger')).toHaveCount(0) const toolbarHeight = await statistics.locator('xpath=ancestor::*[contains(@class, "system-list-toolbar")]').evaluate((toolbar) => toolbar.getBoundingClientRect().height) - expect(toolbarHeight).toBeLessThanOrEqual(64) + // 部门页同时保留左侧组织树,1280px 视口下工具条允许响应式换成两行。 + expect(toolbarHeight).toBeLessThanOrEqual(120) if (item.path === '/system/roles') { const table = page.locator('.roles-table') await expect(page.locator('.role-card')).toHaveCount(0) @@ -88,6 +106,33 @@ test('部门、角色、菜单统计收进列表工具栏', async ({ page }, tes expect(actionLayout.justifyContent).toBe('flex-start') expect(actionLayout.columnGap).toBe('12px') } + if (item.path === '/system/menus') { + const switchControl = page.getByRole('switch', { name: '用户管理状态' }) + await expect(switchControl).toBeChecked() + await page.locator('.el-switch').filter({ has: switchControl }).click() + await page.getByRole('button', { name: '保存全部' }).click() + await expect.poll(() => menuWrites.length).toBe(1) + const groups = menuWrites[0]!.groups as Array<{ items: Array<{ enabled: boolean }> }> + expect(groups[0]!.items[0]!.enabled).toBe(false) + } await captureScreenshot(page, testInfo, item.testId) } }) + +test('系统配置刷新与标题同行,接口列表不再显示装饰图标', async ({ page }, testInfo) => { + await mockSystemPageApi(page) + await page.goto('/system/config') + const header = page.locator('.system-config-form-header') + await expect(header.getByRole('button', { name: '刷新配置' })).toBeVisible() + await expect(page.locator('.content-action-bar')).toHaveCount(0) + const [titleBox, actionBox] = await Promise.all([ + header.getByRole('heading', { name: '通用配置' }).boundingBox(), + header.getByRole('button', { name: '刷新配置' }).boundingBox(), + ]) + expect(Math.abs(titleBox!.y - actionBox!.y)).toBeLessThan(24) + + await page.goto('/system/interfaces') + await expect(page.locator('.interface-identity > span')).toHaveCount(0) + await expect(page.locator('.interface-identity').first()).toContainText('训练服务') + await captureScreenshot(page, testInfo, 'system-config-and-interface-list') +}) diff --git a/tests/system-pages.spec.ts b/tests/system-pages.spec.ts index 00f7dc6..92fe933 100644 --- a/tests/system-pages.spec.ts +++ b/tests/system-pages.spec.ts @@ -6,12 +6,11 @@ const pages = [ ['/system/departments', '部门管理', 'system-departments', '.department-table .el-table__body-wrapper .el-table__row'], ['/system/roles', '角色管理', 'system-roles', '.roles-table .el-table__body-wrapper .el-table__row'], ['/system/menus', '菜单管理', 'system-menus', '.menu-table-panel .el-table__body-wrapper .el-table__row'], - ['/system/permissions', '权限管理', 'system-permissions', '.permission-group'], ['/system/audit', '审计日志', 'system-audit', '.el-table__body-wrapper .el-table__row'], ['/system/interfaces', '平台服务配置', 'system-interfaces', '.interfaces-table .el-table__body-wrapper .el-table__row'], ] as const -test('系统管理七个页面均可加载', async ({ page }, testInfo) => { +test('系统管理页面均可加载且权限配置收口到角色抽屉', async ({ page }, testInfo) => { await loginAsAdmin(page) for (const [path, heading, screenshotLabel, readySelector] of pages) { @@ -57,6 +56,10 @@ test('系统管理七个页面均可加载', async ({ page }, testInfo) => { await expect(page.locator('.role-statistics')).toHaveCount(0) await expect(page.getByTestId('role-inline-statistics')).toContainText('个角色') await expect(page.getByTestId('role-inline-statistics')).toContainText('内置') + const permissionButton = page.locator('.roles-table .role-table-actions').first().getByRole('button', { name: '配置权限' }) + await permissionButton.click() + await expect(page.locator('.role-permission-drawer')).toBeVisible() + await page.locator('.role-permission-drawer .el-drawer__close-btn').click() } if (path === '/system/menus') { await expect(page.locator('.menu-statistics')).toHaveCount(0) @@ -71,17 +74,6 @@ test('系统管理七个页面均可加载', async ({ page }, testInfo) => { await expect(page.getByTestId('interface-inline-statistics')).toContainText('个服务') await expect(page.locator('.system-pagination-total')).toBeVisible() } - if (path === '/system/permissions') { - const permissionCopies = page.locator('.permission-item .permission-copy') - await expect(permissionCopies.first()).toBeVisible() - expect(await permissionCopies.count()).toBeGreaterThan(20) - const incomplete = await permissionCopies.evaluateAll((items) => items.filter((item) => { - const title = item.querySelector('b')?.textContent?.trim() - const code = item.querySelector('code')?.textContent?.trim() - return !title || !code - }).length) - expect(incomplete).toBe(0) - } await captureScreenshot(page, testInfo, screenshotLabel) }) } diff --git a/tests/users-department-filter.spec.ts b/tests/users-department-filter.spec.ts index ede6dc4..b3b115b 100644 --- a/tests/users-department-filter.spec.ts +++ b/tests/users-department-filter.spec.ts @@ -114,12 +114,13 @@ test('用户列表全局中文并支持部门树选择与再次点击取消', as await expect(page.locator('.el-pagination__sizes')).toContainText('条/页') await expect(page.locator('.el-pagination__jump')).toContainText('前往') await expect(page.locator('.system-pagination-total')).toHaveText('共 4 条记录') - await expect(page.locator('.department-filter-panel')).toBeVisible() - await expect(page.locator('.department-filter-panel.system-panel')).toBeVisible() + await expect(page.locator('.department-scope-panel')).toBeVisible() + await expect(page.locator('.department-scope-panel.system-panel')).toBeVisible() await expect(page.locator('.user-list-main.system-panel.system-list-card')).toBeVisible() await expect(page.locator('.user-stats')).toHaveCount(0) const inlineStatistics = page.getByTestId('user-inline-statistics') - await expect(inlineStatistics).toContainText('当前 4 条,共 4 个账号') + await expect(inlineStatistics).toContainText('共 4 个账号') + await expect(inlineStatistics).not.toContainText('当前') await expect(inlineStatistics).toContainText('正常 3') await expect(inlineStatistics).toContainText('停用 1') await expect(inlineStatistics).toContainText('待改密 1') @@ -153,7 +154,7 @@ test('用户列表全局中文并支持部门树选择与再次点击取消', as expect(keywordWidth).toBeLessThanOrEqual(391) const panelsAreSeparate = await page.locator('.user-content-layout').evaluate((container) => { - const departmentPanel = container.querySelector('.department-filter-panel') + const departmentPanel = container.querySelector('.department-scope-panel') const listPanel = container.querySelector('.user-list-main') return departmentPanel !== listPanel && departmentPanel?.parentElement === container @@ -162,7 +163,7 @@ test('用户列表全局中文并支持部门树选择与再次点击取消', as expect(panelsAreSeparate).toBe(true) const normalFontSize = await page.locator('.users-table .el-table__body .cell').first().evaluate((element) => Number.parseFloat(getComputedStyle(element).fontSize)) - const hintFontSize = await page.locator('.department-filter-state').evaluate((element) => Number.parseFloat(getComputedStyle(element).fontSize)) + const hintFontSize = await page.locator('.department-scope-node__name').first().evaluate((element) => Number.parseFloat(getComputedStyle(element).fontSize)) expect(normalFontSize).toBeGreaterThanOrEqual(14) expect(hintFontSize).toBeGreaterThanOrEqual(13) @@ -197,16 +198,15 @@ test('用户列表全局中文并支持部门树选择与再次点击取消', as const disabledUserRow = page.locator('.users-table .el-table__row').filter({ hasText: '李教员' }) await expect(disabledUserRow.getByRole('button', { name: '启用' })).toHaveClass(/el-button--success/) - const departmentNode = page.locator('.department-filter-tree .el-tree-node__content').filter({ hasText: '维修改研室' }).first() + const departmentNode = page.locator('.department-scope-tree .el-tree-node__content').filter({ hasText: '维修改研室' }).first() await departmentNode.click() - await expect(page.locator('.department-filter-state')).toContainText('已选择:维修改研室') + await expect(departmentNode.locator('xpath=..')).toHaveClass(/is-current/) await expect.poll(() => requestedDepartments.at(-1)).toBe('200') await expect(page.locator('.users-table .el-table__body-wrapper .el-table__row')).toHaveCount(2) await captureScreenshot(page, testInfo, 'users-department-selected') await departmentNode.click() - await expect(page.locator('.department-filter-state')).toContainText('当前显示全部部门') - await expect(page.locator('.department-filter-tree .el-tree-node.is-current')).toHaveCount(0) + await expect(page.locator('.department-scope-tree .el-tree-node.is-current')).toHaveCount(0) await expect.poll(() => requestedDepartments.at(-1)).toBeNull() await expect(page.locator('.users-table .el-table__body-wrapper .el-table__row')).toHaveCount(4) diff --git a/tests/zentao-system-ui-regression.spec.ts b/tests/zentao-system-ui-regression.spec.ts index ce9ce38..eea2ab0 100644 --- a/tests/zentao-system-ui-regression.spec.ts +++ b/tests/zentao-system-ui-regression.spec.ts @@ -49,11 +49,18 @@ async function mockSystemApi(page: Page) { if (path.endsWith('/auth/me')) return json(route, { userId: '1', username: 'admin', displayName: '系统管理员', departmentId: '100', departmentName: '数字车间', activeRoleId: '1', roles: [roles[1]], authorizationMode: 'SINGLE_ACTIVE', mustChangePassword: false, - permissions: ['system.departments', 'system.permissions', 'system.permissions.update'], + permissions: ['system.departments', 'system.departments.update', 'system.roles', 'system.permissions', 'system.permissions.update'], }) if (path.endsWith('/departments/tree')) return json(route, departments) + if (path.endsWith('/departments')) { + const keyword = new URL(route.request().url()).searchParams.get('keyword')?.trim() ?? '' + const records = keyword ? [] : [departments[0], departments[0].children[0]] + return json(route, { records, total: records.length, page: 1, size: 10 }) + } if (path.endsWith('/departments/stats')) return json(route, { total: 2, enabled: 2, maxDepth: 2, assignedUsers: 4 }) if (path.endsWith('/roles/all')) return json(route, roles) + if (path.endsWith('/roles/stats')) return json(route, { total: 2, enabled: 2, builtIn: 2, assignedUsers: 4 }) + 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 }) if (path.endsWith('/permissions/catalog')) return json(route, { sections: permissionSections }) if (/\/permissions\/roles\/\d+$/.test(path)) return json(route, { permissionCodes: [], version: 1 }) if (path.endsWith('/menus/navigation')) return json(route, []) @@ -82,27 +89,49 @@ test('Bug 850:无匹配部门时只保留工具栏重置入口', async ({ page await captureScreenshot(page, testInfo, 'bug-850-empty-without-duplicate-reset') }) -test('Bug 853:权限操作栏在内容滚动过程中固定于可视区底部', async ({ page }, testInfo) => { +test('角色配置权限改为固定头尾的授权抽屉', async ({ page }, testInfo) => { await mockSystemApi(page) await page.setViewportSize({ width: 1440, height: 760 }) - await page.goto('/system/permissions') + await page.goto('/system/roles') + await page.locator('.roles-table .el-table__row').filter({ hasText: '教员' }).getByRole('button', { name: '配置权限' }).click() - const routeView = page.locator('.route-view') - const footer = page.locator('.permission-actions') - await expect(page.locator('.permission-group')).toHaveCount(permissionSections.length) + const drawer = page.locator('.role-permission-drawer') + const body = drawer.locator('.el-drawer__body') + const footer = drawer.locator('.el-drawer__footer') + await expect(drawer).toBeVisible() + await expect(drawer.locator('.role-permission-node.is-section')).toHaveCount(permissionSections.length) await expect(footer).toBeVisible() - await routeView.evaluate((element) => { element.scrollTop = Math.round(element.scrollHeight * 0.45) }) - await expect.poll(() => routeView.evaluate((element) => element.scrollTop)).toBeGreaterThan(100) + const scroll = drawer.locator('.role-permission-tree-scroll') + await scroll.evaluate((element) => { element.scrollTop = element.scrollHeight }) + await expect.poll(() => scroll.evaluate((element) => element.scrollTop)).toBeGreaterThan(100) - const [routeBox, footerBox, position] = await Promise.all([ - routeView.boundingBox(), + const [drawerBox, footerBox] = await Promise.all([ + drawer.boundingBox(), footer.boundingBox(), - footer.evaluate((element) => getComputedStyle(element).position), ]) - expect(routeBox).not.toBeNull() + expect(drawerBox).not.toBeNull() expect(footerBox).not.toBeNull() - expect(position).toBe('sticky') - expect(Math.abs((routeBox!.y + routeBox!.height) - (footerBox!.y + footerBox!.height))).toBeLessThanOrEqual(20) - await captureScreenshot(page, testInfo, 'bug-853-sticky-permission-actions') + expect(Math.abs((drawerBox!.y + drawerBox!.height) - (footerBox!.y + footerBox!.height))).toBeLessThanOrEqual(2) + await captureScreenshot(page, testInfo, 'role-permission-drawer-fixed-actions') +}) + +test('编辑部门使用统一选人组件', async ({ page }, testInfo) => { + await mockSystemApi(page) + await page.setViewportSize({ width: 1440, height: 800 }) + await page.goto('/system/departments') + const row = page.locator('.department-table .el-table__row').filter({ hasText: '维修教研室' }) + await row.getByRole('button', { name: '编辑' }).click() + const editor = page.locator('.el-dialog:visible').filter({ hasText: '编辑部门' }) + await expect(editor).toBeVisible() + await editor.getByRole('button', { name: '选择用户' }).click() + const picker = page.locator('.el-dialog:visible').filter({ hasText: '选择部门负责人' }) + await expect(picker).toBeVisible() + await expect(picker.getByRole('button', { name: '搜索', exact: true })).toBeVisible() + await expect(picker.getByRole('button', { name: '重置筛选', exact: true })).toBeVisible() + expect((await picker.boundingBox())!.width).toBeGreaterThan(1000) + await picker.getByRole('button', { name: '选择', exact: true }).click() + await picker.getByRole('button', { name: '确认选择' }).click() + await expect(editor).toContainText('周教员') + await captureScreenshot(page, testInfo, 'department-editor-user-picker') })