Skip to main content

elf-e2e

Elf Express E2E 端對端測試規範(Playwright + 已安裝的 Chrome):團隊定本 `playwright.config.ts`、 `e2e/` 目錄結構、selector 優先順序、頁面物件模式(POM)、錄影 / 截圖 / trace、retries 與 workers、CI job。 預設以 mock 模式(`VITE_USE_MOCK` 預設開)跑、不需要後端與登入;登入 / 真後端為 opt-in 變體。 當任務涉及 Playwright 設定、撰寫或重構 `e2e/*.spec.ts`、selector 選擇、POM、登入狀態重用、 flaky 測試排查、CI 上跑 E2E、或 jsdom 測不到的焦點 / IME / 版面行為時觸發。

معلومات المصدر

المستودع
elf-express/skillshare-source
آخر نشاط في المصدر
٢٠ أكتوبر ٢٠٢٦ في ٠٢:٤٣
لغة SKILL.md المكتشفة
الصينية
النجوم
٠
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
elf-e2e
description
Elf Express E2E 端對端測試規範(Playwright + 已安裝的 Chrome):團隊定本 `playwright.config.ts`、 `e2e/` 目錄結構、selector 優先順序、頁面物件模式(POM)、錄影 / 截圖 / trace、retries 與 workers、CI job。 預設以 mock 模式(`VITE_USE_MOCK` 預設開)跑、不需要後端與登入;登入 / 真後端為 opt-in 變體。 當任務涉及 Playwright 設定、撰寫或重構 `e2e/*.spec.ts`、selector 選擇、POM、登入狀態重用、 flaky 測試排查、CI 上跑 E2E、或 jsdom 測不到的焦點 / IME / 版面行為時觸發。
metadata
{"version":"1.0.0","owner":"Elf Express","upstream":{"name":"Playwright","version":"1.x","docs":"https://playwright.dev/"}}
# Elf Express E2E 測試規範 > 相關 skill:`elf-unit`(邏輯用 Vitest 測)、`elf-integration`(API / DB)、`elf-allure-report`(報告 UI)、 > `elf-vue`(`data-testid` 命名)、`elf-cicd-frontend`(完整 workflow)、`elf-stack`(Node 24.18 / pnpm)。 > > 參考專案:`TypingTrainer` 的 `playwright.config.ts`、`e2e/*.spec.ts`、`.github/workflows/ci.yml`(`e2e` job)。 ## 1. 何時使用 - 新專案導入 Playwright、或修改 `playwright.config.ts` - 撰寫 / 重構 `e2e/*.spec.ts`、建立頁面物件(POM) - 測 jsdom 觀察不到的行為:焦點、IME 組字、捲軸 / 版面是否溢出、真實事件順序、跨頁流程 - 排查 flaky 測試(retries、trace、video) - CI 上執行 E2E、上傳報告 **不要**用 E2E 測可以用 Vitest 測的計算邏輯或 API 回應格式(見 `elf-unit` §2.6)。 ## 2. 固定規則 ### 2.1 位置與命名 1. **MUST** `playwright.config.ts` 放 repo 根目錄;測試放根目錄 `e2e/`,檔名 `<功能>.spec.ts`(kebab-case,例如 `order-checkout.spec.ts`)。 2. **MUST** `.spec.ts` 只用於 E2E;單元測試一律 `.test.ts`(見 `elf-unit`)。 3. **MUST** 目錄結構固定為: ``` e2e/ ├── pages/ ← 頁面物件(一頁一個 class,PascalCase + Page) │ ├── LoginPage.ts │ └── OrderListPage.ts ├── fixtures.ts ← test.extend:注入頁面物件 ├── helpers/ ← 與頁面無關的工具(例如 overflows(el)) ├── order-list.spec.ts └── auth.setup.ts ← 只有 opt-in 的「登入 / 真後端」變體才有(§2.5) playwright/.auth/ ← 登入變體的 storageState 輸出,MUST 加入 .gitignore ``` 4. **MUST** 每個 spec 檔開頭寫 JSDoc:這組測試保護什麼行為、為什麼 jsdom / 單元測試抓不到(參考 `e2e/chinese-ime.spec.ts`)。 5. **MUST** `test.describe('<功能>')` + `test('<預期行為的英文句子>')`。 ### 2.2 Selector 策略(優先順序由高到低) 1. `page.getByTestId('order-submit')` —— **首選**。元件上加 `data-testid="<區塊>-<元素>"`(kebab-case)。 2. `page.getByRole('button', { name: 'Submit' })` —— 無 testid 且有明確無障礙名稱時。 3. `page.getByLabel(...)` / `page.getByPlaceholder(...)` —— 表單欄位。 4. `page.getByText(...)` —— 只用於驗證文字本身,不用來定位要點擊的元素。 5. CSS(`page.locator('.xxx')`)—— 只有在「測的就是該 class 的樣式 / 版面」時使用(例如量測 `.prefs` 是否溢出)。 - **MUST NOT** 使用 XPath、`nth-child`、UI 函式庫產生的 class(`.ant-btn`、`.el-button`)當定位。 參考專案的 `page.locator('button.ant-btn').first()` 是**反例**,新測試不得照抄。 - **MUST NOT** 用 `page.waitForTimeout()` 等待;用 web-first assertion(`await expect(locator).toBeVisible()`)自動等待。 ### 2.3 語系 1. **MUST** 在每個測試明確設定語系(`test.use({ locale: 'en-US' })` 或 `addInitScript` 寫入 app 的語系設定), 並**斷言該語系實際會出現的字串**。 2. **MUST NOT** 斷言「某個其他語系字串不存在」—— 參考專案曾因畫面實際是英文,兩個「中文字不存在」的斷言永遠通過。 ### 2.4 頁面物件模式(POM) 1. **MUST** 一個頁面(或大型區塊)一個 class,放 `e2e/pages/`,建構子收 `Page`,locator 宣告為 `readonly` 屬性。 2. **MUST** 頁面物件提供「動作」方法(`goto()`、`search(keyword)`、`submit()`),**MUST NOT** 在頁面物件內寫 `expect` 斷言 (例外:`goto()` 結尾可 `await expect(this.root).toBeVisible()` 確認頁面載入完成)。 3. **MUST** 頁面物件以 `e2e/fixtures.ts` 的 `test.extend` 注入,spec 從 `./fixtures` import `test` / `expect`,不從 `@playwright/test`。 4. 只在單一 spec 用到的小工具可直接寫在該 spec 內(參考 `settings-layout.spec.ts` 的 `overflows()`);兩個以上 spec 用到就移到 `helpers/`。 ### 2.5 執行模式:預設 mock,登入 / 真後端為 opt-in 1. **MUST** 預設(本機與 CI)以 **mock 模式**跑:前端 `VITE_USE_MOCK` 預設為開(`!== 'false'`,見 `elf-vue` / `elf-api-contract`), `webServer` 只起前端 dev server,**沒有** `setup` project、**沒有** `storageState`、**不需要**後端與 `E2E_USER` / `E2E_PASSWORD`。 預設 mock 資料由 `apps/src/api/mock/fixtures.ts` 提供,斷言以 fixture 內容為準。 2. 需要測「真登入 / 真 API」時才開 **opt-in 登入變體**(§3.1 第二段):`webServer` 同時起後端(`dotnet run`)與前端 (`VITE_USE_MOCK=false`),加 `setup` project(`auth.setup.ts`)登入一次存 `playwright/.auth/user.json`, 其他 project 以 `storageState` 重用;CI 需設定 secrets `E2E_USER` / `E2E_PASSWORD`。 3. **MUST NOT** 在每個測試的 `beforeEach` 走一次 UI 登入。 4. 測試帳密只從環境變數讀(`E2E_USER` / `E2E_PASSWORD`),**MUST NOT** 寫死在程式碼。 5. `page.route` 攔截只在 app 真的發 HTTP 時有作用(`VITE_USE_MOCK=false`);預設 mock 模式下 app 不打 API, 要測錯誤畫面就用登入變體 + `page.route`(範本見 `references/pom-templates.md` §7)。 ### 2.6 平行、retries、紀錄 | 設定 | 值 | 為什麼 | |------|---|--------| | `fullyParallel` | `false` | 測試共用同一個 dev server(與登入變體的後端狀態),先求穩定 | | `workers` | `1` | 同上,避免多 worker 競爭共享狀態 | | `forbidOnly` | CI 時 `true` | 禁止 `.only` 殘留導致漏跑 | | `retries` | CI `2` / 本地 `0` | CI 容忍偶發 flaky,本地暴露真實問題 | | `headless` | `true` | 要看畫面時用 `pnpm test:e2e:ui` 或 `--headed`,不改設定檔 | | `video` | `'on'` | 全錄,故障時資料完整 | | `screenshot` | `'on'` | 全截,搭配 Allure 報告 attach | | `trace` | `'on-first-retry'` | trace 檔大,只在重試時存 | | `actionTimeout` | `30_000` | 30 秒上限,涵蓋網路延遲 | | `projects` | 只有 `chrome`(`channel: 'chrome'`) | 用使用者實際使用的已安裝 Chrome,不下載 Playwright 自帶 Chromium | | `webServer` | `pnpm dev`(mock 模式),CI 不重用既有 server | Playwright 自己起 dev server | | `reporter`(CI) | `github` + `html`(可再加 allure) | `github` 讓失敗顯示在 PR 註解;`html` 讓失敗時上傳的報告不是空的。**MUST NOT** 拿掉 `github` | > **`fullyParallel: true` / `workers > 1`:沒有團隊決議就不開。** 單一 PR 不得自行改動這兩個值; > 需要時先提團隊決議(附 `--repeat-each` 的穩定性證據),決議後再改本定本。 - **MUST** flaky 測試先用 `pnpm exec playwright test <file> --repeat-each=10` 重現,再看 `test-results/**/trace.zip` (`pnpm exec playwright show-trace <path>`)與影片,**MUST NOT** 用加大 retries 或 `waitForTimeout` 掩蓋。 - **MUST** 修 bug 的 E2E:把修正改回去確認測試會紅,再改回來。 1. **MUST** `uses:` 一律釘 40 碼 commit SHA 並在行尾註解版本,SHA 與版本逐字取自 `stack/elf-stack` 3.5 節對照表;**MUST NOT** 改回 `@vN`、`@stable` 或分支名。WHY:見 elf-stack 規則 9 與其 `references/simplification-evidence.md`。 ## 3. 標準範本 ### 3.1 `playwright.config.ts`(團隊定本,直接複製) 預設版(mock 模式、無登入、無後端): ```ts import { defineConfig, devices } from '@playwright/test' /** * End-to-end tests. * * These cover what vitest structurally cannot: focus, IME composition, layout, * and anything that depends on a real browser's event ordering. * Runs against the mock API (VITE_USE_MOCK defaults to on), so no backend and no login are needed. */ export default defineConfig({ testDir: './e2e', // The suite drives one shared dev server, so tests must not race each other over its state. // Changing these two needs a team decision, not a PR. fullyParallel: false, workers: 1, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, reporter: process.env.CI ? [['github'], ['html', { open: 'never', outputFolder: 'playwright-report' }]] : [['list'], ['html', { open: 'never', outputFolder: 'playwright-report' }]], outputDir: 'test-results', use: { baseURL: 'http://localhost:5173', headless: true, video: 'on', screenshot: 'on', trace: 'on-first-retry', actionTimeout: 30_000, locale: 'en-US', }, projects: [ { name: 'chrome', use: { ...devices['Desktop Chrome'], // The installed browser rather than a Playwright download: test the browser users actually run. channel: 'chrome', }, }, ], webServer: { command: 'pnpm dev', url: 'http://localhost:5173', reuseExistingServer: !process.env.CI, timeout: 120_000, }, }) ``` **Opt-in 登入 / 真後端變體**:只替換 `projects` 與 `webServer`,其他設定不變。 ```ts projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chrome', use: { ...devices['Desktop Chrome'], channel: 'chrome', storageState: 'playwright/.auth/user.json', }, dependencies: ['setup'], }, ], webServer: [ { // Real API against a throwaway database; health endpoint decided in elf-api-design. command: 'dotnet run --project server/src/<App>.Api --urls http://localhost:5080', url: 'http://localhost:5080/api/health', // Throwaway DB only (credentials elf / elf_test_pw / elf_test, Timezone=UTC); never a shared dev database. env: { ConnectionStrings__Default: process.env.E2E_DB_CONNECTION ?? '' }, reuseExistingServer: !process.env.CI, timeout: 180_000, }, { command: 'pnpm dev', url: 'http://localhost:5173', // Talk to the backend above instead of the mock fixtures. env: { VITE_USE_MOCK: 'false' }, reuseExistingServer: !process.env.CI, timeout: 120_000, }, ], ``` - 登入變體需要:`e2e/auth.setup.ts`(`references/pom-templates.md` §4)、後端可連的拋棄式資料庫(連線字串放環境變數 `E2E_DB_CONNECTION`)、 CI secrets `E2E_USER` / `E2E_PASSWORD`,以及 CI job 內的 `actions/setup-dotnet` v5(SHA 見 elf-stack 3.5;`global-json-file: global.json`)。 - 後端埠號(上例 5080)與 dev server 的 API proxy 目標以專案設定為準,兩邊要一致。 - `baseURL` / `webServer.url` 的前端埠號以專案 Vite 設定為準(參考專案為 5173)。 ### 3.2 `package.json` scripts 與安裝 ```json { "scripts": { "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", "test:e2e:report": "playwright show-report" } } ``` ```bash pnpm add -D -w @playwright/test # Playwright lives at the repo root, next to playwright.config.ts pnpm exec playwright install chrome # 本機已有 Chrome 可略過 pnpm test:e2e ``` `.gitignore` 加入: ``` /test-results/ /playwright-report/ /playwright/.auth/ /allure-results/ /allure-report/ ``` ### 3.3 POM、fixtures、auth.setup、spec 範本 完整可複製檔案見 [`references/pom-templates.md`](references/pom-templates.md): `pages/LoginPage.ts`、`pages/OrderListPage.ts`、`fixtures.ts`、`auth.setup.ts`(登入變體)、`order-list.spec.ts`、 `helpers/layout.ts`、`page.route` 攔截 API 範例。 ### 3.4 CI job(GitHub Actions) 預設 job(mock 模式,不需要後端與 secrets): ```yaml e2e: name: e2e (browser) runs-on: ubuntu-latest steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 # Version comes from the "packageManager" field in package.json; must run before setup-node. - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: node-version-file: .nvmrc cache: pnpm - run: pnpm install --frozen-lockfile # The config targets the installed Chrome rather than a Playwright download, # so the browser has to be present here too. - name: Install Chrome run: pnpm exec playwright install --with-deps chrome - run: pnpm test:e2e - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 if: failure() with: name: playwright-report path: | playwright-report/ test-results/ retention-days: 7 ``` - 開啟登入變體時,在 `pnpm install` 前加 `actions/setup-dotnet` v5(SHA 見 elf-stack 3.5;`with: global-json-file: global.json`), 並在 `pnpm test:e2e` 步驟加 `env: E2E_USER: ${{ secrets.E2E_USER }}`、`E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}`。 - 參考專案 CI 只設 `reporter: 'github'`,失敗時上傳的 `playwright-report/` 是空的;本定本在 CI 同時輸出 `github` + `html`,修正此問題。 - 完整 workflow(與 lint / unit / build 的相依)見 `elf-cicd-frontend`。 ### 3.5 與 Allure Report 整合 預設 reporter 為 Playwright 內建 HTML。專案採用 Allure 統一報告時(見 `elf-allure-report`): ```bash pnpm add -D -w allure-playwright allure ``` ```ts // playwright.config.ts —— 只加 allure reporter,其他設定不變。CI 仍保留 github + html。 reporter: process.env.CI ? [ ['github'], ['html', { open: 'never', outputFolder: 'playwright-report' }], ['allure-playwright', { detail: true, resultsDir: 'allure-results', suiteTitle: false }], ] : [ ['list'], ['allure-playwright', { detail: true, resultsDir: 'allure-results', suiteTitle: false }], ], ``` ```bash pnpm test:e2e # 寫 JSON 到 allure-results/ pnpm exec allure generate allure-results -o allure-report pnpm exec allure open allure-report
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub