AI 輔助 QA 工作流 2026 — Claude Code + Playwright + GitHub Actions 完整 SOP

「能不能用 AI 幫我寫測試?」這問題從 2023 年問到 2026 年,答案從「勉強能」變成「應該全用」。但 90% 的人沒得到該有的效益,因為工作流斷掉。

這篇給你一條從 spec → 測試 code → CI 部署的完整工作流,整合 Claude Code、Playwright、GitHub Actions,附實戰 prompt 範本、踩雷紀錄、失敗除錯流程。讀完就能落地。

為什麼 2026 是 AI 輔助 QA 的分水嶺

2024-2026 兩年最大的變化不是「AI 變強」,是 AI 進到開發流程內部。Claude Code 直接在 terminal 跑、Cursor 整合進 IDE、GitHub Copilot Workspaces 變成 task agent。

對 QA 來說,意味著:

工作項目 2023 純手寫 2026 AI 輔助
寫一隻 E2E case 30-60 min 5-10 min
測試資料生成 15-30 min 1-2 min
失敗 case 除錯 20-40 min 5-15 min
Page Object 建立 1-2 小時 10-20 min
Bug Report 撰寫 15 min 2-3 min

節省的時間應該拿去做: Exploratory testing、跨團隊溝通、設計測試策略、提升回歸率。不是拿去做更多 E2E case。

相關閱讀:AI 時代 QA 還有未來嗎 — 講 QA 職涯如何因應 AI 浪潮

工作流總覽

flowchart LR
    A[PM Spec / Story] --> B[Claude Code<br>讀 spec + 產生 test plan]
    B --> C[Claude Code<br>寫 Playwright test code]
    C --> D[本地 Playwright<br>跑通 + 修 selector]
    D --> E[git push]
    E --> F[GitHub Actions<br>跑 e2e + 截圖]
    F --> G{結果}
    G -->|通過| H[Merge to main]
    G -->|失敗| I[Claude Code 分析<br>失敗 log + 截圖]
    I --> C

整個流程不超過 30 分鐘就能完成一個 user story 從 spec 到 CI 通過的測試。手寫至少 2 小時。

環境準備

1. 安裝 Claude Code

# 全域安裝(推薦)
curl -fsSL https://claude.ai/install.sh | sh

# 或用 npm
npm install -g @anthropic-ai/claude-code

# 登入(需要 Anthropic 帳號)
claude login

不熟 Claude Code 的話先看 Claude Code 完整使用指南

2. 初始化 Playwright 專案

mkdir my-e2e && cd my-e2e
npm init -y
npm install -D @playwright/test
npx playwright install chromium firefox webkit

3. 建立 CLAUDE.md(最關鍵)

在專案 root 建 CLAUDE.md,告訴 Claude 你的測試規範:

# 專案測試規範

## 測試框架
- Playwright + TypeScript
- 用 page.locator() 不用 page.$()
- 用 data-testid 為主 selector,css 為輔

## Page Object Model
- pages/ 目錄放 Page Object class
- tests/ 目錄放 spec
- fixtures/ 放共用 fixture

## 命名
- spec 檔: feature-name.spec.ts
- POM class: FeatureNamePage

## 不要做
- 不要用 sleep / waitForTimeout(除非註明理由)
- 不要 inline selector,一律封裝進 POM
- 不要硬編碼測試資料

這份 CLAUDE.md 是整個工作流的靈魂。沒有它,Claude 每次都得從零猜你的規範。

Step 1: Spec → Test Plan

PM 給你一份 spec,先讓 Claude 整理成 test plan。

Prompt 範本

Read the following spec and generate a structured test plan.

Spec:
[貼 spec 內容]

Output format:
1. Happy path scenarios (e.g., 用戶成功登入)
2. Edge cases (e.g., 密碼輸入錯誤、帳號鎖定)
3. Error handling
4. Negative scenarios (e.g., XSS 注入)
5. Data validation (e.g., email 格式)

For each scenario, output:
- ID (TC-001, TC-002...)
- Title (一句話描述)
- Preconditions
- Steps
- Expected result
- Priority (P0/P1/P2)

Claude 4.7 通常會生出 15-25 個 scenarios。挑 P0/P1 寫成測試 case,P2 列入手動驗證或 backlog。

實戰範例

Spec:登入頁面,輸入正確 email + password 後跳轉到 /dashboard。錯誤超過 5 次帳號鎖定 15 分鐘。

Claude 產生:

