99 lines
7.8 KiB
TypeScript
99 lines
7.8 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
||
|
||
async function fixture(page: Page, options: { stale?: boolean; legacy?: boolean; missing?: boolean; fail?: boolean; delayFirst?: boolean } = {}) {
|
||
const now = new Date().toISOString(), writes: string[] = [], reads: URL[] = []
|
||
let fail = options.fail, firstDelivered = false
|
||
await page.addInitScript(() => sessionStorage.setItem('vdi.accessToken', 'metrics-fixture'))
|
||
await page.route('**/api/v1/**', async route => {
|
||
const request = route.request(), url = new URL(request.url()), path = url.pathname.replace('/api/v1', '')
|
||
if (request.method() !== 'GET') { writes.push(path); await route.fulfill({ status: 403, json: { message: '只读页面出现写请求' } }); return }
|
||
const list = (items: unknown[]) => route.fulfill({ json: { items, total: items.length, page: 1, pageSize: 100, requestId: 'metrics-fixture' } })
|
||
if (path === '/auth/me') { await route.fulfill({ json: { id: 'metrics-viewer', username: 'viewer', displayName: '只读人员', role: 'viewer' } }); return }
|
||
if (path === '/settings') { await route.fulfill({ json: { mode: 'production', capabilities: [], pveConfigured: true, computeProvider: 'proxmox' } }); return }
|
||
if (path === '/overview') { await route.fulfill({ json: { terminalCount: 1, onlineTerminals: 1, desktopCount: 0, runningDesktops: 0, activeSessions: 0, failedTasks: 0, mode: 'production' } }); return }
|
||
if (path === '/terminals') { await list([{ id: 'metrics-a', name: '指标测试盒子', sn: 'METRICS-001', group: '测试', status: options.stale ? 'offline' : 'online', agentVersion: 'qt-platform/0.4.0', lastSeen: now, sessionDesktopId: null, configVersion: 1, fps: null, rtt: null }]); return }
|
||
if (path === '/terminals/metrics-a/metrics') {
|
||
reads.push(url)
|
||
const requestNumber = reads.length
|
||
if (options.delayFirst && reads.length === 1) await new Promise(resolve => setTimeout(resolve, 2000))
|
||
if (fail) { fail = false; await route.fulfill({ status: 503, json: { code: 'unavailable', message: '指标服务暂不可用', requestId: 'metrics-fixture' } }); return }
|
||
const from = url.searchParams.get('from')!, to = url.searchParams.get('to')!, start = Date.parse(from)
|
||
await route.fulfill({ json: { latest: { cpuPercent: options.missing ? null : options.delayFirst && requestNumber > 1 ? 25 : 0, memoryPercent: 30, socTemperatureC: 44.37, uptimeSeconds: 90120, sampledAt: now, supported: !options.legacy, fresh: !options.stale && !options.legacy, online: !options.stale }, from, to, bucketSeconds: 60, retentionDays: 30, items: [
|
||
{ time: new Date(start + 60000).toISOString(), cpuPercent: 10, memoryPercent: 30, socTemperatureC: 44, uptimeSeconds: 90100, samples: 2 },
|
||
{ time: new Date(start + 120000).toISOString(), cpuPercent: null, memoryPercent: null, socTemperatureC: null, uptimeSeconds: null, samples: 0 },
|
||
{ time: new Date(start + 180000).toISOString(), cpuPercent: 20, memoryPercent: 30, socTemperatureC: 44.37, uptimeSeconds: 90120, samples: 2 },
|
||
] } }); if (requestNumber === 1) firstDelivered = true; return
|
||
}
|
||
if (['/desktops', '/users', '/tasks', '/audit-logs', '/resources'].includes(path)) { await list([]); return }
|
||
await route.fulfill({ status: 404, json: { message: '测试接口未定义' } })
|
||
})
|
||
return { writes, reads, firstDelivered: () => firstDelivered }
|
||
}
|
||
async function open(page: Page) {
|
||
await page.goto('/terminals')
|
||
await page.getByRole('button', { name: '详情', exact: true }).click()
|
||
await page.getByRole('button', { name: '查看运行指标', exact: true }).click()
|
||
return page.getByRole('region', { name: '终端运行指标' })
|
||
}
|
||
test('真实指标独立按需加载,零值、SoC 温度、运行时长与缺失断线显示', async ({ page }, info) => {
|
||
const requests = await fixture(page)
|
||
await page.goto('/terminals'); await page.getByRole('button', { name: '详情', exact: true }).click()
|
||
expect(requests.reads).toHaveLength(0)
|
||
await page.getByRole('button', { name: '查看运行指标', exact: true }).click()
|
||
const panel = page.getByRole('region', { name: '终端运行指标' })
|
||
await expect(panel.getByText('0.0 %', { exact: true })).toBeVisible()
|
||
await expect(panel.getByText('44.4 °C', { exact: true })).toBeVisible()
|
||
await expect(panel.getByText('1 天 1 小时 2 分钟', { exact: true })).toBeVisible()
|
||
await expect(panel.getByRole('img', { name: /历史趋势/ })).toHaveCount(3)
|
||
const path = await panel.getByRole('img', { name: 'CPU 使用率历史趋势' }).locator('path').getAttribute('d')
|
||
expect(path?.match(/M/g)).toHaveLength(2); expect(path).not.toContain('L')
|
||
await expect(panel.getByText(/串流 FPS\/RTT 尚未采集/)).toBeVisible()
|
||
await page.screenshot({ path: info.outputPath('terminal-metrics-dark.png'), fullPage: true })
|
||
expect(requests.writes).toEqual([])
|
||
})
|
||
test('离线隐藏当前数值并保留历史', async ({ page }) => {
|
||
const requests = await fixture(page, { stale: true }); const panel = await open(page)
|
||
await expect(panel.getByText(/设备离线或采样已过期/)).toBeVisible()
|
||
await expect(panel.locator('.metric-values strong')).toHaveText(['未采集', '未采集', '未采集', '未采集'])
|
||
await expect(panel.getByRole('img', { name: /历史趋势/ })).toHaveCount(3); expect(requests.writes).toEqual([])
|
||
})
|
||
test('旧 Agent 给出升级提示,不冒充零值', async ({ page }) => {
|
||
await fixture(page, { legacy: true }); const panel = await open(page)
|
||
await expect(panel.getByText('设备尚未上报系统指标,请更新 Agent', { exact: true })).toBeVisible()
|
||
await expect(panel.locator('.metric-values strong')).toHaveText(['未采集', '未采集', '未采集', '未采集'])
|
||
})
|
||
test('单项缺失保持未采集,读取失败可重试', async ({ page }) => {
|
||
await fixture(page, { missing: true, fail: true }); const panel = await open(page)
|
||
await expect(panel.getByText('指标服务暂不可用', { exact: true })).toBeVisible()
|
||
await expect(panel.locator('.metric-values')).toHaveCount(0)
|
||
await panel.getByRole('button', { name: '刷新指标' }).click()
|
||
await expect(panel.locator('.metric-values strong').first()).toHaveText('未采集')
|
||
await expect(panel.getByText('30.0 %', { exact: true })).toBeVisible()
|
||
})
|
||
test('时间范围切换与窄屏浅色展示', async ({ page }, info) => {
|
||
const requests = await fixture(page)
|
||
await page.addInitScript(() => localStorage.setItem('vdi.theme', 'light'))
|
||
await page.setViewportSize({ width: 390, height: 844 })
|
||
const panel = await open(page)
|
||
await expect(panel.getByText('44.4 °C', { exact: true })).toBeVisible()
|
||
await panel.locator('.el-select').click()
|
||
await page.getByRole('option', { name: '最近 7 天' }).click()
|
||
await expect.poll(() => requests.reads.length).toBe(2)
|
||
const url = requests.reads[1]!
|
||
expect(Date.parse(url.searchParams.get('to')!) - Date.parse(url.searchParams.get('from')!)).toBe(7 * 86400000)
|
||
await expect(panel.getByText('44.4 °C', { exact: true })).toBeVisible()
|
||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(391)
|
||
await page.screenshot({ path: info.outputPath('terminal-metrics-mobile-light.png'), fullPage: true })
|
||
expect(requests.writes).toEqual([])
|
||
})
|
||
test('切换时间范围时旧响应不能覆盖新响应', async ({ page }) => {
|
||
const requests = await fixture(page, { delayFirst: true }); const panel = await open(page)
|
||
await expect.poll(() => requests.reads.length).toBe(1)
|
||
await panel.locator('.el-select').click(); await page.getByRole('option', { name: '最近 24 小时' }).click()
|
||
await expect.poll(() => requests.reads.length).toBe(2)
|
||
await expect(panel.getByText('44.4 °C', { exact: true })).toBeVisible()
|
||
await expect.poll(requests.firstDelivered).toBe(true)
|
||
await expect(panel.getByText('25.0 %', { exact: true })).toBeVisible()
|
||
await expect(panel.locator('.el-select')).toHaveText('最近 24 小时')
|
||
})
|