import type { Page, Route } from '@playwright/test' import { captureScreenshot, expect, test } from './fixtures' const json = (route: Route, data: unknown) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 0, message: '成功', data, timestamp: Date.now(), requestId: 'inline-statistics-mock' }), }) const departments = [{ id: '100', code: 'ORG-100', name: '某类装备维修实训数字车间', parentId: null, enabled: true, isRoot: true, sortOrder: 10, directUserCount: 1, directChildCount: 1, version: 1, children: [{ id: '110', code: 'ORG-110', name: '信息中心', parentId: '100', enabled: true, sortOrder: 10, directUserCount: 3, directChildCount: 0, version: 1, children: [], }], }] const roles = [ { id: '1', code: 'admin', name: '管理员', shortName: '管', enabled: true, builtIn: true, isSuperAdmin: true, dataScopeCode: 'ALL', linkedUserCount: 1, permissionCount: 30, version: 1 }, { id: '2', code: 'teacher', name: '教员', shortName: '教', enabled: true, builtIn: true, isSuperAdmin: false, dataScopeCode: 'SYSTEM', linkedUserCount: 3, permissionCount: 12, version: 1 }, ] const menus = [{ id: 'group-system', parentId: null, code: 'group.system', name: '系统管理', description: '平台与权限配置', type: 'GROUP', sortOrder: 10, enabled: true, builtIn: true, dataVersion: 1, children: [ { id: 'system-users', parentId: 'group-system', code: 'system.users', name: '用户管理', description: '管理用户', type: 'PAGE', sortOrder: 10, enabled: true, builtIn: true, dataVersion: 1, children: [] }, { id: 'system-roles', parentId: 'group-system', code: 'system.roles', name: '角色管理', description: '管理角色', type: 'PAGE', sortOrder: 20, enabled: true, builtIn: true, dataVersion: 1, children: [] }, ], }] 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', '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' }) return json(route, {}) }) await page.addInitScript(() => { window.sessionStorage.setItem('unreal-tran:web:access-token:v1', 'inline-statistics-token') }) } 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 人'] }, { path: '/system/roles', testId: 'role-inline-statistics', removed: '.role-statistics', texts: ['共 2 个角色', '启用 2', '内置 2', '已分配 4 人'] }, { path: '/system/menus', testId: 'menu-inline-statistics', removed: '.menu-statistics', texts: ['共 3 个节点', '分组 1', '页面 2'] }, ] as const for (const item of cases) { await page.goto(item.path) const statistics = page.getByTestId(item.testId) 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) // 部门页同时保留左侧组织树,1280px 视口下工具条允许响应式换成两行。 expect(toolbarHeight).toBeLessThanOrEqual(120) if (item.path === '/system/roles') { const table = page.locator('.roles-table') await expect(page.locator('.role-card')).toHaveCount(0) await expect(table).toHaveClass(/el-table--border/) await expect(table).toHaveClass(/el-table--striped/) await expect(table.locator('.el-table__body-wrapper .el-table__row')).toHaveCount(2) const actions = table.locator('.role-table-actions').first() await expect(actions.getByRole('button', { name: '配置权限' })).toBeVisible() await expect(actions.getByRole('button', { name: '编辑' })).toBeVisible() const actionLayout = await actions.evaluate((element) => { const style = getComputedStyle(element) return { justifyContent: style.justifyContent, columnGap: style.columnGap } }) 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') })