Files
awoooi/apps/web/tests/e2e/dashboard-acceptance.spec.ts
OG T 196d269b92 feat: add all application source code
- apps/api: FastAPI backend with Dockerfile
- apps/web: Next.js frontend with Dockerfile
- apps/sensor: Signal collection agent
- packages: shared packages

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-22 18:57:44 +08:00

184 lines
6.3 KiB
TypeScript

import { test, expect } from '@playwright/test'
/**
* Dashboard 視覺驗收測試
* ======================
* Phase VI E2E 自動化測試
*
* 注意: 使用 domcontentloaded 而非 networkidle
* 因 SSE 連線會持續保持網路活動
*/
test.describe('Dashboard 視覺驗收', () => {
// 增加超時時間
test.setTimeout(60000)
test('繁體中文頁面驗證 - 無 MOCK MODE', async ({ page }) => {
// 1. 導覽至 /zh-TW/demo
await page.goto('/zh-TW/demo', { waitUntil: 'domcontentloaded' })
// 等待主要內容渲染
await page.waitForSelector('h1', { timeout: 15000 })
await page.waitForTimeout(2000) // 等待 hydration
// 截圖: 初始頁面
await page.screenshot({
path: 'test-results/screenshots/01-zh-TW-initial.png',
fullPage: true,
})
// 2. 驗證畫面不存在 MOCK MODE 字樣
const pageContent = await page.content()
expect(pageContent).not.toContain('MOCK MODE')
// 3. 驗證標題正確渲染為繁體中文「全局戰情室」
const dashboardTitle = page.locator('h2').filter({ hasText: '全局戰情室' })
await expect(dashboardTitle).toBeVisible({ timeout: 10000 })
// 截圖: 全局戰情室標題
await page.screenshot({
path: 'test-results/screenshots/02-zh-TW-dashboard-title.png',
})
// 驗證 Demo 頁面標題包含 AWOOOI
const demoTitle = page.locator('h1')
await expect(demoTitle).toContainText('AWOOOI')
// 驗證視覺驗收測試副標題 (繁體中文)
const subtitle = page.locator('text=視覺驗收測試')
await expect(subtitle).toBeVisible()
// 驗證 LIVE 指示器 (非 MOCK MODE)
const liveIndicator = page.locator('text=LIVE')
await expect(liveIndicator).toBeVisible()
})
test('語系切換器功能 - 繁中轉英文', async ({ page }) => {
// 導覽至繁體中文頁面
await page.goto('/zh-TW/demo', { waitUntil: 'domcontentloaded' })
await page.waitForSelector('h2', { timeout: 15000 })
await page.waitForTimeout(2000)
// 驗證初始為繁體中文
const zhTitle = page.locator('h2').filter({ hasText: '全局戰情室' })
await expect(zhTitle).toBeVisible({ timeout: 10000 })
// 截圖: 切換前
await page.screenshot({
path: 'test-results/screenshots/03-before-locale-switch.png',
})
// 4. 點擊語系切換器切換至 EN
const enButton = page.locator('button').filter({ hasText: 'English' })
await expect(enButton).toBeVisible()
await enButton.click()
// 等待頁面導航
await page.waitForURL('**/en/demo', { timeout: 15000 })
await page.waitForSelector('h2', { timeout: 15000 })
await page.waitForTimeout(2000)
// 驗證標題變更為 "Command Center"
const enTitle = page.locator('h2').filter({ hasText: 'Command Center' })
await expect(enTitle).toBeVisible({ timeout: 10000 })
// 驗證副標題變更為英文
const enSubtitle = page.locator('text=Visual Acceptance Test')
await expect(enSubtitle).toBeVisible()
// 截圖: 切換後 (英文)
await page.screenshot({
path: 'test-results/screenshots/04-after-locale-switch-en.png',
fullPage: true,
})
// 驗證 LIVE 指示器存在 (非 MOCK MODE)
const liveIndicator = page.locator('span:has-text("LIVE")')
await expect(liveIndicator).toBeVisible()
})
test('主機卡片顯示真實狀態', async ({ page }) => {
await page.goto('/zh-TW/demo', { waitUntil: 'domcontentloaded' })
await page.waitForSelector('h2', { timeout: 15000 })
// 等待 SSE 數據載入
await page.waitForTimeout(3000)
// 截圖: 主機卡片區域
await page.screenshot({
path: 'test-results/screenshots/05-host-cards.png',
fullPage: true,
})
// 驗證至少有一個 IP 地址顯示 (真實主機卡片)
const ipPattern = page.locator('text=/192\\.168\\.0\\.\\d+/')
const ipCount = await ipPattern.count()
expect(ipCount).toBeGreaterThanOrEqual(1)
// 驗證 LIVE 狀態指示器存在 (非 MOCK MODE)
const liveIndicator = page.locator('text=LIVE')
await expect(liveIndicator).toBeVisible()
})
test('HITL 授權卡片功能驗證', async ({ page }) => {
await page.goto('/zh-TW/demo', { waitUntil: 'domcontentloaded' })
await page.waitForSelector('h2', { timeout: 15000 })
await page.waitForTimeout(2000)
// 滾動到授權卡片區域
await page.evaluate(() => window.scrollBy(0, 800))
await page.waitForTimeout(1000)
// 截圖: 授權卡片區域
await page.screenshot({
path: 'test-results/screenshots/06-approval-cards.png',
fullPage: true,
})
// 驗證授權卡片標題存在
const approvalTitle = page.locator('h2').filter({ hasText: 'HITL' })
await expect(approvalTitle).toBeVisible({ timeout: 10000 })
// 驗證「長按」相關按鈕存在 (繁體中文)
const holdButtons = page.locator('button').filter({ hasText: '長按' })
const holdButtonCount = await holdButtons.count()
expect(holdButtonCount).toBeGreaterThanOrEqual(1)
// 驗證「拒絕」按鈕存在
const rejectButtons = page.locator('button').filter({ hasText: '拒絕' })
const rejectButtonCount = await rejectButtons.count()
expect(rejectButtonCount).toBeGreaterThanOrEqual(1)
})
test('完整頁面截圖 - 雙語對照', async ({ page }) => {
// 繁體中文完整截圖
await page.goto('/zh-TW/demo', { waitUntil: 'domcontentloaded' })
await page.waitForSelector('h2', { timeout: 15000 })
await page.waitForTimeout(3000)
await page.screenshot({
path: 'test-results/screenshots/07-final-zh-TW-fullpage.png',
fullPage: true,
})
// 切換到英文
const enButton = page.locator('button').filter({ hasText: 'English' })
await enButton.click()
await page.waitForURL('**/en/demo', { timeout: 15000 })
await page.waitForSelector('h2', { timeout: 15000 })
await page.waitForTimeout(2000)
// 英文完整截圖
await page.screenshot({
path: 'test-results/screenshots/08-final-en-fullpage.png',
fullPage: true,
})
// 最終驗證: LIVE 指示器存在, Command Center 標題
const liveIndicator = page.locator('span:has-text("LIVE")')
await expect(liveIndicator).toBeVisible()
const commandCenter = page.locator('h2:has-text("Command Center")')
await expect(commandCenter).toBeVisible()
})
})