TC-001 P0: 正確帳密成功登入
TC-002 P0: email 格式錯誤顯示 inline error
TC-003 P0: 密碼錯誤顯示「密碼錯誤」訊息
TC-004 P1: 連續 5 次錯誤後帳號鎖定
TC-005 P1: 鎖定後 15 分鐘可重新嘗試
TC-006 P1: 鎖定期間正確密碼仍無法登入
TC-007 P2: XSS 注入嘗試(email 欄位)
TC-008 P2: SQL injection 嘗試(password 欄位)
TC-009 P2: 空 form submit 顯示 required 提示
TC-010 P2: 1 個瀏覽器多 tab 同時登入

Step 2: 寫 Page Object

Claude 第二步:產生對應頁面的 Page Object。

Prompt 範本

Generate a Playwright Page Object class for the login page.

Page HTML (relevant parts):
[貼 DOM 結構或 testid]

Requirements:
- TypeScript
- Use data-testid as primary selector
- Encapsulate: navigate(), fillEmail(), fillPassword(), clickSubmit(),
  getErrorMessage(), isLockedOut()
- Add JSDoc comments
- Follow CLAUDE.md conventions

Claude 產出:

import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;
  readonly lockoutMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByTestId('login-email');
    this.passwordInput = page.getByTestId('login-password');
    this.submitButton = page.getByTestId('login-submit');
    this.errorMessage = page.getByTestId('login-error');
    this.lockoutMessage = page.getByTestId('login-lockout');
  }

  async navigate() {
    await this.page.goto('/login');
  }

  async fillEmail(email: string) {
    await this.emailInput.fill(email);
  }

  async fillPassword(password: string) {
    await this.passwordInput.fill(password);
  }

  async clickSubmit() {
    await this.submitButton.click();
  }

  async getErrorMessage(): Promise<string> {
    return await this.errorMessage.textContent() ?? '';
  }

  async isLockedOut(): Promise<boolean> {
    return await this.lockoutMessage.isVisible();
  }

  async login(email: string, password: string) {
    await this.fillEmail(email);
    await this.fillPassword(password);
    await this.clickSubmit();
  }
}

注意:Claude 預設用 getByTestId(),這是 Playwright 推薦的做法。如果你的網站沒裝 testid,先請工程師加,不要硬用 text=css=。詳見 Page Object Model

Step 3: 寫測試 spec

把 Page Object 跟測試案例丟給 Claude,產出 spec:

Prompt 範本

Generate Playwright spec for the login feature.

Test cases (from test plan):
TC-001 P0: 正確帳密成功登入
TC-002 P0: email 格式錯誤顯示 inline error
TC-003 P0: 密碼錯誤顯示「密碼錯誤」訊息

Page Object: LoginPage (已寫好)
Base URL: https://staging.example.com

Requirements:
- Use test.describe + test pattern
- Use fixtures for LoginPage instance
- Test data from fixtures/users.ts ([email protected] / Test1234)
- No inline selectors
- Add expect() with descriptive messages

Claude 產出:

import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { validUser, invalidEmailFormat } from '../fixtures/users';

test.describe('Login Feature', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.navigate();
  });

  test('TC-001: 正確帳密成功登入後跳轉 dashboard', async ({ page }) => {
    await loginPage.login(validUser.email, validUser.password);
    await expect(page).toHaveURL(/\/dashboard/);
    await expect(page.getByTestId('user-menu')).toBeVisible();
  });

  test('TC-002: email 格式錯誤顯示 inline error', async () => {
    await loginPage.fillEmail(invalidEmailFormat);
    await loginPage.passwordInput.blur();
    const error = await loginPage.getErrorMessage();
    expect(error).toContain('email 格式錯誤');
  });

  test('TC-003: 密碼錯誤顯示提示訊息', async () => {
    await loginPage.login(validUser.email, 'wrongpass');
    const error = await loginPage.getErrorMessage();
    expect(error).toContain('密碼錯誤');
  });
});

Step 4: 本地跑通

npx playwright test --headed

Claude 生的測試 80% 第一次能跑,剩 20% 通常是: - selector 跟實際 DOM 不符(請 Claude 重看 DOM) - 時序問題(加 await expect(...).toBeVisible() 取代 waitForTimeout) - testid 沒裝(請工程師補)

相關閱讀:Flaky Test 除錯指南

Step 5: GitHub Actions CI

.github/workflows/e2e.yml 也讓 Claude 寫:

name: E2E Tests

