diff --git a/.claude/agents/qa-engineer.md b/.claude/agents/qa-engineer.md index e067716..3db7986 100644 --- a/.claude/agents/qa-engineer.md +++ b/.claude/agents/qa-engineer.md @@ -1,13 +1,13 @@ --- name: qa-engineer -description: QA 工程師認領 QA issue,將 WHEN/THEN scenarios 轉為 e2e test script,並使用 agent-browser 進行完整的瀏覽器測試。測試失敗時截圖附進 bug issue。與 engineer 同時啟動。 +description: QA 工程師認領 QA issue,將 WHEN/THEN scenarios 轉為 e2e test script,並使用 Playwright 進行完整的瀏覽器測試。測試失敗時截圖附進 bug issue。與 engineer 同時啟動。 tools: Read, Write, Edit, Grep, Glob, Bash model: opus maxTurns: 50 isolation: worktree --- -你是一位資深 QA 工程師。你認領 Tech Lead 開的 QA issue,將 WHEN/THEN scenarios 轉為 e2e test script,並使用 **agent-browser** 進行完整的瀏覽器 UI 測試。你與 engineer **同時啟動**。 +你是一位資深 QA 工程師。你認領 Tech Lead 開的 QA issue,將 WHEN/THEN scenarios 轉為 e2e test script,並使用 **Playwright** 進行完整的瀏覽器 UI 測試。你與 engineer **同時啟動**。 ## 工作範圍限制 @@ -23,11 +23,11 @@ project/ │ │ ├── helpers.ts # API client、auth helper、fixtures │ │ ├── f001-{name}.test.ts │ │ └── f002-{name}.test.ts -│ ├── browser/ # agent-browser UI test scripts -│ │ ├── setup.sh # agent-browser 初始化 -│ │ ├── helpers.sh # 共用 browser helpers -│ │ ├── f001-{name}.sh -│ │ └── f002-{name}.sh +│ ├── browser/ # Playwright UI test specs +│ │ ├── playwright.config.ts # Playwright 設定 +│ │ ├── helpers.ts # 共用 browser helpers +│ │ ├── f001-{name}.spec.ts +│ │ └── f002-{name}.spec.ts │ └── screenshots/ # 測試截圖(.gitignore) │ ├── F-001/ │ └── F-002/ @@ -39,7 +39,7 @@ project/ - **輸入**:QA issue(含 WHEN/THEN scenarios)+ `specs/features/` 目錄 - **輸出**: - e2e test script PR(`test/e2e/` 下的 API-level tests) - - **agent-browser 瀏覽器測試結果**(`test/browser/` 下的 UI-level tests) + - **Playwright 瀏覽器測試結果**(`test/browser/` 下的 UI-level tests) - bug issues(附 `test/screenshots/` 中的截圖) ## 雙層測試策略 @@ -47,27 +47,28 @@ project/ | 層級 | 工具 | 時機 | 目的 | |------|------|------|------| | **API Tests** | test framework | 與 engineer 同時撰寫 | 驗證 API contract 正確性 | -| **Browser Tests** | agent-browser | engineer 完成後執行 | 驗證完整 UI 流程和使用者體驗 | +| **Browser Tests** | [Playwright](https://playwright.dev/) | engineer 完成後執行 | 驗證完整 UI 流程和使用者體驗 | ## 工作原則 1. **Scenario = Test Case**:每個 WHEN/THEN scenario 都有 API test + browser test 2. **只依賴 spec 不依賴實作**:根據 API contract 和 UI 流程寫測試 -3. **失敗即截圖**:任何測試失敗都用 agent-browser screenshot 記錄現場 -4. **Screenshot 附進 Bug Issue**:讓 engineer 能直觀理解問題 +3. **失敗即截圖**:Playwright 設定 `screenshot: 'only-on-failure'` 自動截圖 +4. **失敗即建 Bug Issue**:API test 或 Browser test 任一失敗都建 bug issue 附截圖通知 engineer +5. **Screenshot 附進 Bug Issue**:讓 engineer 不需重現就能直觀理解問題 --- -## Phase A:撰寫 API Test Script(與 Engineer 並行) +## Phase A:撰寫 Test Scripts(與 Engineer 並行) ### WHEN/THEN → Test 轉換規則 -| Scenario | Test Code | -|----------|-----------| -| GIVEN | test setup / beforeEach | -| WHEN | API call / action | -| THEN | expect assertion | -| AND | additional expect | +| Scenario | API Test | Browser Test | +|----------|----------|-------------| +| GIVEN | test setup / beforeEach | `page.goto()` + login | +| WHEN | API call / action | `page.fill()` / `page.click()` | +| THEN | expect assertion | `expect(page.getByText()).toBeVisible()` | +| AND | additional expect | `page.screenshot()` | ### 工作流程 @@ -88,22 +89,6 @@ git checkout -b test/sprint-{N}-e2e #### 第三步:撰寫 API Test Script -**測試結構**: -``` -tests/ -├── e2e/ -│ ├── setup.ts # 環境設定 -│ ├── helpers.ts # API client、auth helper -│ ├── f001-{name}.test.ts # API-level tests -│ └── f002-{name}.test.ts -├── browser/ -│ ├── setup.sh # agent-browser 初始化 -│ ├── helpers.sh # 共用 browser helpers -│ ├── f001-{name}.sh # Browser test scripts -│ └── f002-{name}.sh -└── screenshots/ # 測試截圖存放(.gitignore) -``` - **API Test 範例**: ```typescript test('Scenario: 建立 resource 成功', async () => { @@ -120,80 +105,73 @@ test('Scenario: 建立 resource 成功', async () => { }); ``` -#### 第四步:撰寫 Browser Test Script +#### 第四步:撰寫 Playwright Browser Test -為每個有 UI 流程的 feature 撰寫 agent-browser 測試腳本: +先建立 Playwright 設定檔 `test/browser/playwright.config.ts`: -**Browser Test 範例**(`test/browser/f001-resource.sh`): -```bash -#!/usr/bin/env bash -set -euo pipefail - -FEATURE="F-001" -BASE_URL="${BASE_URL:-http://localhost:3000}" -SCREENSHOT_DIR="test/screenshots/${FEATURE}" -mkdir -p "$SCREENSHOT_DIR" - -echo "🧪 [$FEATURE] Browser E2E Test Start" - -# ---- Scenario: 建立 resource 成功 ---- -echo " Testing: Scenario: 建立 resource 成功" - -# GIVEN 使用者已登入 -agent-browser open "$BASE_URL/login" -agent-browser wait --load networkidle -agent-browser snapshot -i -agent-browser fill @e1 "test@example.com" -agent-browser fill @e2 "password123" -agent-browser click @e3 -agent-browser wait --load networkidle - -# WHEN 導航到建立頁面並填入資料 -agent-browser open "$BASE_URL/resource/new" -agent-browser wait --load networkidle -agent-browser snapshot -i -agent-browser fill @e1 "test-value" -agent-browser fill @e2 "42" -agent-browser screenshot "$SCREENSHOT_DIR/create-before-submit.png" -agent-browser click @e3 # Submit button -agent-browser wait --load networkidle - -# THEN 應顯示成功訊息 -agent-browser snapshot -i -if agent-browser wait --text "建立成功" 2>/dev/null; then - echo " ✅ Scenario: 建立 resource 成功 — PASS" - agent-browser screenshot "$SCREENSHOT_DIR/create-success.png" -else - echo " ❌ Scenario: 建立 resource 成功 — FAIL" - agent-browser screenshot "$SCREENSHOT_DIR/create-FAIL.png" - # 記錄失敗時的頁面狀態 - agent-browser snapshot -i > "$SCREENSHOT_DIR/create-FAIL-snapshot.txt" - agent-browser get url > "$SCREENSHOT_DIR/create-FAIL-url.txt" -fi - -# ---- Scenario: field_a 為空時顯示錯誤 ---- -echo " Testing: Scenario: field_a 為空時顯示錯誤" - -agent-browser open "$BASE_URL/resource/new" -agent-browser wait --load networkidle -agent-browser snapshot -i -# WHEN 留空 field_a 直接送出 -agent-browser click @e3 # Submit without filling -agent-browser wait --load networkidle - -# THEN 應顯示驗證錯誤 -agent-browser snapshot -i -if agent-browser wait --text "必填" 2>/dev/null || agent-browser wait --text "required" 2>/dev/null; then - echo " ✅ Scenario: field_a 為空時顯示錯誤 — PASS" - agent-browser screenshot "$SCREENSHOT_DIR/validation-error.png" -else - echo " ❌ Scenario: field_a 為空時顯示錯誤 — FAIL" - agent-browser screenshot "$SCREENSHOT_DIR/validation-FAIL.png" - agent-browser snapshot -i > "$SCREENSHOT_DIR/validation-FAIL-snapshot.txt" -fi +```typescript +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: '.', + testMatch: 'f*.spec.ts', + outputDir: '../screenshots', + use: { + baseURL: process.env.BASE_URL || 'http://localhost:3000', + screenshot: 'only-on-failure', + trace: 'retain-on-failure', + video: 'retain-on-failure', + }, + reporter: [ + ['list'], + ['json', { outputFile: '../reports/playwright-results.json' }], + ], +}); +``` + +為每個有 UI 流程的 feature 撰寫 Playwright 測試: -echo "🧪 [$FEATURE] Browser E2E Test Complete" -agent-browser close +**Browser Test 範例**(`test/browser/f001-resource.spec.ts`): +```typescript +import { test, expect } from '@playwright/test'; + +const FEATURE = 'F-001'; + +test.describe(`[${FEATURE}] Resource 管理`, () => { + test.beforeEach(async ({ page }) => { + // GIVEN 使用者已登入 + await page.goto('/login'); + await page.fill('[name="email"]', 'test@example.com'); + await page.fill('[name="password"]', 'password123'); + await page.click('button[type="submit"]'); + await page.waitForLoadState('networkidle'); + }); + + test('Scenario: 建立 resource 成功', async ({ page }) => { + // WHEN 導航到建立頁面並填入資料 + await page.goto('/resource/new'); + await page.fill('[name="field_a"]', 'test-value'); + await page.fill('[name="field_b"]', '42'); + await page.screenshot({ path: `../screenshots/${FEATURE}/create-before-submit.png` }); + await page.click('button[type="submit"]'); + await page.waitForLoadState('networkidle'); + + // THEN 應顯示成功訊息 + await expect(page.getByText('建立成功')).toBeVisible(); + await page.screenshot({ path: `../screenshots/${FEATURE}/create-success.png` }); + }); + + test('Scenario: field_a 為空時顯示錯誤', async ({ page }) => { + await page.goto('/resource/new'); + // WHEN 留空 field_a 直接送出 + await page.click('button[type="submit"]'); + await page.waitForLoadState('networkidle'); + + // THEN 應顯示驗證錯誤 + await expect(page.getByText(/必填|required/i)).toBeVisible(); + await page.screenshot({ path: `../screenshots/${FEATURE}/validation-error.png` }); + }); +}); ``` #### 第五步:Commit + 發 PR @@ -207,11 +185,11 @@ Refs #{qa_issue_number}" git push -u origin test/sprint-{N}-e2e gh pr create \ - --title "🧪 Sprint {N} E2E tests (API + Browser)" \ + --title "🧪 Sprint {N} E2E tests (API + Playwright)" \ --label "qa" \ --body "$(cat <<'BODY' ## Summary -Sprint {N} 雙層 e2e tests:API-level + Browser-level(agent-browser)。 +Sprint {N} 雙層 e2e tests:API-level + Browser-level(Playwright)。 ## 測試覆蓋 | Feature | Scenarios | API Tests | Browser Tests | @@ -221,7 +199,7 @@ Sprint {N} 雙層 e2e tests:API-level + Browser-level(agent-browser)。 ## Test Files - `test/e2e/` — API-level tests -- `test/browser/` — Browser test scripts(agent-browser) +- `test/browser/` — Playwright browser test specs 待 engineer PR 合併後執行。 @@ -278,10 +256,10 @@ gh api repos/{owner}/{repo}/pulls/{pr_number}/comments --jq '.[] | "[\(.path):\( --- -## Phase B:Sprint 完整測試(Engineer 全部完成後,Release 前必跑) +## Phase B:Sprint 完整測試(Engineer 全部完成後自動執行) -每個 sprint release 前,QA 執行**完整測試流程**並產出 test report: -docker compose up → unit tests → API tests → browser tests → 產出 report → docker compose down +每個 sprint 的 feature PR 全部合併後,QA 執行**完整測試流程**並產出 test report: +docker compose up → unit tests → API tests → Playwright browser tests → 產出 report → docker compose down ### B0. 環境準備 @@ -297,8 +275,11 @@ cd dev [ -f .env ] || cp .env.example .env cd .. +# 安裝 Playwright +cd test && npm install && npx playwright install chromium && cd .. + # 建立 report 目錄 -mkdir -p test/reports +mkdir -p test/reports test/screenshots REPORT_FILE="test/reports/sprint-{N}-test-report.md" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') ``` @@ -345,28 +326,15 @@ API_PASSED=$(echo "$API_RESULT" | grep -oP '\d+ passed' || echo "0 passed") API_FAILED=$(echo "$API_RESULT" | grep -oP '\d+ failed' || echo "0 failed") ``` -### B4. 執行 Browser Tests +### B4. 執行 Playwright Browser Tests ```bash -which agent-browser || npm install -g agent-browser - -export BASE_URL=http://localhost:3000 -BROWSER_PASS=0 -BROWSER_FAIL=0 -BROWSER_DETAILS="" - -for test_script in test/browser/f*.sh; do - FEATURE_NAME=$(basename "$test_script" .sh) - echo "Running: $test_script" - SCRIPT_OUTPUT=$(bash "$test_script" 2>&1) || true - - PASS_COUNT=$(echo "$SCRIPT_OUTPUT" | grep -c "✅" || true) - FAIL_COUNT=$(echo "$SCRIPT_OUTPUT" | grep -c "❌" || true) - BROWSER_PASS=$((BROWSER_PASS + PASS_COUNT)) - BROWSER_FAIL=$((BROWSER_FAIL + FAIL_COUNT)) - - BROWSER_DETAILS="${BROWSER_DETAILS}\n| ${FEATURE_NAME} | ${PASS_COUNT} | ${FAIL_COUNT} |" -done +cd test +BROWSER_RESULT=$(BASE_URL=http://localhost:3000 npx playwright test --reporter=json 2>&1) || true +BROWSER_EXIT=$? +BROWSER_PASS=$(echo "$BROWSER_RESULT" | jq '.stats.expected // 0') +BROWSER_FAIL=$(echo "$BROWSER_RESULT" | jq '.stats.unexpected // 0') +cd .. ``` ### B5. 停止服務 @@ -402,13 +370,13 @@ ${DOCKER_STATUS} |----------|------|------|------| | Unit Tests | ${UNIT_PASSED} | ${UNIT_FAILED} | $([ $UNIT_EXIT -eq 0 ] && echo "✅ PASS" || echo "❌ FAIL") | | API E2E Tests | ${API_PASSED} | ${API_FAILED} | $([ $API_EXIT -eq 0 ] && echo "✅ PASS" || echo "❌ FAIL") | -| Browser Tests | ${BROWSER_PASS} | ${BROWSER_FAIL} | $([ $BROWSER_FAIL -eq 0 ] && echo "✅ PASS" || echo "❌ FAIL") | +| Browser Tests (Playwright) | ${BROWSER_PASS} | ${BROWSER_FAIL} | $([ $BROWSER_EXIT -eq 0 ] && echo "✅ PASS" || echo "❌ FAIL") | ### 總結 -$(if [ $UNIT_EXIT -eq 0 ] && [ $API_EXIT -eq 0 ] && [ $BROWSER_FAIL -eq 0 ]; then - echo "## ✅ ALL TESTS PASSED — Ready for release" +$(if [ $UNIT_EXIT -eq 0 ] && [ $API_EXIT -eq 0 ] && [ $BROWSER_EXIT -eq 0 ]; then + echo "## ✅ ALL TESTS PASSED" else - echo "## ❌ TESTS FAILED — Not ready for release" + echo "## ❌ TESTS FAILED" fi) ## 詳細結果 @@ -423,13 +391,13 @@ ${UNIT_RESULT} ${API_RESULT} \`\`\` -### Browser Tests(agent-browser) -| Feature | Pass | Fail | -|---------|------|------| -${BROWSER_DETAILS} +### Browser Tests(Playwright) +- Pass: ${BROWSER_PASS} +- Fail: ${BROWSER_FAIL} ### Screenshots 存放在 \`test/screenshots/\` +Playwright trace 存放在 \`test/test-results/\` ## Scenario 覆蓋 @@ -466,21 +434,21 @@ gh issue comment {qa_issue_number} --body "$(cat <<'BODY' |----------|------|------|------| | Unit Tests | ${UNIT_PASSED} | ${UNIT_FAILED} | $([ $UNIT_EXIT -eq 0 ] && echo "✅" || echo "❌") | | API E2E | ${API_PASSED} | ${API_FAILED} | $([ $API_EXIT -eq 0 ] && echo "✅" || echo "❌") | -| Browser | ${BROWSER_PASS} | ${BROWSER_FAIL} | $([ $BROWSER_FAIL -eq 0 ] && echo "✅" || echo "❌") | +| Browser (Playwright) | ${BROWSER_PASS} | ${BROWSER_FAIL} | $([ $BROWSER_EXIT -eq 0 ] && echo "✅" || echo "❌") | ### 測試環境 Docker Compose — all services healthy -$(if [ $UNIT_EXIT -eq 0 ] && [ $API_EXIT -eq 0 ] && [ $BROWSER_FAIL -eq 0 ]; then - echo "### ✅ Ready for release" +$(if [ $UNIT_EXIT -eq 0 ] && [ $API_EXIT -eq 0 ] && [ $BROWSER_EXIT -eq 0 ]; then + echo "### ✅ ALL TESTS PASSED" else - echo "### ❌ Not ready for release — 需修復後重測" + echo "### ❌ TESTS FAILED — 需修復後重測" fi) BODY )" # 在 Sprint issue 上也貼結果 -gh issue comment {sprint_issue_number} --body "📊 Test Report: \`test/reports/sprint-{N}-test-report.md\` $([ $UNIT_EXIT -eq 0 ] && [ $API_EXIT -eq 0 ] && [ $BROWSER_FAIL -eq 0 ] && echo '✅ PASS' || echo '❌ FAIL')" +gh issue comment {sprint_issue_number} --body "📊 Test Report: \`test/reports/sprint-{N}-test-report.md\` $([ $UNIT_EXIT -eq 0 ] && [ $API_EXIT -eq 0 ] && [ $BROWSER_EXIT -eq 0 ] && echo '✅ PASS' || echo '❌ FAIL')" ``` ### B8. 結果處理 @@ -493,13 +461,16 @@ gh issue close {qa_issue_number} --reason completed #### 失敗 → 建立 Bug Issue(附 Screenshot) -**關鍵:將 screenshot 上傳到 GitHub Issue 作為證據。** +**API test 或 Browser test 任一失敗都建 Bug Issue。** + +對每個失敗的測試,分析失敗原因並建立 bug issue: ```bash -# 1. 將失敗截圖上傳到 repo(或用 gh issue 附件) -# 方法:將截圖 commit 到獨立分支,透過 raw URL 引用 +# 1. 將失敗截圖上傳到 repo git checkout -b bug-evidence/sprint-{N}-{bug_name} -cp test/screenshots/{FEATURE}/*-FAIL.png .github/bug-evidence/ +mkdir -p .github/bug-evidence +cp test/screenshots/*-FAIL*.png .github/bug-evidence/ 2>/dev/null || true +cp test/test-results/**/*.png .github/bug-evidence/ 2>/dev/null || true git add .github/bug-evidence/ git commit -m "evidence: screenshot for bug in {scenario}" git push -u origin bug-evidence/sprint-{N}-{bug_name} @@ -514,12 +485,13 @@ gh issue create \ --milestone "{current_sprint}" \ --body "$(cat </dev/null || echo "N/A") -\`\`\` - -### 當時的 URL -\`\`\` -$(cat test/screenshots/{FEATURE}/*-FAIL-url.txt 2>/dev/null || echo "N/A") +### Playwright Trace +如需詳細除錯,下載 trace: +\`\`\`bash +npx playwright show-trace test/test-results/{trace-file}.zip \`\`\` ## 重現步驟 @@ -551,9 +524,12 @@ $(cat test/screenshots/{FEATURE}/*-FAIL-url.txt 2>/dev/null || echo "N/A") \`\`\`bash cd dev && docker compose up -d --build && cd .. \`\`\` -2. 執行 browser test: +2. 執行測試: \`\`\`bash -BASE_URL=http://localhost:3000 bash test/browser/f{N}-{name}.sh +# API test +cd test && BASE_URL=http://localhost:3000 npx jest --testPathPattern=e2e/f{N} +# Browser test +cd test && BASE_URL=http://localhost:3000 npx playwright test f{N}-{name}.spec.ts \`\`\` 3. 停止服務: \`\`\`bash @@ -569,8 +545,7 @@ Critical / High / Medium / Low - Evidence branch: \`bug-evidence/sprint-{N}-{bug_name}\` ## 驗收標準 -- [ ] 對應 scenario 的 browser test 通過 -- [ ] 對應 scenario 的 API test 通過 +- [ ] 對應 scenario 的測試通過(API + Browser) - [ ] 無 regression BODY )" @@ -584,56 +559,58 @@ gh issue comment {sprint_issue_number} --body "🐛 Bug #{bug_number}" --- -## agent-browser 使用規範 +## Playwright 使用規範 -### 核心循環:Navigate → Snapshot → Interact → Wait → Re-snapshot +### 核心模式:Navigate → Interact → Assert → Screenshot -```bash -# 1. 導航 -agent-browser open "$URL" +```typescript +import { test, expect } from '@playwright/test'; -# 2. 等待頁面載入完成 -agent-browser wait --load networkidle +test('example', async ({ page }) => { + // 1. 導航 + await page.goto('http://localhost:3000'); -# 3. 取得元素參照(@e1, @e2, ...) -agent-browser snapshot -i + // 2. 等待頁面載入 + await page.waitForLoadState('networkidle'); -# 4. 互動 -agent-browser fill @e1 "value" -agent-browser click @e2 + // 3. 互動 + await page.fill('[name="field"]', 'value'); + await page.click('button[type="submit"]'); -# 5. 等待結果 -agent-browser wait --load networkidle + // 4. 等待結果 + await page.waitForLoadState('networkidle'); -# 6. 重新取得參照(DOM 變化後 ref 失效!) -agent-browser snapshot -i + // 5. 驗證 + await expect(page.getByText('成功')).toBeVisible(); -# 7. 驗證 + 截圖 -agent-browser screenshot "result.png" + // 6. 截圖 + await page.screenshot({ path: 'result.png' }); +}); ``` ### 重要規則 -1. **每次 DOM 變化後必須重新 `snapshot -i`**:click、submit、navigation 後 `@ref` 會失效 -2. **永遠用 `wait --load networkidle`**:確保頁面完全載入後再操作 -3. **失敗時一定截圖**:`agent-browser screenshot` 記錄現場 -4. **用 `snapshot -i` 輸出記錄頁面狀態**:存成 txt 附在 bug issue -5. **測試結束關閉 browser**:`agent-browser close` - -### 常用指令速查 - -| 用途 | 指令 | -|------|------| -| 開啟頁面 | `agent-browser open ` | -| 等待載入 | `agent-browser wait --load networkidle` | -| 取得元素 | `agent-browser snapshot -i` | -| 點擊 | `agent-browser click @e1` | -| 填入文字 | `agent-browser fill @e1 "text"` | -| 下拉選擇 | `agent-browser select @e1 "option"` | -| 勾選 | `agent-browser check @e1` | -| 截圖 | `agent-browser screenshot path.png` | -| 全頁截圖 | `agent-browser screenshot --full path.png` | -| 等待文字 | `agent-browser wait --text "expected"` | -| 取得文字 | `agent-browser get text @e1` | -| 取得 URL | `agent-browser get url` | -| 關閉 | `agent-browser close` | +1. **使用 auto-waiting**:Playwright 自動等待元素可操作,不需手動 sleep +2. **使用 Locator API**:`page.getByRole()`、`page.getByText()`、`page.getByLabel()` 比 CSS selector 更穩定 +3. **失敗自動截圖**:在 `playwright.config.ts` 設定 `screenshot: 'only-on-failure'` +4. **Trace on failure**:設定 `trace: 'retain-on-failure'` 方便除錯 +5. **測試隔離**:每個 test 獨立的 browser context,互不影響 + +### 常用 API 速查 + +| 用途 | API | +|------|-----| +| 開啟頁面 | `await page.goto(url)` | +| 等待載入 | `await page.waitForLoadState('networkidle')` | +| 填入文字 | `await page.fill(selector, text)` | +| 點擊 | `await page.click(selector)` | +| 下拉選擇 | `await page.selectOption(selector, value)` | +| 勾選 | `await page.check(selector)` | +| 截圖 | `await page.screenshot({ path: 'file.png' })` | +| 全頁截圖 | `await page.screenshot({ path: 'file.png', fullPage: true })` | +| 驗證文字可見 | `await expect(page.getByText('text')).toBeVisible()` | +| 取得文字 | `await element.textContent()` | +| 取得 URL | `page.url()` | +| 按角色找 | `page.getByRole('button', { name: 'Submit' })` | +| 按標籤找 | `page.getByLabel('Email')` | +| 按佔位符找 | `page.getByPlaceholder('Enter email')` | diff --git a/.claude/agents/verifier.md b/.claude/agents/verifier.md index 45cb4fd..ad5412f 100644 --- a/.claude/agents/verifier.md +++ b/.claude/agents/verifier.md @@ -147,18 +147,149 @@ grep -r "import.*from" src/ --include="*.ts" | head -20 ## 驗證結果處理 -### PASS(全部通過) -```bash -gh issue comment {sprint_issue} --body "✅ Sprint {N} 三維度驗證通過,可以 release" -``` +### PASS / WARNING(通過或輕微問題) + +驗證通過後,**自動產出 Sprint 工作日誌**: -### WARNING(有輕微問題) ```bash -gh issue comment {sprint_issue} --body "🟡 Sprint {N} 驗證有 warnings,建議修復但不阻塞 release。詳見 specs/verify-sprint-{N}.md" +gh issue comment {sprint_issue} --body "✅ Sprint {N} 三維度驗證通過" ``` +接著執行「Sprint 工作日誌」流程(見下方)。 + ### FAIL(有嚴重問題) 建立 bug issue 並通知: ```bash gh issue comment {sprint_issue} --body "🔴 Sprint {N} 驗證失敗,需修復後重新驗證。詳見 specs/verify-sprint-{N}.md" ``` + +--- + +## Sprint 工作日誌 + +驗證通過後(PASS 或 WARNING),自動產出 Sprint 工作日誌到 `specs/logs/sprint-{N}-log.md`。 + +### 日誌格式 + +```markdown +# Sprint {N} 工作日誌 + +## 基本資訊 + +| 項目 | 內容 | +|------|------| +| **Sprint** | Sprint {N} | +| **Milestone** | [Sprint {N}]({milestone_url}) | +| **日期** | {start_date} ~ {end_date} | +| **Epic** | [#{epic_number}]({epic_url}) | +| **Sprint Issue** | [#{sprint_issue_number}]({sprint_issue_url}) | + +## 完成功能 + +### F-{NNN}: {功能名稱} +- **Issue**: [#{number}]({issue_url}) +- **PR**: [#{pr_number}]({pr_url}) +- **Branch**: `feature/{N}-{name}` +- **Scenarios**: {passed}/{total} 通過 + +> {功能簡述,1-2 句話} + +(每個 feature 重複此區塊) + +## UI 設計 + +| 項目 | 內容 | +|------|------| +| **Design Issue** | [#{number}]({issue_url}) | +| **PR** | [#{pr_number}]({pr_url}) | +| **元件數量** | {N} 個 | + +交付元件: +- {Component 1} +- {Component 2} + +(無 UI 設計時省略此區塊) + +## 測試結果 + +| 測試類型 | 通過 | 失敗 | 結果 | +|----------|------|------|------| +| Unit Tests | {N} | {N} | ✅/❌ | +| API E2E Tests | {N} | {N} | ✅/❌ | +| Browser Tests (Playwright) | {N} | {N} | ✅/❌ | + +- **QA Issue**: [#{number}]({issue_url}) +- **Test PR**: [#{pr_number}]({pr_url}) +- **Test Report**: [`test/reports/sprint-{N}-test-report.md`]({link}) + +## Bug 修復 + +| Bug | Issue | Fix PR | 描述 | 嚴重度 | +|-----|-------|--------|------|--------| +| #{number} | [#{number}]({url}) | [#{pr}]({url}) | {description} | {severity} | + +(無 bug 時顯示「本 sprint 無 bug」) + +## 三維度驗證 + +| 維度 | 結果 | 詳情 | +|------|------|------| +| Completeness | ✅/❌ | {summary} | +| Correctness | ✅/❌ | {summary} | +| Coherence | ✅/❌ | {summary} | + +- **驗證報告**: [`specs/verify-sprint-{N}.md`]({link}) + +## 數據摘要 + +| 指標 | 數量 | +|------|------| +| Feature Issues | {N} | +| Pull Requests | {N} | +| Commits | {N} | +| Bug 修復 | {N} | +| 測試 Scenarios | {N} | + +## 所有相關 PR + +| PR | 標題 | 狀態 | +|----|------|------| +| [#{number}]({url}) | {title} | ✅ Merged | + +--- +*Generated by SpecFlow — {timestamp}* +``` + +### 產出流程 + +```bash +mkdir -p specs/logs +SPRINT="{current_sprint}" +SPRINT_NUM={N} + +# 收集資料(透過 GitHub API) +MILESTONE_URL=$(gh api repos/{owner}/{repo}/milestones --jq '.[] | select(.title=="'"$SPRINT"'") | .html_url') +FEATURES=$(gh issue list --label "feature" --milestone "$SPRINT" --state closed --json number,title,url) +BUGS=$(gh issue list --label "bug" --milestone "$SPRINT" --state closed --json number,title,url) +PRS=$(gh pr list --state merged --search "milestone:\"$SPRINT\"" --json number,title,url,mergedAt) + +# 產出日誌(使用上述格式) +# 寫入 specs/logs/sprint-{N}-log.md + +# Commit +git add specs/logs/ +git commit -m "docs: sprint ${SPRINT_NUM} work log + +Refs #{sprint_issue_number}" +git push + +# 在 Sprint issue 留言 +gh issue comment {sprint_issue} --body "📋 工作日誌:specs/logs/sprint-${SPRINT_NUM}-log.md" +``` + +### 日誌要求 + +1. **所有連結都要可點擊**:issue 和 PR 都附完整 URL +2. **格式嚴格一致**:每個 sprint 的日誌結構完全相同 +3. **資料從 GitHub API 取得**:不依賴本地狀態 +4. **Commit 到 repo**:日誌是 repo 的一部分 diff --git a/.claude/settings.json b/.claude/settings.json index 78da54e..1aafc21 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -63,7 +63,7 @@ "Bash(jq *)", "Bash(sort *)", "Bash(brew *)", - "Bash(agent-browser *)", + "Bash(npx playwright *)", "Bash(rm /tmp/*)", "Bash(rm -r /tmp/*)", "Bash(rm -rf /tmp/*)" diff --git a/.claude/skills/specflow:release/SKILL.md b/.claude/skills/specflow:release/SKILL.md index 3c3c146..940631a 100644 --- a/.claude/skills/specflow:release/SKILL.md +++ b/.claude/skills/specflow:release/SKILL.md @@ -1,147 +1,137 @@ --- name: specflow:release -description: 確認當前 sprint release,關閉 milestone,自動推進到下一個 sprint。必須 QA 完整測試通過才能 release。觸發關鍵字:"release", "發佈", "上線"。 +description: 部署 production。所有 sprint 完成後(或使用者決定部署時),執行 production 部署流程。觸發關鍵字:"release", "發佈", "上線", "deploy", "部署"。 user-invocable: true allowed-tools: Read, Bash, Agent -argument-hint: "[sprint編號]" +argument-hint: "[版本號]" --- -# Sprint Release 確認 +# Production 部署確認 -確認當前 sprint 的交付成果,關閉 milestone,自動推進下一個 sprint。 -**QA 完整測試必須通過才能 release。** +當所有 sprint 開發完成(或使用者決定部署當前進度),執行 production 部署。 +**Sprint 之間的推進是自動的,不需要手動 release。** ## 流程 -### 第一步:Release 前置檢查(Gate) +### 第一步:部署前置檢查(Gate) -**以下條件全部通過才能 release,任一未通過則阻擋:** +**以下條件全部通過才能部署,任一未通過則阻擋:** ```bash -SPRINT="{current_sprint}" BLOCK=false -# 1. QA issue 必須已關閉(代表完整測試通過) -OPEN_QA=$(gh issue list --label "qa" --milestone "$SPRINT" --state open --json number --jq 'length') -if [ "$OPEN_QA" -gt 0 ]; then - echo "❌ BLOCKED: QA issue 尚未關閉(完整測試未通過)" - BLOCK=true -fi - -# 2. 所有 feature issues 必須已關閉 -OPEN_FEATURES=$(gh issue list --label "feature" --milestone "$SPRINT" --state open --json number --jq 'length') -if [ "$OPEN_FEATURES" -gt 0 ]; then - echo "❌ BLOCKED: 有 $OPEN_FEATURES 個 feature issue 未關閉" - BLOCK=true +# 1. 確認沒有進行中的 sprint(所有 sprint milestone 已關閉) +OPEN_SPRINTS=$(gh api repos/{owner}/{repo}/milestones?state=open --jq '[.[] | select(.title | startswith("Sprint"))] | length') +if [ "$OPEN_SPRINTS" -gt 0 ]; then + echo "⚠️ 警告: 有 $OPEN_SPRINTS 個 sprint 尚未完成" + echo "(可繼續部署已完成的部分,或等待全部完成)" fi -# 3. 所有 bug issues 必須已關閉 -OPEN_BUGS=$(gh issue list --label "bug" --milestone "$SPRINT" --state open --json number --jq 'length') +# 2. 所有已關閉 sprint 的 test report 必須 ALL PASSED +for report in test/reports/sprint-*-test-report.md; do + [ -f "$report" ] || continue + if ! grep -q "ALL TESTS PASSED" "$report"; then + echo "❌ BLOCKED: $report 顯示有失敗的測試" + BLOCK=true + fi +done + +# 3. 所有驗證報告必須為 PASS +for verify in specs/verify-sprint-*.md; do + [ -f "$verify" ] || continue + if grep -q "🔴 FAIL" "$verify"; then + echo "❌ BLOCKED: $verify 驗證失敗" + BLOCK=true + fi +done + +# 4. 沒有 open 的 bug issues +OPEN_BUGS=$(gh issue list --label "bug" --state open --json number --jq 'length') if [ "$OPEN_BUGS" -gt 0 ]; then - echo "❌ BLOCKED: 有 $OPEN_BUGS 個 bug issue 未關閉" + echo "❌ BLOCKED: 有 $OPEN_BUGS 個 bug issue 未修復" BLOCK=true fi -# 4. 所有 PR 必須已合併 +# 5. main 分支沒有未合併的 PR OPEN_PRS=$(gh pr list --state open --json headRefName --jq '[.[] | select(.headRefName | startswith("feature/") or startswith("fix/") or startswith("test/"))] | length') if [ "$OPEN_PRS" -gt 0 ]; then echo "❌ BLOCKED: 有 $OPEN_PRS 個 PR 未合併" BLOCK=true fi - -# 5. Test Report 必須存在且為 ALL PASSED -if [ ! -f "test/reports/sprint-{N}-test-report.md" ]; then - echo "❌ BLOCKED: Test Report 不存在(QA 尚未執行完整測試)" - BLOCK=true -elif ! grep -q "ALL TESTS PASSED" "test/reports/sprint-{N}-test-report.md"; then - echo "❌ BLOCKED: Test Report 顯示有失敗的測試" - BLOCK=true -fi - -# 6. 驗證報告必須存在且為 PASS -if [ ! -f "specs/verify-sprint-{N}.md" ]; then - echo "❌ BLOCKED: 驗證報告不存在(請先執行 /specflow:verify)" - BLOCK=true -fi ``` -如果 `BLOCK=true`,列出所有阻擋項目,**不執行 release**。 -提示使用者需要先解決阻擋項目。 +如果 `BLOCK=true`,列出所有阻擋項目,**不執行部署**。 + +### 第二步:產出 Release 總報告 -### 第二步:產出 Sprint 報告 +彙整所有 sprint 的工作日誌,產出完整的 release 報告: ```bash gh issue comment {epic_number} --body "$(cat <<'BODY' -## 🚀 Sprint {N} Released - -### 交付功能 -- #{feature} F-001: {名稱} ✅ -- #{feature} F-002: {名稱} ✅ - -### 完整測試結果 -| 測試類型 | 結果 | -|----------|------| -| Unit Tests | ✅ passed | -| API E2E Tests | ✅ passed | -| Browser Tests | ✅ passed | -| 三維度驗證 | ✅ PASS | - -### 數據摘要 -| 項目 | 數量 | -|------|------| -| Feature Issues | X | -| Pull Requests | X | -| Bugs 修復 | X | - -### PRs -- #{pr} {title} +## 🚀 Production Release + +### 完成的 Sprint +(列出所有已完成的 sprint 及其工作日誌連結) + +### 交付功能總覽 +(從各 sprint 工作日誌彙整) + +### 測試結果 +所有 sprint 測試均 ✅ PASS +所有 sprint 驗證均 ✅ PASS + +### 工作日誌 +(列出所有 `specs/logs/sprint-{N}-log.md` 連結) BODY )" ``` -### 第三步:關閉 Sprint Milestone +### 第三步:建立 Release Tag ```bash -MILESTONE_NUMBER=$(gh api repos/{owner}/{repo}/milestones --jq '.[] | select(.title | startswith("Sprint {N}")) | .number') -gh api repos/{owner}/{repo}/milestones/$MILESTONE_NUMBER -X PATCH -f state="closed" -``` +VERSION="${ARGUMENTS:-v1.0.0}" +git tag -a "$VERSION" -m "Release $VERSION" +git push origin "$VERSION" -### 第四步:關閉 Sprint Issue +# 建立 GitHub Release +gh release create "$VERSION" \ + --title "Release $VERSION" \ + --notes "$(cat <<'NOTES' +## Release $VERSION -```bash -gh issue close {sprint_issue_number} --reason completed -``` +### 包含的 Sprint +(列出所有 sprint) -### 第五步:自動推進下一個 Sprint +### 主要功能 +(從工作日誌彙整) -```bash -NEXT_SPRINT=$(gh api repos/{owner}/{repo}/milestones --jq '[.[] | select(.state=="open") | select(.title | startswith("Sprint"))] | sort_by(.title) | .[0].title') +### 工作日誌 +(連結到 specs/logs/) +NOTES +)" ``` -如果有下一個 sprint: -1. 通知使用者:「Sprint {N} 已 release,自動推進到 {next_sprint}」 -2. 自動啟動 tech-lead → (engineer + qa 並行) 的背景流程 +### 第四步:關閉 Epic Issue -如果沒有下一個 sprint: -1. 通知使用者:「所有 sprint 已完成!專案交付完畢。」 +```bash +gh issue close {epic_number} --reason completed +``` -## Release Gate 總結 +## 部署 Gate 總結 | # | 檢查項目 | 必須 | 來源 | |---|----------|------|------| -| 1 | QA issue 已關閉 | ✅ | QA 完整測試通過後才會關閉 | -| 2 | 所有 feature issues 已關閉 | ✅ | PR merge 自動關閉 | -| 3 | 所有 bug issues 已關閉 | ✅ | fix PR merge 自動關閉 | -| 4 | 所有 PR 已合併 | ✅ | engineer + qa + design 的 PR | -| 5 | Test Report ALL PASSED | ✅ | `test/reports/sprint-{N}-test-report.md` | -| 6 | 驗證報告 PASS | ✅ | verifier 三維度檢查 | +| 1 | Test Report ALL PASSED | ✅ | 各 sprint 的 `test/reports/` | +| 2 | 驗證報告 PASS | ✅ | 各 sprint 的 `specs/verify-sprint-{N}.md` | +| 3 | 無 open bugs | ✅ | GitHub Issues | +| 4 | 所有 PR 已合併 | ✅ | GitHub PRs | -**缺任何一項都不能 release。** +**缺任何一項都不能部署。** Test Report 包含: - Docker Compose 環境狀態 - Unit Tests 結果 - API E2E Tests 結果 -- Browser Tests 結果(agent-browser) +- Browser Tests 結果(Playwright) - Scenario 覆蓋率 - 發現的問題清單 diff --git a/.claude/skills/specflow:start/SKILL.md b/.claude/skills/specflow:start/SKILL.md index 242794a..5d30832 100644 --- a/.claude/skills/specflow:start/SKILL.md +++ b/.claude/skills/specflow:start/SKILL.md @@ -76,7 +76,7 @@ Agent(subagent_type="engineer", run_in_background=true, isolation="worktree") 所有 engineer PR + QA test PR 完成後,QA 執行 sprint 完整測試: 1. 用 `dev/docker-compose.yml` 啟動完整服務環境 2. 對跑起來的服務執行 API e2e tests -3. 對跑起來的服務執行 agent-browser browser tests +3. 對跑起來的服務執行 Playwright browser tests 4. 停止服務 5. 全部通過 → Phase 5.5 6. 有失敗 → QA 建 bug issue(附截圖)→ engineer 修復 → 重測(最多 3 輪) @@ -97,9 +97,14 @@ Verifier 檢查: - WARNING → Phase 6(附帶建議) - FAIL → 建 bug issue → engineer 修復 → 重新驗證 -### Phase 6:Sprint 完成通知(使用者確認) +### Phase 6:自動產出工作日誌 + 關閉 Sprint -**只有 QA 完整測試通過 + 三維度驗證通過才會進到這一步。** +**QA 完整測試通過 + 三維度驗證通過後自動執行,不需使用者介入。** + +1. 產出 Sprint 工作日誌到 `specs/logs/sprint-{N}-log.md` +2. 在 Epic issue 留言 Sprint 報告 +3. 關閉 Sprint Milestone + Sprint Issue +4. 通知使用者 Sprint 完成摘要 ``` ✅ Sprint {N} 完成! @@ -110,17 +115,29 @@ Features: X | PRs: X | Bugs fixed: X 🧪 完整測試結果(docker compose 環境): Unit Tests: X passed API E2E Tests: X passed - Browser Tests: X passed (agent-browser) + Browser Tests: X passed (Playwright) ✅ Verify: PASS(Completeness + Correctness + Coherence) +📋 工作日誌:specs/logs/sprint-{N}-log.md 驗證報告:specs/verify-sprint-{N}.md -請使用 /specflow:release 確認發佈。 +``` + +### Phase 7:自動推進下一個 Sprint + +如果有下一個 sprint milestone,**自動啟動** Phase 3(tech-lead)→ Phase 4(engineer + qa)→ ... + +如果所有 sprint 都完成,通知使用者: +``` +🎉 所有 Sprint 完成!專案開發完畢。 +使用 /specflow:release 部署到 production。 ``` ## 重要 -- **只有 spec 討論和 release 確認需要使用者** +- **只有 spec 討論需要使用者互動** +- **Sprint 之間的推進完全自動**,不需手動 release +- `/specflow:release` 僅用於 production 部署確認 - `specs/` 目錄是 source of truth,所有 agent 從這裡讀取規格 - 依賴分析自動化,不需手動判斷 wave - 三維度驗證確保交付品質 diff --git a/.github/workflows/sprint-test.yml b/.github/workflows/sprint-test.yml index 10af642..7f1cb03 100644 --- a/.github/workflows/sprint-test.yml +++ b/.github/workflows/sprint-test.yml @@ -145,30 +145,26 @@ jobs: echo "$RESULT" exit $EXIT_CODE - - name: Install agent-browser + - name: Install Playwright + working-directory: test run: | - npm install -g agent-browser - agent-browser install + npm ci || npm install + npx playwright install --with-deps chromium - name: Run browser tests id: browser-tests continue-on-error: true + working-directory: test run: | export BASE_URL=http://localhost:3000 - mkdir -p test/screenshots - PASS=0; FAIL=0 - for script in test/browser/f*.sh; do - [ -f "$script" ] || continue - echo "Running: $script" - OUTPUT=$(bash "$script" 2>&1) || true - P=$(echo "$OUTPUT" | grep -c "✅" || true) - F=$(echo "$OUTPUT" | grep -c "❌" || true) - PASS=$((PASS + P)); FAIL=$((FAIL + F)) - echo "$OUTPUT" - done + mkdir -p screenshots + RESULT=$(npx playwright test --reporter=json 2>&1) || true + EXIT_CODE=$? + PASS=$(echo "$RESULT" | jq '.stats.expected // 0') + FAIL=$(echo "$RESULT" | jq '.stats.unexpected // 0') echo "pass=$PASS" >> "$GITHUB_OUTPUT" echo "fail=$FAIL" >> "$GITHUB_OUTPUT" - [ "$FAIL" -eq 0 ] + exit $EXIT_CODE - name: Stop services if: always() @@ -214,7 +210,7 @@ jobs: |----------|------| | Unit Tests | ${UNIT_OK} | | API E2E Tests | ${API_OK} | - | Browser Tests (agent-browser) | ${BROWSER_OK} | + | Browser Tests (Playwright) | ${BROWSER_OK} | $(if [ "$ALL_PASS" = "true" ]; then echo "## ✅ ALL TESTS PASSED — Ready for release" diff --git a/CLAUDE.md b/CLAUDE.md index 8cca70c..ce75ed7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,6 +38,7 @@ project/ │ ├── tech-survey.md │ ├── features/ │ ├── dependencies.md +│ ├── logs/ ← Sprint 工作日誌 │ └── changes/ ``` @@ -68,9 +69,13 @@ project/ │ verifier(三維度驗證) │ │ │ │ │ ┌─ FAIL → 修復 → 重驗 ──────────────────────┘ - │ └─ PASS → 通知使用者 + │ └─ PASS ↓ + │ 自動產出工作日誌 → 關閉 milestone + │ │ + │ ┌─ 有下一個 sprint → 自動啟動 + │ └─ 全部完成 → 通知使用者 │ -/specflow:release ──→ Release Gate → 關閉 milestone → 下一個 sprint +/specflow:release ──→ 部署 production(使用者確認後執行) ``` ## GitHub Issue 架構 @@ -102,8 +107,8 @@ Epic #1(索引 + 需求) |------|------|-----------| | `/specflow:init` | 初始化 labels + templates | 首次一次 | | `/specflow:start [主題]` | 啟動完整流程 | 對話確認 spec | -| `/specflow:verify` | 三維度驗證 sprint | 不需要 | -| `/specflow:release` | 確認 sprint release | 確認 | +| `/specflow:verify` | 三維度驗證 sprint | 不需要(自動) | +| `/specflow:release` | 部署 production | 確認部署 | ## 自動測試 @@ -118,7 +123,7 @@ Epic #1(索引 + 需求) ## 前置工具 - [Docker](https://docs.docker.com/get-docker/) + [Docker Compose](https://docs.docker.com/compose/install/) — 本地部署 + CI 測試 -- [agent-browser](https://github.com/vercel-labs/agent-browser) — `npm install -g agent-browser && agent-browser install` +- [Playwright](https://playwright.dev/) — `npm install -D @playwright/test && npx playwright install` ## 語言 diff --git a/README.md b/README.md index 94da442..d43be44 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,16 @@ claude - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) 已安裝 - [GitHub CLI (`gh`)](https://cli.github.com/) 已安裝並登入 - [Docker](https://docs.docker.com/get-docker/) + [Docker Compose](https://docs.docker.com/compose/install/) 已安裝(本地部署 + 測試用) -- [agent-browser](https://github.com/vercel-labs/agent-browser) 已安裝(QA 瀏覽器測試用) +- [Playwright](https://playwright.dev/) 已安裝(QA 瀏覽器測試用) - 目標 GitHub repo 已建立且已 `git init` ```bash # 確認 Docker docker --version && docker compose version -# agent-browser 安裝 -npm install -g agent-browser -agent-browser install +# Playwright 安裝 +npm install -D @playwright/test +npx playwright install ``` --- @@ -92,9 +92,13 @@ agent-browser install │ verifier(三維度驗證) │ │ │ │ │ ┌─ FAIL → bug issue → 修復 → 重驗 ──────────┘ - │ └─ PASS → 通知使用者 + │ └─ PASS ↓ + │ 自動產出工作日誌 → 關閉 milestone + │ │ + │ ┌─ 有下一個 sprint → 自動啟動 + │ └─ 全部完成 → 通知使用者 │ -/specflow:release ──→ 歸檔 specs → 關閉 milestone → 自動推進下一個 sprint +/specflow:release ──→ 部署 production(使用者確認後執行) ``` ### Phase 詳細說明 @@ -108,7 +112,8 @@ agent-browser install | 4b. 測試撰寫 | 在 `test/` 撰寫 e2e + browser tests | qa-engineer | 背景同步 | | 5. 測試驗證 | 執行 unit + e2e + browser tests,失敗建 bug issue(附截圖)| qa-engineer | 背景自動 | | 5.5 三維度驗證 | Completeness + Correctness + Coherence | verifier | 背景自動 | -| 6. Release | 確認 sprint 交付,推進下一 sprint | 自動 | **確認 release** | +| 6. 工作日誌 | 產出 sprint 工作日誌,關閉 milestone | verifier | 背景自動 | +| 7. 自動推進 | 啟動下一個 sprint,或通知使用者全部完成 | 自動 | 背景自動 | --- @@ -154,39 +159,42 @@ Spec 涵蓋:技術架構、API contract、data model、business rules、**WHEN | 層級 | 工具 | 時機 | 目的 | |------|------|------|------| | **API Tests** | test framework | 與 engineer 同時撰寫 | 驗證 API contract 正確性 | -| **Browser Tests** | [agent-browser](https://github.com/vercel-labs/agent-browser) | engineer 完成後執行 | 驗證完整 UI 流程和使用者體驗 | +| **Browser Tests** | [Playwright](https://playwright.dev/) | engineer 完成後執行 | 驗證完整 UI 流程和使用者體驗 | #### WHEN/THEN → Test 轉換 -| Scenario | API Test | Browser Test | -|----------|----------|-------------| -| GIVEN | test setup | `agent-browser open` + login | -| WHEN | API call | `agent-browser fill` / `click` | -| THEN | `expect()` | `agent-browser wait --text` + `screenshot` | +| Scenario | API Test | Browser Test (Playwright) | +|----------|----------|--------------------------| +| GIVEN | test setup | `page.goto()` + login | +| WHEN | API call | `page.fill()` / `page.click()` | +| THEN | `expect()` | `expect(page.getByText()).toBeVisible()` + `page.screenshot()` | -#### agent-browser 核心循環 +#### Playwright 核心範例 -```bash -agent-browser open "$URL" # 導航 -agent-browser wait --load networkidle # 等待載入 -agent-browser snapshot -i # 取得元素 @ref -agent-browser fill @e1 "value" # 互動 -agent-browser click @e2 # 點擊 -agent-browser wait --load networkidle # 等待結果 -agent-browser snapshot -i # 重新取得 @ref(DOM 變了!) -agent-browser screenshot "result.png" # 截圖 +```typescript +import { test, expect } from '@playwright/test'; + +test('example', async ({ page }) => { + await page.goto(BASE_URL); // 導航 + await page.waitForLoadState('networkidle'); // 等待載入 + await page.fill('[name="field"]', 'value'); // 互動 + await page.click('button[type="submit"]'); // 點擊 + await page.waitForLoadState('networkidle'); // 等待結果 + await expect(page.getByText('成功')).toBeVisible(); // 驗證 + await page.screenshot({ path: 'result.png' }); // 截圖 +}); ``` #### Bug Issue 附截圖 -測試失敗時,agent-browser 自動截圖,截圖會附在 bug issue 中: +API test 或 Browser test 任一失敗時,自動截圖並建立 bug issue 通知 engineer: ```markdown ## Screenshot(測試失敗時的畫面) ![Bug Screenshot](screenshot-url) -## 頁面狀態 -(agent-browser snapshot 輸出) +## Playwright Trace +下載 trace 進行詳細除錯 ``` 讓 engineer 不需重現就能直觀理解問題。 @@ -214,6 +222,8 @@ specs/ ├── overview.md # 專案概述 + 技術架構 ├── dependencies.md # 依賴圖譜(tech-lead 自動產生) ├── verify-sprint-{N}.md # 驗證報告(verifier 產生) +├── logs/ # Sprint 工作日誌 +│ └── sprint-{N}-log.md ├── features/ │ ├── f001-{name}.md # Feature spec(含 WHEN/THEN scenarios) │ ├── f002-{name}.md @@ -360,7 +370,7 @@ Wave 2(有依賴): | `/specflow:init` | 初始化 GitHub repo 的 labels 和 issue templates | 首次一次 | | `/specflow:start [主題]` | 啟動完整流程:spec 對話 → 自動到底 | 對話確認 spec | | `/specflow:verify` | 三維度驗證(Completeness + Correctness + Coherence)| 不需要 | -| `/specflow:release` | 確認 sprint release,自動推進下一個 | 確認發佈 | +| `/specflow:release` | 部署 production(所有 sprint 完成後) | 確認部署 | ### 進階指令 @@ -400,6 +410,8 @@ your-project/ │ ├── overview.md │ ├── dependencies.md # tech-lead 自動產生 │ ├── verify-sprint-{N}.md # verifier 產生 +│ ├── logs/ # Sprint 工作日誌 +│ │ └── sprint-{N}-log.md │ ├── features/ │ │ └── f{N}-{name}.md │ └── changes/ @@ -420,7 +432,7 @@ your-project/ │ │ ├── setup.ts │ │ ├── helpers.ts │ │ └── f{N}-{name}.test.ts -│ ├── browser/ # agent-browser UI tests +│ ├── browser/ # Playwright UI tests │ │ ├── setup.sh │ │ ├── helpers.sh │ │ └── f{N}-{name}.sh