feat(e2e): Wave 4 E2E Hardening
All checks were successful
E2E Health Check / e2e-health (push) Successful in 15s
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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@ dist/
|
||||
# Playwright 測試產物 (動態生成,不需版本控制)
|
||||
**/playwright-report/
|
||||
**/test-results/
|
||||
**/.auth/
|
||||
# 保留 Phase 19 參考截圖
|
||||
!apps/web/test-results/phase19/
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
94
apps/web/tests/e2e/global.setup.ts
Normal file
94
apps/web/tests/e2e/global.setup.ts
Normal 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
|
||||
Reference in New Issue
Block a user