on:
  pull_request:
    branches: [main, develop]
  schedule:
    - cron: '0 2 * * *'  # 每天 02:00 UTC 跑

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium firefox

      - name: Run Playwright tests
        run: npx playwright test --reporter=github
        env:
          BASE_URL: ${{ secrets.STAGING_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASS: ${{ secrets.TEST_PASS }}

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: failure-screenshots
          path: test-results/
          retention-days: 7

延伸閱讀:GitHub Actions + Playwright 完整實戰

Step 6: 失敗除錯流程

CI 失敗時的 SOP:

1. 下載 artifact

gh run download <run-id> --name failure-screenshots
gh run download <run-id> --name playwright-report

2. 把失敗 log 丟給 Claude

The following Playwright test failed in CI. Analyze the failure:

[貼 test code]

Error log:
[貼 error message + stack trace]

Screenshot description: [描述截圖看到什麼]

Possible causes:
1. ...
2. ...

Suggested fix:

Claude 4.7 對失敗分析準確率約 75%。常見幾類: - selector 變了:UI 改版但 testid 沒同步 - 時序問題:API response 慢 → 用 expect().toBeVisible({ timeout: 10_000 }) - 資料污染:前一個 test 沒清乾淨 → 加 test.afterEach() cleanup - 環境差異:CI 跑 headless 跟本地 headed 行為不同

3. 修完重跑

npx playwright test login.spec.ts --headed --debug

進階:Claude Code Agent Mode

Claude Code 2026 開始支援 Agent Mode,可以自動跑測試、看失敗、修 code、重跑循環,直到測試通過。

claude --agent "Run the e2e tests, fix any failures, and commit."

Agent 模式適合: - 大量機械性的失敗修正(如 selector 大規模重命名) - 跨多檔案的調整(如 API endpoint 整批改名)

不適合: - 邏輯複雜的測試案例設計 - 涉及商業決策的 trade-off

工作流 ROI 計算

跑 3 個月實際數據(QA 個人經驗):

項目 純手寫 AI 輔助 節省
寫 1 隻 E2E case 45 min 8 min 82%
寫 Page Object 1.5 hr 12 min 87%
失敗除錯 30 min 8 min 73%
Test plan from spec 1 hr 5 min 92%
每週 saved 約 12-15 hr

省下的時間建議拿去: 1. 寫更扎實的 spec review(Spec Review checklist) 2. Exploratory testing(Exploratory Testing Playbook) 3. 規劃測試策略(Test Strategy vs Plan vs Case) 4. 學新工具 / 進修

常見地雷

1. 完全不寫 CLAUDE.md

Claude 每次都從零猜,產出風格不一致、難維護。第一週寫好 CLAUDE.md,省下後面 99% 重複功夫

2. 不檢視 AI 產出直接 commit

Claude 偶爾會產出「看起來對但實際邏輯錯」的 code。Code review 仍是 QA 自己的責任。

3. 把所有 E2E 都讓 AI 寫

高商業價值的核心流程(如金流、登入、權限)建議手寫 + AI 校對,純機械性回歸再讓 AI 全包。

4. 沒有 Failure Loop

CI 失敗只看 log 不收集 artifact,下次失敗又從頭追。設好 artifact retention + Slack 通知,失敗 1 小時內處理

5. 忽略測試報告品質

AI 寫 expect 訊息常太簡略。要求 expect(error).toContain('密碼錯誤', '當密碼錯誤時應顯示中文訊息') 這種自描述的 assertion,失敗時看 log 就知道哪裡壞。

一週導入計畫

動作
Day 1 安裝 Claude Code + Playwright,建 CLAUDE.md
Day 2 挑 1 個 user story 試完整流程:spec → POM → spec
Day 3 跑本地通過,記錄踩雷與 Claude 弱點
Day 4 設 GitHub Actions CI,跑通第一次 PR e2e
Day 5 把上週手寫的 5 個 case 全部讓 Claude 重寫一次比對
Day 6 整理 prompt 範本,存進 prompts/ 目錄
Day 7 內部分享,讓全組換上工作流

結語

AI 不會取代 QA,但用 AI 的 QA 會取代不用 AI 的 QA

這個工作流不是一勞永逸,每 3-6 個月隨 Claude / Playwright 版本更新調整。重點是把「機械性重複工作」交給 AI,把「需要判斷的工作」留給人。

時間省下來不是用來寫更多 case,是用來提升整體測試品質與策略視角。這才是 QA 在 2026 該有的樣子。


相關閱讀: - AI 時代 QA 還有未來嗎 - Playwright Starter 完整入門 - GitHub Actions + Playwright 實戰 - Page Object Model 深度 - Flaky Test 除錯 - Claude Code 完整使用指南(9niche) - AI 聊天機器人比較(9niche)