164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
/**
|
||
* AWOOOI Smoke Tests
|
||
* ==================
|
||
* 5 個關鍵頁面的 Smoke Test — 部署後快速驗證基本功能
|
||
*
|
||
* 測試範圍:
|
||
* 1. 首頁 (/) — 200 OK,標題包含 AWOOOI
|
||
* 2. Dashboard (/dashboard) — 無 JS console 錯誤
|
||
* 3. Incidents (/incidents) — 列表容器存在
|
||
* 4. Approvals (/approvals) — 頁面可存取
|
||
* 5. Omni-Terminal (/terminal) — WebSocket 連線不報錯
|
||
*
|
||
* 注意: 需登入才能查看的頁面內容以 test.skip 跳過,僅驗證頁面可存取性。
|
||
* 截圖存至 test-results/screenshots/ 供人工比對。
|
||
*
|
||
* @author 首席架構師 (Claude Code)
|
||
* @version 1.0.0
|
||
* @date 2026-04-01 (台北時間)
|
||
*/
|
||
|
||
import { test, expect } from '@playwright/test'
|
||
import * as fs from 'fs'
|
||
import * as path from 'path'
|
||
|
||
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000'
|
||
const SCREENSHOT_DIR = 'test-results/screenshots'
|
||
|
||
// 確保截圖目錄存在
|
||
test.beforeAll(() => {
|
||
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
||
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true })
|
||
}
|
||
})
|
||
|
||
test.setTimeout(30000)
|
||
|
||
// =============================================================================
|
||
// 1. 首頁 — 200 OK + 標題包含 AWOOOI
|
||
// =============================================================================
|
||
|
||
test('首頁載入 — HTTP 200 + 標題含 AWOOOI', async ({ page }) => {
|
||
const response = await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' })
|
||
|
||
// HTTP 狀態碼 200
|
||
expect(response?.status()).toBe(200)
|
||
|
||
// 頁面標題含 AWOOOI (大小寫不限)
|
||
const title = await page.title()
|
||
expect(title.toLowerCase()).toContain('awoooi')
|
||
|
||
// 截圖
|
||
await page.screenshot({
|
||
path: path.join(SCREENSHOT_DIR, '01-home.png'),
|
||
fullPage: false,
|
||
})
|
||
})
|
||
|
||
// =============================================================================
|
||
// 2. Dashboard — 無 JS console 錯誤
|
||
// =============================================================================
|
||
|
||
test('Dashboard 載入 — 無 JS console 錯誤', async ({ page }) => {
|
||
const jsErrors: string[] = []
|
||
|
||
// 收集 JS 錯誤 (排除 Next.js HMR/chunk 載入警告)
|
||
page.on('pageerror', (err) => {
|
||
jsErrors.push(err.message)
|
||
})
|
||
|
||
const response = await page.goto(`${BASE_URL}/dashboard`, { waitUntil: 'domcontentloaded' })
|
||
|
||
// 頁面可存取 (200 或 重導向後的頁面)
|
||
expect(response?.status()).toBeLessThan(500)
|
||
|
||
// 截圖
|
||
await page.screenshot({
|
||
path: path.join(SCREENSHOT_DIR, '02-dashboard.png'),
|
||
fullPage: false,
|
||
})
|
||
|
||
// 無嚴重 JS 錯誤
|
||
const criticalErrors = jsErrors.filter(
|
||
(e) =>
|
||
!e.includes('ChunkLoadError') && // Next.js 開發時的 chunk 錯誤可忽略
|
||
!e.includes('Loading chunk') &&
|
||
!e.includes('Loading CSS chunk'),
|
||
)
|
||
expect(criticalErrors).toHaveLength(0)
|
||
})
|
||
|
||
// =============================================================================
|
||
// 3. Incidents — 列表容器存在
|
||
// =============================================================================
|
||
|
||
test('Incidents 頁面 — 列表容器存在', async ({ page }) => {
|
||
const response = await page.goto(`${BASE_URL}/incidents`, { waitUntil: 'domcontentloaded' })
|
||
|
||
expect(response?.status()).toBeLessThan(500)
|
||
|
||
// 截圖 (登入前)
|
||
await page.screenshot({
|
||
path: path.join(SCREENSHOT_DIR, '03-incidents.png'),
|
||
fullPage: false,
|
||
})
|
||
|
||
// 頁面應存在某個容器 (未登入時會是登入頁或列表頁)
|
||
// 驗證頁面 body 有內容 (非空白頁)
|
||
const bodyText = await page.evaluate(() => document.body.innerText.trim())
|
||
expect(bodyText.length).toBeGreaterThan(0)
|
||
|
||
// Note: 列表容器 (data-testid="incidents-list" 或同類) 需登入才可見,
|
||
// 此處僅驗證頁面可存取且非空白。
|
||
// 完整列表驗證見 authenticated E2E tests。
|
||
})
|
||
|
||
// =============================================================================
|
||
// 4. Approvals — 頁面可存取
|
||
// =============================================================================
|
||
|
||
test('Approvals 頁面 — 頁面可存取', async ({ page }) => {
|
||
const response = await page.goto(`${BASE_URL}/approvals`, { waitUntil: 'domcontentloaded' })
|
||
|
||
expect(response?.status()).toBeLessThan(500)
|
||
|
||
await page.screenshot({
|
||
path: path.join(SCREENSHOT_DIR, '04-approvals.png'),
|
||
fullPage: false,
|
||
})
|
||
|
||
// 頁面有內容
|
||
const bodyText = await page.evaluate(() => document.body.innerText.trim())
|
||
expect(bodyText.length).toBeGreaterThan(0)
|
||
})
|
||
|
||
// =============================================================================
|
||
// 5. Omni-Terminal — WebSocket 連線不報錯
|
||
// =============================================================================
|
||
|
||
test('Omni-Terminal — 頁面載入且無 WebSocket 連線錯誤', async ({ page }) => {
|
||
const wsErrors: string[] = []
|
||
|
||
// 監聽 WebSocket 錯誤
|
||
page.on('pageerror', (err) => {
|
||
if (err.message.toLowerCase().includes('websocket')) {
|
||
wsErrors.push(err.message)
|
||
}
|
||
})
|
||
|
||
const response = await page.goto(`${BASE_URL}/terminal`, { waitUntil: 'domcontentloaded' })
|
||
|
||
expect(response?.status()).toBeLessThan(500)
|
||
|
||
// 等待短暫時間讓 WS 初始化
|
||
await page.waitForTimeout(2000)
|
||
|
||
await page.screenshot({
|
||
path: path.join(SCREENSHOT_DIR, '05-terminal.png'),
|
||
fullPage: false,
|
||
})
|
||
|
||
// 無 WebSocket 錯誤
|
||
expect(wsErrors).toHaveLength(0)
|
||
})
|