import { test, expect, type Page } from '@playwright/test' // Browser fixtures exercise UI states without sending commands to PVE or a terminal. async function fixtures(page: Page, role = 'super_admin', state = 'connected') { const now = new Date().toISOString() const desktop = { id: 'ui-desk', name: '界面测试桌面', os: 'Ubuntu', node: 'ui-node', vmid: 9000, pool: '', powerState: 'running', serviceState: 'unknown', provider: 'proxmox', endpoint: 'desktop.example', app: 'Desktop', width: 2560, height: 1440, fps: 45, updatedAt: now } const terminal = { id: 'ui-terminal', sn: 'UI-TEST-DEVICE', name: '界面测试终端', group: '测试组', status: 'online', agentVersion: 'ui-fixture', lastSeen: now, sessionDesktopId: desktop.id, sessionState: state, configVersion: 1, fps: null, rtt: null } const commands: unknown[] = [] await page.addInitScript(() => sessionStorage.setItem('vdi.accessToken', 'ui-fixture-token')) await page.route('**/api/v1/**', async route => { const request = route.request(), endpoint = new URL(request.url()).pathname.replace('/api/v1', '') if (request.method() === 'POST' && endpoint.endsWith('/commands')) { commands.push(request.postDataJSON()) await route.fulfill({ status: 202, json: { id: 'ui-command', kind: 'terminal', action: 'reset-session', targetId: terminal.id, targetName: terminal.name, actor: 'ui-admin', status: 'queued', message: '等待回执', providerRef: '', createdAt: now, updatedAt: now } }) return } if (endpoint === '/auth/me') { await route.fulfill({ json: { id: 'ui-admin', username: 'ui-admin', displayName: '界面测试管理员', role } }); return } if (endpoint === '/settings') { await route.fulfill({ json: { mode: 'production', computeProvider: 'proxmox', pveConfigured: true, terminalTransport: 'https-poll', capabilities: [] } }); return } if (endpoint === '/overview') { await route.fulfill({ json: { terminalCount: 1, onlineTerminals: 1, desktopCount: 1, runningDesktops: 1, activeSessions: state === 'connected' ? 1 : 0, failedTasks: 0, mode: 'production' } }); return } const lists: Record = { '/desktops': [desktop], '/terminals': [terminal], '/users': [{ id: 'ui-user', username: 'ui-user', displayName: '测试使用者', enabled: true, terminalId: terminal.id, desktopIds: [desktop.id] }], '/resources': [{ id: 'ui-node', name: 'ui-node', status: 'online', cpu: .2, memory: .4, storage: .3, provider: 'proxmox', updatedAt: now }], '/tasks': [], '/audit-logs': [], } if (endpoint in lists) { const items = lists[endpoint] || []; await route.fulfill({ json: { items, page: 1, pageSize: 100, total: items.length, requestId: 'ui-fixture' } }); return } await route.fulfill({ status: 404, json: { code: 'fixture_not_found', message: '测试接口未定义', requestId: 'ui-fixture' } }) }) return commands } test('远程会话保留缺失指标,取消断开不发命令,确认后使用现有接口', async ({ page }, testInfo) => { const commands = await fixtures(page) await page.setViewportSize({ width: 1440, height: 1000 }) await page.goto('/sessions') await expect(page.getByRole('heading', { name: '会话信息', exact: true })).toBeVisible() await expect(page.getByText('桌面正在终端显示')).toBeVisible() await expect(page.getByText('暂无数据', { exact: true })).toHaveCount(2) await expect(page.getByText('尚未采集', { exact: true })).toBeVisible() await page.screenshot({ path: testInfo.outputPath('session-fixture-dark.png'), fullPage: true, animations: 'disabled' }) await page.getByRole('button', { name: '切换浅色主题' }).click() await page.screenshot({ path: testInfo.outputPath('session-fixture-light.png'), fullPage: true, animations: 'disabled' }) await page.getByRole('button', { name: '断开连接', exact: true }).click() await page.getByRole('button', { name: '取消', exact: true }).click() expect(commands).toHaveLength(0) await page.getByRole('button', { name: '断开连接', exact: true }).click() await page.getByRole('button', { name: '确认提交', exact: true }).click() await expect(page.getByText('断开命令已提交,等待终端回执')).toBeVisible() expect(commands).toEqual([{ action: 'reset-session' }]) }) test('只读角色可查看会话,没有断开按钮与系统设置入口', async ({ page }) => { await fixtures(page, 'viewer') await page.goto('/sessions') await expect(page.getByRole('heading', { name: '会话信息', exact: true })).toBeVisible() await expect(page.getByRole('button', { name: '断开连接', exact: true })).toHaveCount(0) await expect(page.getByRole('navigation', { name: '主导航' }).getByRole('link', { name: '系统设置' })).toHaveCount(0) await page.goto('/settings') await expect(page.getByText('无访问权限', { exact: true })).toBeVisible() }) test('连接预约不显示为已连接,未上报指标保持为空', async ({ page }) => { await fixtures(page, 'operator', 'connecting') await page.goto('/sessions') await expect(page.getByText('终端正在建立连接')).toBeVisible() await expect(page.getByText('0 个已连接', { exact: true })).toBeVisible() await expect(page.getByText('1 个连接中', { exact: true })).toBeVisible() await expect(page.getByText('暂无数据', { exact: true })).toHaveCount(2) }) test('深浅主题跨页面和刷新保留,窄屏导航可用', async ({ page }) => { await fixtures(page) await page.setViewportSize({ width: 390, height: 844 }) await page.goto('/overview') await expect(page.getByRole('heading', { name: '运行总览' })).toBeVisible() await page.getByRole('button', { name: '切换浅色主题' }).click() await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') await page.getByRole('button', { name: '打开导航' }).click() await page.getByRole('navigation', { name: '主导航' }).getByRole('link', { name: '桌面管理' }).click() await expect(page.getByRole('heading', { name: '桌面管理', exact: true })).toBeVisible() await expect(page.locator('.sidebar')).not.toHaveClass(/is-open/) await page.reload() await expect(page.locator('html')).toHaveAttribute('data-theme', 'light') await expect(page.locator('.desktop-card')).toHaveCount(1) expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(391) })