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(測試失敗時的畫面)

-## 頁面狀態
-(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