--- name: E2E 端到端測試規範 (E2E Testing with Playwright) description: 規範 Playwright 端到端測試的撰寫慣例、目錄結構、共用工具與執行方式,確保所有 E2E 測試保持一致性與可維護性。 --- # E2E 端到端測試規範 (E2E Testing with Playwright) 本技能定義了 Star ERP 系統中端到端 (E2E) 測試的實作標準,使用 Playwright 模擬真實使用者操作瀏覽器,驗證 UI 顯示與功能流程的正確性。 --- ## 1. 專案結構 ### 1.1 目錄配置 ``` star-erp/ ├── playwright.config.ts # Playwright 設定檔 ├── e2e/ # E2E 測試根目錄 │ ├── helpers/ # 共用工具函式 │ │ └── auth.ts # 登入 helper │ ├── screenshots/ # 測試截圖存放 │ ├── auth.spec.ts # 認證相關測試(登入、登出) │ ├── inventory.spec.ts # 庫存模組測試 │ ├── products.spec.ts # 商品模組測試 │ └── {module}.spec.ts # 依模組命名 ├── playwright-report/ # HTML 測試報告(自動產生,已 gitignore) └── test-results/ # 失敗截圖與錄影(自動產生,已 gitignore) ``` ### 1.2 命名規範 | 項目 | 規範 | 範例 | |---|---|---| | 測試檔案 | 小寫,依模組命名 `.spec.ts` | `inventory.spec.ts` | | 測試群組 | `test.describe('中文功能名稱')` | `test.describe('庫存查詢')` | | 測試案例 | 中文描述「**應**」開頭 | `test('應顯示庫存清單')` | | 截圖檔案 | `{module}-{scenario}.png` | `inventory-search-result.png` | --- ## 2. 設定檔 (playwright.config.ts) ### 2.1 核心設定 ```typescript import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:8081', // Sail 開發伺服器 screenshot: 'only-on-failure', // 失敗時自動截圖 video: 'retain-on-failure', // 失敗時保留錄影 trace: 'on-first-retry', // 重試時收集 trace }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], }); ``` ### 2.2 重要注意事項 > [!IMPORTANT] > `baseURL` 必須指向本機 Sail 開發伺服器(預設 `http://localhost:8081`)。 > 確保測試前已執行 `./vendor/bin/sail up -d` 與 `./vendor/bin/sail npm run dev`。 --- ## 3. 共用工具 (Helpers) ### 3.1 登入 Helper 位置:`e2e/helpers/auth.ts` ```typescript import { Page } from '@playwright/test'; /** * 共用登入函式 * 使用測試帳號登入 ERP 系統 */ export async function login(page: Page, username = 'mama', password = 'mama9453') { await page.goto('/'); await page.fill('#username', username); await page.fill('#password', password); await page.getByRole('button', { name: '登入系統' }).click(); // 等待儀表板載入完成 await page.waitForSelector('text=系統概況', { timeout: 10000 }); } ``` ### 3.2 使用方式 ```typescript import { login } from './helpers/auth'; test('應顯示庫存清單', async ({ page }) => { await login(page); await page.goto('/inventory/stock-query'); // ...斷言 }); ``` --- ## 4. 測試撰寫規範 ### 4.1 測試結構模板 ```typescript import { test, expect } from '@playwright/test'; import { login } from './helpers/auth'; test.describe('模組功能名稱', () => { // 若整個 describe 都需要登入,使用 beforeEach test.beforeEach(async ({ page }) => { await login(page); }); test('應正確顯示頁面標題與關鍵元素', async ({ page }) => { await page.goto('/target-page'); // 驗證頁面標題 await expect(page.getByText('頁面標題')).toBeVisible(); // 驗證表格存在 await expect(page.locator('table')).toBeVisible(); }); test('應能執行 CRUD 操作', async ({ page }) => { // ... }); }); ``` ### 4.2 斷言 (Assertions) 慣例 | 場景 | 優先使用 | 避免使用 | |---|---|---| | 驗證頁面載入 | `page.getByText('關鍵文字')` | `page.waitForURL()` ※ | | 驗證元素存在 | `expect(locator).toBeVisible()` | `.count() > 0` | | 驗證表格資料 | `page.locator('table tbody tr')` | 硬編碼行數 | | 等待操作完成 | `expect().toBeVisible({ timeout })` | `page.waitForTimeout()` | > [!NOTE] > ※ Star ERP 使用 Inertia.js,頁面導航不一定改變 URL(例如儀表板路由為 `/`)。 > 因此**優先使用頁面內容驗證**,而非依賴 URL 變化。 ### 4.3 選擇器優先順序 依照 Playwright 官方建議,選擇器優先順序為: 1. **Role** — `page.getByRole('button', { name: '登入系統' })` 2. **Text** — `page.getByText('系統概況')` 3. **Label** — `page.getByLabel('帳號')` 4. **Placeholder** — `page.getByPlaceholder('請輸入...')` 5. **Test ID** — `page.getByTestId('submit-btn')`(需在元件加 `data-testid`) 6. **CSS** — `page.locator('#username')`(最後手段) ### 4.4 禁止事項 ```typescript // ❌ 禁止:硬等待(不可預期的等待時間) await page.waitForTimeout(5000); // ✅ 正確:等待特定條件 await expect(page.getByText('操作成功')).toBeVisible({ timeout: 5000 }); // ❌ 禁止:在測試中寫死測試資料的 ID await page.goto('/products/42/edit'); // ✅ 正確:從頁面互動導航 await page.locator('table tbody tr').first().getByRole('button', { name: '編輯' }).click(); ``` --- ## 5. 截圖與視覺回歸 ### 5.1 手動截圖(文件用途) ```typescript // 成功截圖存於 e2e/screenshots/ await page.screenshot({ path: 'e2e/screenshots/inventory-list.png', fullPage: true, }); ``` ### 5.2 視覺回歸測試(偵測 UI 變化) ```typescript test('庫存頁面 UI 應保持一致', async ({ page }) => { await login(page); await page.goto('/inventory/stock-query'); // 比對截圖,pixel 級差異會報錯 await expect(page).toHaveScreenshot('stock-query.png', { maxDiffPixelRatio: 0.01, // 容許 1% 差異(動態資料) }); }); ``` > [!NOTE] > 首次執行 `toHaveScreenshot()` 會自動建立基準截圖。 > 後續執行會與基準比對,更新基準用:`npx playwright test --update-snapshots` --- ## 6. 執行指令速查 ```bash # 執行所有 E2E 測試 npx playwright test # 執行特定模組測試 npx playwright test e2e/login.spec.ts # UI 互動模式(可視化瀏覽器操作) npx playwright test --ui # 帶頭模式(顯示瀏覽器畫面) npx playwright test --headed # 產生 HTML 報告並開啟 npx playwright test --reporter=html npx playwright show-report # 更新視覺回歸基準截圖 npx playwright test --update-snapshots # 只執行特定測試案例(用 -g 篩選名稱) npx playwright test -g "登入" # Debug 模式(逐步執行) npx playwright test --debug ``` --- ## 7. 開發檢核清單 (Checklist) ### 新增頁面或功能時: - [ ] 是否已為新頁面建立對應的 `.spec.ts` 測試檔? - [ ] 測試是否覆蓋主要的 Happy Path(正常操作流程)? - [ ] 測試是否覆蓋關鍵的 Error Path(錯誤處理)? - [ ] 共用的登入步驟是否使用 `helpers/auth.ts`? - [ ] 斷言是否優先使用頁面內容而非 URL? - [ ] 選擇器是否遵循優先順序(Role > Text > Label > CSS)? - [ ] 測試是否可獨立執行(不依賴其他測試的狀態)? ### 提交程式碼前: - [ ] 全部 E2E 測試是否通過?(`npx playwright test`) - [ ] 是否有遺留的 `test.only` 或 `test.skip`?