- 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>
184 lines
6.3 KiB
TypeScript
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()
|
|
})
|
|
})
|