AI活用

Claude Code HooksとPlaywrightでWebアプリのE2Eテストを自動化する方法

Claude CodeのStop HookとPlaywrightを組み合わせ、Webアプリの変更後にE2Eスモークテストを自動実行する設定を解説。無限ループ防止、対象ファイル絞り込み、CIとの使い分けも扱います。

この記事の目次
  1. 結論:Stop Hookでスモークテストを実行し、失敗時だけClaudeへ修正を戻す
  2. 今回作る自動化の流れ
  3. 前提環境
  4. Playwrightをインストールする
  5. インストールを確認する
  6. Webアプリ用のスモークテストを作る
  7. Playwrightから開発サーバーを起動する
  8. Stop Hookのスクリプトを作る
  9. 実行権限を付ける
  10. スクリプトを単体で確認する
  11. Stop Hookを設定する
  12. 実際にClaude Codeで確認する
  13. stop_hook_activeが必要な理由
  14. PostToolUseとStopの使い分け
  15. テストを重くしすぎない工夫
  16. Webアプリで自動化できる確認例
  17. フォームの送信
  18. ログインと権限制御
  19. レスポンシブ表示と主要導線
  20. アクセシビリティの基本
  21. CIにもPlaywrightを残す
  22. あわせて読みたい記事
  23. よくある質問
  24. まとめ
  25. 参考リンク

Claude Code HooksとPlaywrightを組み合わせると、Webアプリの変更後にブラウザテストを自動実行し、失敗した場合はClaudeへ修正を続けさせられます。

この記事では、短いE2Eスモークテスト、Stop Hook、無限ループ防止、変更ファイルの絞り込み、GitHub Actionsとの使い分けまで実装します。

情報確認日:2026年7月11日(日本時間)

結論:Stop Hookでスモークテストを実行し、失敗時だけClaudeへ修正を戻す

この記事の結論

  • PlaywrightはWebアプリの画面操作をブラウザで再現し、表示・遷移・フォームなどを確認できる
  • Claude CodeのStop Hookを使うと、Claudeが作業を終えようとするたびにE2Eテストを確認できる
  • 対象ファイルが変更されたときだけスモークテストを走らせ、毎回の重い全テストを避ける
  • stop_hook_activeを確認し、失敗→継続→再停止の無限ループを防ぐ
  • ローカルHookは早いフィードバック、GitHub Actionsはマージ前の最終ゲートとして併用する

今回作る自動化の流れ

  1. Claude CodeがWebアプリのファイルを編集する
  2. Claudeが回答を終えようとするとStop Hookが起動する
  3. Hookが変更ファイルを確認し、画面に関係する変更がある場合だけPlaywrightを実行する
  4. テスト成功ならそのまま終了する
  5. テスト失敗なら終了コード2で失敗内容をClaudeへ返し、修正を続けさせる

Stop Hookはセッション全体の最後だけでなく、Claudeが各ターンを終えようとするときに実行されます。そのため、全E2Eを無条件に起動すると待ち時間と負荷が増えます。この記事では短いスモークテストと変更ファイルの絞り込みを使います。

前提環境

  • Node.jsとnpmを使うWebプロジェクト
  • Claude Codeがプロジェクトで動作している
  • Gitリポジトリとして初期化されている
  • jqが利用できるmacOS・Linux・WSL環境
  • 開発サーバーをローカルで起動できる

Windows PowerShellだけで運用する場合は、HookスクリプトをPowerShellへ置き換えてください。Claude CodeのHookはシェル指定にも対応しますが、この記事の例はBashとjqを使います。

Playwrightをインストールする

npm init playwright@latest

対話中にTypeScript、テストディレクトリ、GitHub Actions追加、ブラウザのインストールなどを選びます。既存プロジェクトでは、生成された設定と現在の構成が衝突しないか差分を確認します。

インストールを確認する

npx playwright test
npx playwright show-report

最初は生成されたサンプルが成功することを確認します。Hookを追加する前に、手動実行で失敗しているテストを直してください。

Webアプリ用のスモークテストを作る

すべての画面や境界条件をStop Hookで毎回確認する必要はありません。トップページが開く、主要ナビゲーションが使える、重大なコンソールエラーがない、といった短いスモークテストを用意します。

import { expect, test } from "@playwright/test";

