vdi-cloud/console/e2e/ui.spec.ts

85 lines
6.3 KiB
TypeScript

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<string, unknown[]> = {
'/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)
})