95 lines
7.6 KiB
TypeScript
95 lines
7.6 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
||
|
||
async function fixture(page: Page, options: { role?: string; stale?: boolean; resolved?: boolean; fail?: boolean } = {}) {
|
||
const writes: { path: string; body: Record<string, unknown> }[] = [], now = new Date().toISOString()
|
||
let acknowledged = false
|
||
await page.addInitScript(() => sessionStorage.setItem('vdi.accessToken', 'terminal-alert-fixture'))
|
||
const rules: Record<string, unknown>[] = [{ id: 'temp-rule', name: '终端温度高水位', metric: 'terminal_temperature', threshold: 80, holdSeconds: 120, enabled: true }]
|
||
await page.route('**/api/v1/**', async route => {
|
||
const request = route.request(), path = new URL(request.url()).pathname.replace('/api/v1', '')
|
||
const list = (items: unknown[], extra = {}) => route.fulfill({ json: { items, total: items.length, page: 1, pageSize: 100, requestId: 'alert-ui', ...extra } })
|
||
if (request.method() !== 'GET') {
|
||
const body = request.postDataJSON(); writes.push({ path, body })
|
||
if (path.endsWith('/acknowledge')) { acknowledged = true; await route.fulfill({ json: { accepted: true } }); return }
|
||
if (path.startsWith('/monitor/rules')) {
|
||
if (options.fail) { await route.fulfill({ status: 400, json: { message: '温度阈值超出范围', code: 'invalid_request', requestId: 'alert-ui' } }); return }
|
||
const rule = { ...body, id: path.endsWith('/temp-rule') ? 'temp-rule' : 'created-rule' }
|
||
if (path.endsWith('/temp-rule')) rules[0] = rule; else rules.push(rule)
|
||
await route.fulfill({ json: rule }); return
|
||
}
|
||
await route.fulfill({ status: 403, json: { message: '不应出现的写操作' } }); return
|
||
}
|
||
if (path === '/auth/me') { await route.fulfill({ json: { id: 'alerts-reader', username: 'reader', displayName: '告警人员', role: options.role || 'super_admin' } }); return }
|
||
if (path === '/settings') { await route.fulfill({ json: { mode: 'production', capabilities: ['terminal-metric-alerts'], pveConfigured: false } }); 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 === '/monitor/rules') { await list(rules); return }
|
||
if (path === '/monitor/alerts') {
|
||
await list([{ id: 'temp-alert', ruleId: 'temp-rule', ruleName: '终端温度高水位', metric: 'terminal_temperature', targetId: 'box1', targetName: '测试盒子', value: 86.5, threshold: 80, status: options.resolved ? 'resolved' : 'firing', startedAt: now, openedAt: now, resolvedAt: options.resolved ? now : null, resolveReason: options.resolved ? 'condition_cleared' : '', acknowledgedAt: acknowledged ? now : null, acknowledgedBy: acknowledged ? 'reader' : '', dataFresh: !options.stale }], { firingCount: options.resolved ? 0 : 1, unacknowledgedCount: acknowledged || options.resolved ? 0 : 1 }); return
|
||
}
|
||
if (['/resources', '/desktops', '/users', '/terminals', '/tasks', '/audit-logs'].includes(path)) { await list([]); return }
|
||
await route.fulfill({ status: 404, json: { message: '测试接口未定义', requestId: 'alert-ui' } })
|
||
})
|
||
return writes
|
||
}
|
||
|
||
test('新增 SoC 温度规则使用摄氏度和独立上限,切换内存使用百分比', async ({ page }, info) => {
|
||
const writes = await fixture(page); await page.goto('/monitor')
|
||
await expect(page.getByRole('cell', { name: '86.5 / 80 °C', exact: true })).toBeVisible()
|
||
await page.getByRole('tab', { name: '告警规则' }).click()
|
||
await expect(page.getByRole('cell', { name: '80°C / 120 秒', exact: true })).toBeVisible()
|
||
await page.getByRole('button', { name: '新增规则', exact: true }).click()
|
||
const dialog = page.getByRole('dialog', { name: '新增告警规则' })
|
||
await dialog.locator('.el-select').click(); await page.getByRole('option', { name: '终端 SoC 温度', exact: true }).click()
|
||
await expect(dialog.getByText('SoC 温度阈值(°C)', { exact: true })).toBeVisible()
|
||
await expect(dialog.getByRole('spinbutton', { name: '告警阈值' })).toHaveAttribute('aria-valuemax', '150')
|
||
await expect(dialog.getByText(/重复读取不推进/)).toBeVisible()
|
||
await dialog.getByRole('textbox', { name: '规则名称' }).fill('盒子过热')
|
||
await page.screenshot({ path: info.outputPath('terminal-alert-create-dark.png'), fullPage: true, animations: 'disabled' })
|
||
await dialog.getByRole('button', { name: '保存规则', exact: true }).click()
|
||
await expect(dialog).toBeHidden()
|
||
expect(writes[0]?.body).toMatchObject({ name: '盒子过热', metric: 'terminal_temperature', threshold: 80, holdSeconds: 120, enabled: true })
|
||
await page.getByRole('button', { name: '新增规则', exact: true }).click()
|
||
await dialog.locator('.el-select').click(); await page.getByRole('option', { name: '终端内存', exact: true }).click()
|
||
await expect(dialog.getByText('使用率阈值(%)', { exact: true })).toBeVisible()
|
||
await expect(dialog.getByRole('spinbutton', { name: '告警阈值' })).toHaveAttribute('aria-valuemax', '100')
|
||
await dialog.getByRole('button', { name: '取消', exact: true }).click()
|
||
expect(writes).toHaveLength(1)
|
||
})
|
||
|
||
test('过期数据保留告警和最近值,确认不表示恢复', async ({ page }, info) => {
|
||
const writes = await fixture(page, { role: 'operator', stale: true }); await page.goto('/monitor')
|
||
await expect(page.getByText('数据已过期或缺失;显示最近告警值,等待有效采样', { exact: true })).toBeVisible()
|
||
await page.getByRole('button', { name: '确认告警' }).click()
|
||
await expect(page.getByRole('cell', { name: '正在告警', exact: true })).toBeVisible()
|
||
await expect(page.getByRole('button', { name: '确认告警' })).toHaveCount(0)
|
||
await page.getByRole('button', { name: '切换浅色主题' }).click()
|
||
await page.setViewportSize({ width: 390, height: 844 })
|
||
await page.screenshot({ path: info.outputPath('terminal-alert-stale-mobile-light.png'), fullPage: true, animations: 'disabled' })
|
||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(391)
|
||
await page.getByRole('tab', { name: '告警规则' }).click()
|
||
await expect(page.getByRole('button', { name: '新增规则' })).toHaveCount(0)
|
||
expect(writes).toHaveLength(1)
|
||
})
|
||
|
||
test('只读人员可看恢复事件和摄氏度,无确认或规则写入口', async ({ page }) => {
|
||
const writes = await fixture(page, { role: 'viewer', stale: true, resolved: true }); await page.goto('/monitor')
|
||
await expect(page.getByRole('cell', { name: '已恢复', exact: true })).toBeVisible()
|
||
await expect(page.getByText('指标恢复', { exact: true })).toBeVisible()
|
||
await expect(page.getByText(/数据已过期或缺失/)).toHaveCount(0)
|
||
await expect(page.getByRole('button', { name: '确认告警' })).toHaveCount(0)
|
||
await page.getByRole('tab', { name: '告警规则' }).click()
|
||
await expect(page.getByRole('button', { name: '编辑规则' })).toHaveCount(0)
|
||
expect(writes).toEqual([])
|
||
})
|
||
|
||
test('保存错误保留表单和数值,取消不改变规则', async ({ page }) => {
|
||
const writes = await fixture(page, { fail: true }); await page.goto('/monitor')
|
||
await page.getByRole('tab', { name: '告警规则' }).click(); await page.getByRole('button', { name: '编辑规则' }).click()
|
||
const dialog = page.getByRole('dialog', { name: '编辑告警规则' })
|
||
await dialog.getByRole('button', { name: '保存规则', exact: true }).click()
|
||
await expect(page.getByText(/温度阈值超出范围/)).toBeVisible()
|
||
await expect(dialog).toBeVisible()
|
||
await dialog.getByRole('button', { name: '取消', exact: true }).click(); await expect(dialog).toBeHidden()
|
||
expect(writes).toHaveLength(1)
|
||
})
|