test("top page and primary navigation work", async ({ page }) => {
  const consoleErrors: string[] = [];

  page.on("console", (message) => {
    if (message.type() === "error") {
      consoleErrors.push(message.text());
    }
  });

  await page.goto("/");
  await expect(page).toHaveTitle(/.+/);

  const main = page.getByRole("main");
  await expect(main).toBeVisible();

  const firstLink = main.getByRole("link").first();
  if (await firstLink.isVisible()) {
    await firstLink.click();
    await expect(page).not.toHaveURL("about:blank");
  }

  expect(consoleErrors).toEqual([]);
});

外部広告、解析タグ、ブラウザ拡張などがコンソールエラーを出すサイトでは、無条件に全エラーを失敗へせず、アプリ由来のエラーだけを絞り込んでください。

Playwrightから開発サーバーを起動する

playwright.config.tswebServerを使うと、テスト前にローカル開発サーバーを起動し、利用可能になるまで待機できます。

import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  timeout: 30_000,
  use: {
    baseURL: "http://127.0.0.1:3000",
    trace: "retain-on-failure",
    screenshot: "only-on-failure",
  },
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
  webServer: {
    command: "npm run dev -- --host 127.0.0.1 --port 3000",
    url: "http://127.0.0.1:3000",
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

Next.js、Vite、Nuxtなどによって起動コマンドとポートは異なります。プロジェクトの実際のコマンドに置き換えてください。Hookから本番URLへアクセスさせず、ローカルまたはテスト専用環境を使います。

Stop Hookのスクリプトを作る

.claude/hooks/run-e2e.shを作成します。Claude CodeはHookへJSONを標準入力で渡すため、最初に読み取ります。

        #!/usr/bin/env bash
        set -u

        INPUT="$(cat)"

        # Stop HookがClaudeを継続させた直後の再実行では、
        # もう一度ブロックせず停止を許可する。
        if [ "$(printf '%s' "$INPUT" | jq -r '.stop_hook_active // false')" = "true" ]; then
          exit 0
        fi

        cd "${CLAUDE_PROJECT_DIR:-.}"

        # Playwrightを使わないプロジェクトでは何もしない。
        if [ ! -f "playwright.config.ts" ] && [ ! -f "playwright.config.js" ]; then
          exit 0
        fi

        # Git管理外や差分なしの場合はスキップする。
        if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
          exit 0
        fi

        CHANGED_FILES="$(git diff --name-only HEAD 2>/dev/null || true)"
        if [ -z "$CHANGED_FILES" ]; then
          exit 0
        fi

        # UIやE2Eテストに関係する変更だけを対象にする。
        if ! printf '%s
' "$CHANGED_FILES" | grep -Eq           '^(src|app|pages|components|public|tests|e2e)/|playwright\.config\.(ts|js)$'; then
          exit 0
        fi

        echo "Running Playwright smoke test..."

        if ! npx playwright test tests/smoke.spec.ts           --project=chromium           --reporter=line; then
          echo "Playwright smoke test failed. Inspect the failing step, trace, and screenshot, then fix the implementation before finishing." >&2
          exit 2
        fi

        exit 0

実行権限を付ける

chmod +x .claude/hooks/run-e2e.sh

スクリプトを単体で確認する

printf '%s' '{"stop_hook_active":false}' |           CLAUDE_PROJECT_DIR="$PWD" .claude/hooks/run-e2e.sh

Hookへ登録する前に、成功時は0、テスト失敗時は2で終了することを確認します。

Stop Hookを設定する

プロジェクトの.claude/settings.jsonへ追加します。既存設定がある場合はhooksを上書きせず統合してください。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-e2e.sh\"",
            "timeout": 300
          }
        ]
      }
    ]
  }
}

設定ファイルはJSONなのでコメントや末尾カンマを入れられません。Claude Codeの/hooks画面は現在のHook確認に使えますが、編集は設定ファイルで行います。

実際にClaude Codeで確認する

トップページのカードコンポーネントを変更してください。
既存のアクセシビリティを維持し、Playwrightのスモークテストが通ることを確認してから終了してください。

Claudeが作業を終えようとするとHookが起動します。テストが失敗した場合、標準エラーへ出した説明がClaudeへ返り、次の修正へ進みます。成功した場合は通常どおり回答が終わります。

stop_hook_activeが必要な理由

Stop Hookが失敗を返すと、Claudeは作業を継続します。その後もう一度終了しようとしたときにもStop Hookが起動します。失敗原因が環境依存などで直せない場合、同じ失敗を繰り返す可能性があります。stop_hook_activeがtrueのときに停止を許可することで、無限ループを避けます。

