From 60b461df50e9c0d5166e20e979c798c4706a1edf Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 31 Mar 2026 16:18:36 +0800 Subject: [PATCH] feat(e2e): Wave 4 E2E Hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .gitignore | 1 + apps/web/playwright.config.ts | 17 +++++- apps/web/tests/e2e/global.setup.ts | 94 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 apps/web/tests/e2e/global.setup.ts diff --git a/.gitignore b/.gitignore index bb8490ce..b61c8d25 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ dist/ # Playwright 測試產物 (動態生成,不需版本控制) **/playwright-report/ **/test-results/ +**/.auth/ # 保留 Phase 19 參考截圖 !apps/web/test-results/phase19/ diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index 41e956c8..195ffea9 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -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', diff --git a/apps/web/tests/e2e/global.setup.ts b/apps/web/tests/e2e/global.setup.ts new file mode 100644 index 00000000..4815bd69 --- /dev/null +++ b/apps/web/tests/e2e/global.setup.ts @@ -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