import { captureScreenshot, expect, test } from './fixtures' import type { Page, Route } from '@playwright/test' const navigationModes = [ { label: '顶部一级 + 左侧二级', value: 'top-side' }, { label: '左侧分组菜单', value: 'side' }, { label: '顶部下拉菜单', value: 'top' }, ] as const const waitForDashboard = async (page: import('@playwright/test').Page) => { await expect(page.locator('.platform-shell')).toBeVisible() } const envelope = (data: unknown) => JSON.stringify({ code: 200, message: '成功', data, timestamp: '2026-08-30T10:00:00+08:00', requestId: 'ute2e-preferences', }) const fulfillJson = (route: Route, data: unknown) => route.fulfill({ status: 200, contentType: 'application/json', body: envelope(data), }) const installMockedSession = async (page: Page) => { await page.addInitScript(() => { sessionStorage.setItem('unreal-tran:web:access-token:v1', 'ute2e-preferences-token') }) await page.route('**/api/auth/v1/**', (route) => { const path = new URL(route.request().url()).pathname if (path === '/api/auth/v1/system-config/public') { return fulfillJson(route, { systemName: '装备维修实训数字车间', loginDescription: '统一承载内容制作、虚实训练、训练考核与资源管理。', defaultTheme: 'military', logoConfigured: false, faviconConfigured: false, loginLogoConfigured: false, }) } if (path === '/api/auth/v1/auth/me') { return fulfillJson(route, { user: { id: '1', username: 'admin', displayName: '系统管理员', departmentId: '10', departmentName: '信息中心', mustChangePassword: false, version: 1, }, activeRoleId: '100', roles: [{ id: '100', code: 'admin', name: '管理员', shortName: '管', status: 1, builtIn: 1, isSuperAdmin: 1, dataScopeCode: 'ALL', }], permissions: ['dashboard.view'], authorizationMode: 'SINGLE_ACTIVE', loginTime: 1788064800, }) } if (path === '/api/auth/v1/menus/navigation') return fulfillJson(route, []) return fulfillJson(route, {}) }) await page.route('**/api/tran/v1/**', (route) => fulfillJson(route, {})) await page.goto('/dashboard') await waitForDashboard(page) } const contrastRatio = (foreground: string, background: string) => { const channels = (value: string) => (value.match(/[\d.]+/g) ?? []).slice(0, 3).map(Number) const luminance = (value: string) => channels(value).reduce((sum, channel, index) => { const normalized = channel / 255 const linear = normalized <= .03928 ? normalized / 12.92 : ((normalized + .055) / 1.055) ** 2.4 return sum + linear * [.2126, .7152, .0722][index]! }, 0) const [lighter, darker] = [luminance(foreground), luminance(background)].sort((left, right) => right - left) return (lighter! + .05) / (darker! + .05) } test('三种导航模式与四种主题可以持久化', async ({ page }, testInfo) => { await page.emulateMedia({ colorScheme: 'dark' }) await installMockedSession(page) await expect(page.locator('.platform-shell')).toHaveAttribute('data-navigation-mode', 'top-side') await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') await expect(page.locator('html')).toHaveAttribute('data-color-theme', 'military') expect(await page.locator('html').evaluate((element) => getComputedStyle(element).getPropertyValue('--primary').trim())).toBe('#168a72') await expect(page.getByText('本地服务正常', { exact: true })).toHaveCount(0) await expect(page.getByText('平台服务正常', { exact: true })).toHaveCount(0) for (const mode of navigationModes) { await test.step(`导航模式:${mode.label}`, async () => { await page.getByRole('button', { name: '界面与导航设置' }).click() const drawer = page.locator('.el-drawer:visible') await drawer.getByRole('button', { name: new RegExp(mode.label.replace('+', '\\+')) }).click() await expect(page.locator('.platform-shell')).toHaveAttribute('data-navigation-mode', mode.value) await page.keyboard.press('Escape') await page.reload() await expect(page.locator('.platform-shell')).toHaveAttribute('data-navigation-mode', mode.value) await waitForDashboard(page) await captureScreenshot(page, testInfo, `navigation-${mode.value}`) }) } await test.step('桌面侧栏收起、刷新持久化与展开恢复', async () => { await page.getByRole('button', { name: '界面与导航设置' }).click() const drawer = page.locator('.el-drawer:visible') await drawer.getByRole('button', { name: /顶部一级 \+ 左侧二级/ }).click() await page.keyboard.press('Escape') await expect(drawer).toBeHidden() const shell = page.locator('.platform-shell') await expect(shell).toHaveAttribute('data-navigation-mode', 'top-side') const collapseButton = page.getByRole('button', { name: '收起左侧导航' }) await expect(collapseButton).toBeVisible() await collapseButton.click() await expect(shell).toHaveClass(/sidebar-collapsed/) await expect(page.getByRole('button', { name: '展开左侧导航' })).toBeVisible() await captureScreenshot(page, testInfo, 'sidebar-collapsed') await page.reload() await waitForDashboard(page) await expect(shell).toHaveClass(/sidebar-collapsed/) const expandButton = page.getByRole('button', { name: '展开左侧导航' }) await expect(expandButton).toBeVisible() await expandButton.click() await expect(shell).not.toHaveClass(/sidebar-collapsed/) await expect(page.getByRole('button', { name: '收起左侧导航' })).toBeVisible() await captureScreenshot(page, testInfo, 'sidebar-expanded') await page.reload() await waitForDashboard(page) await expect(shell).not.toHaveClass(/sidebar-collapsed/) }) const themes = [ { label: '军工绿', value: 'military', expected: 'light', primary: '#168a72' }, { label: '科技蓝', value: 'technology', expected: 'light', primary: '#197fc8' }, { label: '深空灰', value: 'graphite', expected: 'light', primary: '#4f8995' }, { label: '暗色', value: 'dark', expected: 'dark', primary: '#55d0bd' }, ] as const for (const theme of themes) { await test.step(`主题模式:${theme.label}`, async () => { await page.getByRole('button', { name: '界面与导航设置' }).click() const drawer = page.locator('.el-drawer:visible') await drawer.getByRole('button', { name: new RegExp(theme.label) }).click() await expect(page.locator('html')).toHaveAttribute('data-theme', theme.expected) await expect(page.locator('html')).toHaveAttribute('data-color-theme', theme.value) await page.keyboard.press('Escape') await page.reload() await expect(page.locator('html')).toHaveAttribute('data-theme', theme.expected) await expect(page.locator('html')).toHaveAttribute('data-color-theme', theme.value) await waitForDashboard(page) expect(await page.locator('html').evaluate((element) => getComputedStyle(element).getPropertyValue('--primary').trim())).toBe(theme.primary) if (theme.expected === 'dark') { const colors = await page.locator('html').evaluate(() => { const probe = document.createElement('div') probe.style.color = 'var(--text-primary)' probe.style.backgroundColor = 'var(--surface)' document.body.appendChild(probe) const result = { foreground: getComputedStyle(probe).color, background: getComputedStyle(probe).backgroundColor, } probe.remove() return result }) expect(contrastRatio(colors.foreground, colors.background)).toBeGreaterThanOrEqual(4.5) } await captureScreenshot(page, testInfo, `theme-${theme.value}`) }) } // 保持默认值,避免 headed 模式下人工继续检查时继承最后一次设置。 await page.getByRole('button', { name: '界面与导航设置' }).click() const drawer = page.locator('.el-drawer:visible') await drawer.getByRole('button', { name: /军工绿/ }).click() await drawer.getByRole('button', { name: /顶部一级 \+ 左侧二级/ }).click() })