vdi-cloud/console/e2e/terminal-alerts.spec.ts

95 lines
7.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
})