功能:新增云管API与Web控制台并完成现场接入
This commit is contained in:
1 parent
18a9ce995d
commit
3fb7b4da8c
79 files changed
+17367
-3
No files matched your search
@@ -0,0 +1,102 @@
|
||||
import { test, expect, type Page } from '@playwright/test'
|
||||
async function login(page: Page, username = 'admin') {
|
||||
await page.goto('/login')
|
||||
await page.getByRole('textbox', { name: '用户名', exact: true }).fill(username)
|
||||
await page.getByRole('textbox', { name: '密码', exact: true }).fill('Demo@2026')
|
||||
await page.getByRole('button', { name: '登录', exact: true }).click()
|
||||
await expect(page.getByRole('heading', { name: '运行总览' })).toBeVisible()
|
||||
}
|
||||
test.beforeAll(async ({ request }) => { const r = await request.get('/healthz'); expect(r.ok()).toBe(true); expect((await r.json()).mode).toBe('demo') })
|
||||
test('登录失败、登录恢复与退出', async ({ page }) => {
|
||||
await page.goto('/login')
|
||||
await page.getByRole('textbox', { name: '用户名', exact: true }).fill('admin')
|
||||
await page.getByRole('textbox', { name: '密码', exact: true }).fill('incorrect')
|
||||
await page.getByRole('button', { name: '登录', exact: true }).click()
|
||||
await expect(page.getByText('用户名或密码错误')).toBeVisible()
|
||||
await page.getByRole('textbox', { name: '密码', exact: true }).fill('Demo@2026')
|
||||
await page.getByRole('button', { name: '登录', exact: true }).click()
|
||||
await expect(page.getByRole('heading', { name: '运行总览' })).toBeVisible()
|
||||
await page.reload()
|
||||
await expect(page.getByRole('heading', { name: '运行总览' })).toBeVisible()
|
||||
await page.getByRole('button', { name: '退出登录' }).click()
|
||||
await expect(page).toHaveURL(/\/login/)
|
||||
})
|
||||
test('桌面命令与最终任务结果', async ({ page }) => {
|
||||
await login(page)
|
||||
await page.getByRole('navigation').getByRole('link', { name: '桌面管理' }).click()
|
||||
await page.getByRole('button', { name: '列表视图' }).click()
|
||||
const row = page.getByRole('row').filter({ hasText: '国产办公桌面' })
|
||||
await expect(row).toBeVisible()
|
||||
const currentStopped = await row.getByText('已关机', { exact: true }).count()
|
||||
const action = currentStopped ? '开机' : '关机'
|
||||
await row.getByRole('button', { name: action, exact: true }).click()
|
||||
await page.getByRole('button', { name: '确认提交', exact: true }).click()
|
||||
await expect(page.getByText('任务已提交,请在任务与审计中查看最终结果')).toBeVisible()
|
||||
await page.getByRole('navigation').getByRole('link', { name: '任务与审计' }).click()
|
||||
const task = page.getByRole('row').filter({ hasText: '国产办公桌面' }).first()
|
||||
await expect(task.getByText('成功', { exact: true })).toBeVisible()
|
||||
await expect(task).toContainText('演示计算任务已完成')
|
||||
})
|
||||
test('创建用户、编辑多个桌面授权并持久化', async ({ page }) => {
|
||||
await login(page)
|
||||
await page.getByRole('navigation').getByRole('link', { name: '用户与授权' }).click()
|
||||
await page.getByRole('button', { name: '新建用户', exact: true }).click()
|
||||
const dialog = page.getByRole('dialog')
|
||||
const name = `测试用户 ${Date.now()}`
|
||||
await dialog.getByRole('textbox', { name: /姓名/ }).fill(name)
|
||||
await dialog.getByRole('textbox', { name: /用户账号/ }).fill(`e2e.${Date.now()}`)
|
||||
await dialog.getByRole('button', { name: '创建用户', exact: true }).click()
|
||||
await page.getByRole('textbox', { name: '搜索用户' }).fill(name)
|
||||
await page.getByRole('row').filter({ hasText: name }).getByRole('button', { name: '编辑授权' }).click()
|
||||
await dialog.locator('label.el-checkbox').filter({ hasText: '设计专属桌面' }).click()
|
||||
await expect(dialog.getByRole('checkbox', { name: /设计专属桌面/ })).toBeChecked()
|
||||
await dialog.locator('label.el-checkbox').filter({ hasText: '研发办公桌面' }).click()
|
||||
await dialog.getByRole('button', { name: '保存授权' }).click()
|
||||
const row = page.getByRole('row').filter({ hasText: name })
|
||||
await expect(row).toContainText('设计专属桌面')
|
||||
await expect(row).toContainText('研发办公桌面')
|
||||
await page.reload()
|
||||
await page.getByRole('textbox', { name: '搜索用户' }).fill(name)
|
||||
await expect(page.getByRole('row').filter({ hasText: name })).toContainText('设计专属桌面')
|
||||
})
|
||||
test('登记终端只显示一次激活密钥', async ({ page }) => {
|
||||
await login(page)
|
||||
await page.getByRole('navigation').getByRole('link', { name: '终端管理' }).click()
|
||||
await page.getByRole('button', { name: '登记终端' }).click()
|
||||
const dialog = page.getByRole('dialog')
|
||||
const serial = `E2E-${Date.now()}`
|
||||
await dialog.getByRole('textbox', { name: '设备 SN' }).fill(serial)
|
||||
await dialog.getByRole('textbox', { name: '终端名称' }).fill('联调终端')
|
||||
await dialog.getByRole('button', { name: '登记并生成激活信息' }).click()
|
||||
await expect(page.getByTestId('activation-secret')).toContainText('activate_')
|
||||
await page.getByRole('button', { name: '已保存,关闭' }).click()
|
||||
await page.getByRole('textbox', { name: '搜索终端' }).fill(serial)
|
||||
await expect(page.getByRole('row').filter({ hasText: serial })).toContainText('待激活')
|
||||
})
|
||||
for (const username of ['viewer', 'operator']) test(`${username} 权限边界`, async ({ page }) => {
|
||||
await login(page, username)
|
||||
await page.getByRole('navigation').getByRole('link', { name: '用户与授权' }).click()
|
||||
await expect(page.getByRole('button', { name: '新建用户' })).toHaveCount(0)
|
||||
await expect(page.getByRole('button', { name: '编辑授权' })).toHaveCount(0)
|
||||
await page.goto('/settings')
|
||||
await expect(page.getByText('无访问权限', { exact: true })).toBeVisible()
|
||||
await page.goto('/desktops')
|
||||
await expect(page.getByRole('heading', { name: '桌面管理' })).toBeVisible()
|
||||
await page.getByRole('button', { name: '列表视图' }).click()
|
||||
if (username === 'viewer') await expect(page.getByRole('button', { name: '关机', exact: true })).toHaveCount(0)
|
||||
else await expect(page.getByRole('button', { name: '关机', exact: true }).first()).toBeVisible()
|
||||
})
|
||||
test('空列表、刷新异常及 401 处理', async ({ page }) => {
|
||||
await login(page)
|
||||
await page.getByRole('navigation').getByRole('link', { name: '桌面管理' }).click()
|
||||
await page.getByRole('textbox', { name: '搜索桌面' }).fill('不存在的桌面')
|
||||
await expect(page.getByText('暂无符合条件的桌面')).toBeVisible()
|
||||
await page.route('**/api/v1/overview', route => route.fulfill({ status: 503, json: { code: 'unavailable', message: '测试服务暂不可用', requestId: 'test-offline' } }))
|
||||
await page.getByRole('navigation').getByRole('link', { name: '云桌面总览', exact: true }).click()
|
||||
await page.getByRole('button', { name: '刷新数据' }).click()
|
||||
await expect(page.getByText('数据刷新失败,当前信息可能已过期')).toBeVisible()
|
||||
await page.unroute('**/api/v1/overview')
|
||||
await page.evaluate(() => sessionStorage.setItem('vdi.accessToken', 'invalid-token'))
|
||||
await page.reload()
|
||||
await expect(page).toHaveURL(/\/login/)
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
import {test,expect} from '@playwright/test'
|
||||
import {readFileSync,mkdirSync} from 'node:fs'
|
||||
const env=Object.fromEntries(readFileSync('../.local/live/.env','utf8').trim().split(/\r?\n/).map(line=>{ const i=line.indexOf('='); return [line.slice(0,i),line.slice(i+1)] }))
|
||||
function required(key: string) { const value=env[key]; if (!value) throw new Error(`Missing live configuration: ${key}`); return value }
|
||||
test('现场真实资源、设备在线、日志采集与下载',async({page,request})=>{
|
||||
const health=await request.get('/healthz'); expect((await health.json()).mode).toBe('production')
|
||||
await page.goto('/login')
|
||||
await expect(page.getByText(/演示账号/)).toHaveCount(0)
|
||||
await page.getByRole('textbox',{name:'用户名',exact:true}).fill(required('VDI_ADMIN_USERNAME'))
|
||||
await page.getByRole('textbox',{name:'密码',exact:true}).fill(required('VDI_ADMIN_PASSWORD'))
|
||||
await page.getByRole('button',{name:'登录',exact:true}).click()
|
||||
await expect(page.getByRole('heading',{name:'运行总览'})).toBeVisible()
|
||||
await expect(page.getByText('starhe',{exact:true})).toBeVisible()
|
||||
mkdirSync('../.local/live/screenshots',{recursive:true})
|
||||
await page.screenshot({path:'../.local/live/screenshots/overview.png',fullPage:true,animations:'disabled'})
|
||||
await page.getByRole('navigation').getByRole('link',{name:'桌面管理'}).click()
|
||||
await expect(page.getByRole('heading',{name:'桌面管理',exact:true})).toBeVisible()
|
||||
await page.getByRole('button',{name:'列表视图'}).click()
|
||||
for(const name of ['Windows 10','Ubuntu','麒麟']) await expect(page.getByRole('row').filter({hasText:name})).toBeVisible()
|
||||
await page.screenshot({path:'../.local/live/screenshots/desktops.png',fullPage:true,animations:'disabled'})
|
||||
await page.getByRole('navigation').getByRole('link',{name:'终端管理'}).click()
|
||||
const row=page.getByRole('row').filter({hasText:'ee621cae7199a2e9'})
|
||||
await expect(row.getByText('在线',{exact:true})).toBeVisible()
|
||||
await row.getByRole('button',{name:'详情',exact:true}).click()
|
||||
await expect(page.getByText('qt-platform/0.2.0',{exact:true})).toBeVisible()
|
||||
await page.getByRole('button',{name:'收集日志',exact:true}).click()
|
||||
await page.getByRole('button',{name:'确认提交',exact:true}).click()
|
||||
await page.getByRole('dialog',{name:'终端详情'}).locator('button.el-drawer__close-btn').click()
|
||||
await page.getByRole('navigation').getByRole('link',{name:'任务与审计'}).click()
|
||||
const task=page.getByRole('row').filter({hasText:'收集日志'}).first()
|
||||
await expect(task.getByText('成功',{exact:true})).toBeVisible()
|
||||
await task.getByRole('button').click()
|
||||
const download=page.waitForEvent('download')
|
||||
await page.getByRole('button',{name:'下载诊断日志'}).click()
|
||||
const artifact=await download
|
||||
const path='../.local/live/device-diagnostics.json'; await artifact.saveAs(path)
|
||||
const diagnostic=JSON.parse(readFileSync(path,'utf8'))
|
||||
expect(diagnostic.agentVersion).toBe('qt-platform/0.2.0'); expect(diagnostic.bootId).toBeTruthy(); expect(diagnostic.uptimeSeconds).toBeGreaterThan(0)
|
||||
await page.screenshot({path:'../.local/live/screenshots/diagnostics.png',fullPage:true,animations:'disabled'})
|
||||
})
|
||||
@@ -0,0 +1,84 @@
|
||||
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)
|
||||
})
|
||||
Reference in new issue
Block a user