feat(e2e): Wave 4 E2E Hardening
All checks were successful
E2E Health Check / e2e-health (push) Successful in 15s

- playwright.config.ts: ignoreHTTPSErrors + deviceScaleFactor + maxDiffPixelRatio
- global.setup.ts: 環境連通性驗證 + Storage State 結構
- .gitignore: 排除 .auth/ 目錄

支援:
- 自簽憑證環境測試
- Visual Baseline 一致性 (deviceScaleFactor: 1)
- 5% 比對容差 (避免字體渲染差異)
- 未來 Auth 擴展點

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-31 16:18:36 +08:00
parent b94a7800ad
commit 60b461df50
3 changed files with 111 additions and 1 deletions

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ dist/
# Playwright 測試產物 (動態生成,不需版本控制)
**/playwright-report/
**/test-results/
**/.auth/
# 保留 Phase 19 參考截圖
!apps/web/test-results/phase19/

View File

@@ -4,6 +4,7 @@ import { defineConfig, devices } from '@playwright/test'
* Playwright E2E 測試配置
* =======================
* Phase VI: 截圖 + 錄影自動產出
* Wave 4: ignoreHTTPSErrors + Visual Baseline (2026-03-31)
*/
// Phase 20: 支持環境變數覆蓋 baseURL可針對生產環境測試
@@ -11,6 +12,9 @@ const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000'
const isRemote = baseURL !== 'http://localhost:3000'
export default defineConfig({
// Wave 4: Global Setup for Auth Bypass
globalSetup: './tests/e2e/global.setup.ts',
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
@@ -25,19 +29,30 @@ export default defineConfig({
// Base URL for navigation (可通過 PLAYWRIGHT_BASE_URL 環境變數覆蓋)
baseURL,
// Wave 4: HTTPS 錯誤忽略 (自簽憑證環境)
ignoreHTTPSErrors: true,
// 截圖與錄影 - 統帥強制要求
screenshot: 'on',
video: 'on',
trace: 'on-first-retry',
// Viewport
// Viewport - Wave 4: deviceScaleFactor 固定為 1 (Visual Baseline 一致性)
viewport: { width: 1920, height: 1080 },
deviceScaleFactor: 1,
// Timeouts
actionTimeout: 10000,
navigationTimeout: 30000,
},
// Wave 4: Visual Regression 比對容差 (5%)
expect: {
toHaveScreenshot: {
maxDiffPixelRatio: 0.05,
},
},
// Output directory for screenshots and videos
outputDir: 'test-results',

View File

@@ -0,0 +1,94 @@
/**
* Playwright Global Setup
* =======================
* Wave 4: E2E Auth Bypass + 全局初始化
*
* 功能:
* 1. 驗證測試環境連通性
* 2. 設置 Storage State (未來 Auth 擴展點)
* 3. 預熱 API 端點
*
* 使用方式:
* - 此檔案在所有測試執行前自動運行一次
* - 產生 .auth/state.json 供測試重用
*
* 建立: 2026-03-31 (台北時區)
* 建立者: Claude Code (Wave 4 E2E Hardening)
*/
import { chromium, FullConfig } from '@playwright/test'
const STORAGE_STATE_PATH = 'tests/e2e/.auth/state.json'
async function globalSetup(config: FullConfig) {
const baseURL = config.projects[0].use.baseURL || 'http://localhost:3000'
console.log(`[Global Setup] Starting with baseURL: ${baseURL}`)
const browser = await chromium.launch()
const context = await browser.newContext({
ignoreHTTPSErrors: true,
})
const page = await context.newPage()
try {
// Step 1: 驗證測試環境連通性
console.log('[Global Setup] Verifying environment connectivity...')
const response = await page.goto(baseURL, {
waitUntil: 'domcontentloaded',
timeout: 30000,
})
if (!response || !response.ok()) {
throw new Error(`Failed to reach ${baseURL}: ${response?.status()}`)
}
console.log(`[Global Setup] Environment OK: ${response.status()}`)
// Step 2: 預熱 API 端點 (可選)
// 確保 API 已就緒,避免首個測試因冷啟動而失敗
try {
const apiResponse = await page.request.get(`${baseURL}/api/v1/health`)
if (apiResponse.ok()) {
console.log('[Global Setup] API Health Check: OK')
}
} catch {
console.log('[Global Setup] API Health Check: Skipped (may not be available)')
}
// Step 3: 儲存 Storage State
// 目前無 Auth但保留此結構供未來擴展
// 當實作登入時,在此處執行登入流程並儲存狀態
await context.storageState({ path: STORAGE_STATE_PATH })
console.log(`[Global Setup] Storage state saved to: ${STORAGE_STATE_PATH}`)
// =========================================================================
// 未來 Auth 擴展點
// =========================================================================
//
// 當實作登入機制後,在此處加入:
//
// const E2E_USERNAME = process.env.E2E_USERNAME || 'e2e-test'
// const E2E_PASSWORD = process.env.E2E_PASSWORD
//
// if (E2E_PASSWORD) {
// await page.goto(`${baseURL}/login`)
// await page.fill('[data-testid="username"]', E2E_USERNAME)
// await page.fill('[data-testid="password"]', E2E_PASSWORD)
// await page.click('[data-testid="login-button"]')
// await page.waitForURL(`${baseURL}/dashboard`)
// await context.storageState({ path: STORAGE_STATE_PATH })
// }
//
// =========================================================================
} catch (error) {
console.error('[Global Setup] Failed:', error)
throw error
} finally {
await browser.close()
}
console.log('[Global Setup] Complete')
}
export default globalSetup