この例は「1回フィードバックしたら次の停止を許可する」保守的な構成です。必ず成功するまで続けたい場合でも、連続ブロック上限、タイムアウト、失敗理由、手動停止方法を用意してください。

PostToolUseとStopの使い分け

イベント 実行タイミング 向いている確認 避けたいこと
PostToolUse EditやWriteの直後 Prettier、対象ファイルのLint、短い型確認 毎編集後のフルE2E
Stop Claudeがターンを終えようとするとき スモークテスト、ビルド、変更全体の確認 長時間の全ブラウザ・全端末テスト
GitHub Actions pushやPull Request 全E2E、複数ブラウザ、成果物保存 ローカルの高速フィードバックだけに使う

ファイル保存のたびに整形したい場合はPostToolUse、複数の変更がまとまった後に画面を確認したい場合はStopが適しています。マージを止める最終判断はCIへ置きます。

テストを重くしすぎない工夫

  • Stop HookはChromiumの短いスモークテストだけにする
  • 変更ファイルを見て、UIに関係しない変更ならスキップする
  • ログインが必要なテストは保存済み認証状態や専用テストユーザーを使う
  • 失敗時だけtraceとscreenshotを保存する
  • 全ブラウザ、モバイル、長いシナリオはCIで実行する
  • APIや外部サービスはモックまたはステージングへ向ける
  • テストサーバーと子プロセスが終了する設定を確認する

Webアプリで自動化できる確認例

フォームの送信

必須項目、エラー表示、二重送信防止、成功後の遷移を確認します。メール送信や決済はテスト環境またはモックへ向けます。

ログインと権限制御

未ログイン時のリダイレクト、権限別の表示、ログアウトを確認します。実ユーザーの認証情報をHookへ置かず、専用アカウントと最小権限を使います。

レスポンシブ表示と主要導線

CI側でDesktop Chromeとモバイル幅を分け、主要ナビゲーションやモーダルが操作できることを確認します。Stop Hookは1つの代表環境へ絞ります。

アクセシビリティの基本

見出し、ラベル、ボタン名、キーボード操作などをPlaywrightのroleベースLocatorで確認します。見た目だけをCSSセレクターで追うより、UIの意味をテストへ反映できます。

CIにもPlaywrightを残す

ローカルHookを通過しても、環境差やHookのスキップ条件によって見逃しは起こります。GitHub Actionsでは依存を固定し、必要なブラウザを導入し、全E2Eをマージ条件へ含めます。

name: e2e

on:
  pull_request:
  push:
    branches: [main]

jobs:
  playwright:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --project=chromium
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

Actionのメジャーバージョン、Node.js、ブラウザはプロジェクトのサポート方針に合わせて固定・更新してください。AIのHookが成功したことをCI成功の代わりにしないでください。

あわせて読みたい記事

よくある質問

Stop HookはClaude Codeのセッション終了時に1回だけ動きますか?

いいえ。Claudeが各ターンを終えようとするときに動きます。重い処理を無条件に登録せず、変更対象とテスト範囲を絞ります。

テスト失敗時にClaudeへ修正を続けさせるには?

command Hookが説明を標準エラーへ出して終了コード2で終わると、StopをブロックしてClaudeへ理由を返せます。

Playwrightの全テストをStop Hookで実行してよいですか?

小規模なら可能ですが、通常は短いスモークテストだけにします。全ブラウザや長いシナリオはCIへ分けた方が待ち時間と安定性を管理しやすくなります。

Hookが何度も同じテストを実行します

stop_hook_activeを確認し、2回目の継続では停止を許可します。変更ファイル判定とタイムアウトも確認してください。

PostToolUseでPlaywrightを実行してはいけませんか?

実行できますが、EditやWriteのたびに起動すると非常に重くなります。PostToolUseは整形や対象ファイルのLint、Stopはまとまったスモークテストに向きます。

まとめ

Claude Code HooksとPlaywrightを組み合わせると、Webアプリの変更後にブラウザ操作を自動確認し、失敗内容をClaudeへ戻せます。Stop Hook、変更ファイルの絞り込み、短いスモークテストを使うことで、対話中のフィードバックを現実的な時間に抑えられます。

Hookは便利ですが、環境依存の失敗や無限ループを考慮する必要があります。stop_hook_active、タイムアウト、テスト専用環境を設定し、最終的な品質ゲートはGitHub Actionsと人間のレビューに残してください。

参考リンク