107 lines
8.9 KiB
TypeScript
107 lines
8.9 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
|
|
|
// All calls are intercepted: these tests cannot create a real VM or change live assignments.
|
|
async function catalogFixture(page: Page, options: { role?: string; empty?: boolean; fail?: boolean } = {}) {
|
|
const now = new Date().toISOString(), requests: { endpoint: string; body: Record<string, unknown>; key?: string }[] = []
|
|
const templates = options.empty ? [] : [{ id: 'tpl-ui', name: 'Ubuntu 标准镜像', version: '1.0', os: 'Ubuntu', node: 'node-a', vmid: 8000, enabled: true, validatedAt: now }]
|
|
const pools = [{ id: 'pool-ui', name: '研发专属池', type: 'dedicated', maxCount: 5, templateId: 'tpl-ui', enabled: true, createdAt: now, desktopCount: 0, reservedCount: 0, assignedCount: 0, activeCount: 0 }]
|
|
const desktop = { id: 'desk-ui', name: '待初始化桌面', os: 'Ubuntu', node: 'node-a', vmid: 9000, pool: '', poolId: null, setupRequired: true, powerState: 'stopped', serviceState: 'unknown', provider: 'proxmox', endpoint: '', app: 'Desktop', width: 1920, height: 1080, fps: 30, updatedAt: now }
|
|
await page.addInitScript(() => sessionStorage.setItem('vdi.accessToken', 'catalog-fixture'))
|
|
await page.route('**/api/v1/**', async route => {
|
|
const r = route.request(), endpoint = new URL(r.url()).pathname.replace('/api/v1', '')
|
|
const list = (items: unknown[]) => route.fulfill({ json: { items, page: 1, pageSize: 100, total: items.length, requestId: 'catalog-ui' } })
|
|
if (r.method() !== 'GET') {
|
|
const body = r.postData() ? r.postDataJSON() as Record<string, unknown> : {}
|
|
requests.push({ endpoint, body, key: r.headers()['idempotency-key'] })
|
|
if (endpoint === '/templates') { const item = { ...templates[0], ...body, id: 'tpl-new', enabled: true, validatedAt: now } as typeof templates[number]; templates.push(item); await route.fulfill({ status: 201, json: item }); return }
|
|
if (endpoint === '/pools') { const item = { ...pools[0], ...body, id: 'pool-new' } as typeof pools[number]; pools.push(item); await route.fulfill({ json: item }); return }
|
|
if (endpoint === '/desktops/clone/check') { await route.fulfill({ json: { available: true, message: '预检通过;创建后需初始化' } }); return }
|
|
if (endpoint === '/desktops/clone') { await route.fulfill({ status: 202, json: { id: 'task-ui-clone', kind: 'provision', action: 'clone', targetId: desktop.id, targetName: '测试克隆桌面', actor: 'root', status: 'queued', message: '等待 PVE', providerRef: '', createdAt: now, updatedAt: now } }); return }
|
|
if (endpoint === '/desktops/desk-ui/connection') { Object.assign(desktop, body, { setupRequired: false }); await route.fulfill({ json: desktop }); return }
|
|
if (endpoint.endsWith('/members')) { await route.fulfill({ json: { accepted: true } }); return }
|
|
}
|
|
if (endpoint === '/auth/me') { await route.fulfill({ json: { id: 'admin-ui', username: 'root', displayName: '测试管理员', role: options.role || 'super_admin' } }); return }
|
|
if (endpoint === '/overview') { await route.fulfill({ json: { terminalCount: 0, onlineTerminals: 0, desktopCount: 1, runningDesktops: 0, activeSessions: 0, failedTasks: 0, mode: 'production' } }); return }
|
|
if (endpoint === '/settings') { await route.fulfill({ json: { mode: 'production', computeProvider: 'proxmox', pveConfigured: true, terminalTransport: 'https-poll', capabilities: [] } }); return }
|
|
if (endpoint === '/templates') { if (options.fail) { await route.fulfill({ status: 503, json: { code: 'unavailable', message: '模板读取失败,请重试', requestId: 'catalog-ui' } }); return }; await list(templates); return }
|
|
if (endpoint === '/pools') { await list(pools); return }
|
|
if (endpoint === '/resources/templates') { await list(options.empty ? [] : [{ name: 'Ubuntu 新模板', node: 'node-a', vmid: 8001, status: 'stopped' }]); return }
|
|
if (endpoint === '/resources/storage') { await list([{ storage: 'local', avail: 100 * 1073741824, enabled: 1, active: 1, content: 'images' }]); return }
|
|
if (endpoint === '/desktops') { await list([desktop]); return }
|
|
if (['/terminals', '/users', '/resources', '/tasks', '/audit-logs'].includes(endpoint)) { await list([]); return }
|
|
await route.fulfill({ status: 404, json: { code: 'fixture_missing', message: '测试接口未定义', requestId: 'catalog-ui' } })
|
|
})
|
|
return requests
|
|
}
|
|
|
|
test('模板克隆向导先预检,再提交带去重标识的任务', async ({ page }, testInfo) => {
|
|
const requests = await catalogFixture(page)
|
|
await page.setViewportSize({ width: 1440, height: 1000 })
|
|
await page.goto('/templates')
|
|
await expect(page.getByText('Ubuntu 标准镜像', { exact: true })).toBeVisible()
|
|
await page.screenshot({ path: testInfo.outputPath('templates-dark.png'), fullPage: true })
|
|
await page.getByRole('button', { name: '创建桌面', exact: true }).click()
|
|
const dialog = page.getByRole('dialog', { name: '从模板创建桌面' })
|
|
await expect(dialog.getByRole('button', { name: '提交创建任务' })).toBeDisabled()
|
|
await dialog.getByPlaceholder('例如:研发桌面 01').fill('测试克隆桌面')
|
|
await dialog.locator('.el-select__wrapper').last().click()
|
|
await page.getByRole('option', { name: /local · 可用/ }).click()
|
|
await dialog.getByRole('button', { name: '检查创建条件' }).click()
|
|
await expect(dialog.getByText('预检通过;创建后需初始化')).toBeVisible()
|
|
await dialog.getByRole('button', { name: '提交创建任务' }).click()
|
|
await expect(dialog.getByText('创建任务已提交', { exact: true })).toBeVisible()
|
|
expect(requests.filter(r => r.endpoint === '/desktops/clone')).toHaveLength(1)
|
|
expect(requests.find(r => r.endpoint === '/desktops/clone')?.body).toMatchObject({ templateId: 'tpl-ui', name: '测试克隆桌面', vmid: 9000, full: true, storage: 'local' })
|
|
expect(requests.find(r => r.endpoint === '/desktops/clone')?.key).toBeTruthy()
|
|
})
|
|
|
|
test('桌面池创建保存业务字段,窄屏与浅色主题可用', async ({ page }, testInfo) => {
|
|
const requests = await catalogFixture(page)
|
|
await page.goto('/pools')
|
|
await expect(page.getByText('0 台桌面 + 0 台创建预留 / 5')).toBeVisible()
|
|
await page.getByRole('button', { name: '新建桌面池' }).click()
|
|
const dialog = page.getByRole('dialog', { name: '新建桌面池' })
|
|
await dialog.getByRole('textbox').fill('财务专属池')
|
|
await dialog.getByRole('button', { name: '保存桌面池' }).click()
|
|
await expect(page.getByText('财务专属池', { exact: true })).toBeVisible()
|
|
expect(requests.find(r => r.endpoint === '/pools')?.body).toEqual({ name: '财务专属池', type: 'dedicated', maxCount: 10, templateId: null, enabled: true })
|
|
await page.getByRole('button', { name: '切换浅色主题' }).click()
|
|
await page.screenshot({ path: testInfo.outputPath('pools-light.png'), fullPage: true })
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await page.reload()
|
|
await expect(page.getByRole('heading', { name: '桌面池', exact: true })).toBeVisible()
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(391)
|
|
await page.screenshot({ path: testInfo.outputPath('pools-mobile.png'), fullPage: true })
|
|
})
|
|
|
|
test('待初始化桌面必须勾选确认才能保存连接', async ({ page }) => {
|
|
const requests = await catalogFixture(page)
|
|
await page.goto('/desktops')
|
|
await expect(page.getByText('待初始化', { exact: true }).first()).toBeVisible()
|
|
await page.getByRole('button', { name: '查看详情' }).click()
|
|
await page.getByRole('button', { name: '配置连接', exact: true }).click()
|
|
const dialog = page.getByRole('dialog', { name: '配置桌面连接' })
|
|
await dialog.getByPlaceholder('例如 192.168.1.200,不含协议和端口').fill('192.0.2.20')
|
|
await expect(dialog.getByRole('button', { name: '保存连接配置' })).toBeDisabled()
|
|
await dialog.getByText('已完成系统身份、IP、Sunshine 服务和终端配对', { exact: true }).click()
|
|
await dialog.getByRole('button', { name: '保存连接配置' }).click()
|
|
await expect(dialog).not.toBeVisible()
|
|
expect(requests.find(r => r.endpoint.endsWith('/connection'))?.body).toMatchObject({ endpoint: '192.0.2.20', initialized: true, width: 1920, height: 1080, fps: 30 })
|
|
})
|
|
|
|
test('空模板和接口错误显示明确原因,只读角色没有管理按钮', async ({ page }) => {
|
|
await catalogFixture(page, { role: 'viewer', empty: true })
|
|
await page.goto('/templates')
|
|
await expect(page.getByText(/尚未登记模板。请先在 PVE/)).toBeVisible()
|
|
await expect(page.getByRole('button', { name: '登记模板' })).toHaveCount(0)
|
|
await page.goto('/pools')
|
|
await expect(page.getByRole('button', { name: '新建桌面池' })).toHaveCount(0)
|
|
await expect(page.getByRole('button', { name: '成员', exact: true })).toHaveCount(0)
|
|
})
|
|
|
|
test('模板读取失败提供错误反馈', async ({ page }) => {
|
|
await catalogFixture(page, { fail: true })
|
|
await page.goto('/templates')
|
|
await expect(page.getByText(/模板读取失败,请重试/)).toBeVisible()
|
|
})
|