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はマージ前の最終ゲートとして併用する
今回作る自動化の流れ
- Claude CodeがWebアプリのファイルを編集する
- Claudeが回答を終えようとするとStop Hookが起動する
- Hookが変更ファイルを確認し、画面に関係する変更がある場合だけPlaywrightを実行する
- テスト成功ならそのまま終了する
- テスト失敗なら終了コード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.tsのwebServerを使うと、テスト前にローカル開発サーバーを起動し、利用可能になるまで待機できます。
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成功の代わりにしないでください。
あわせて読みたい記事
- Claude Code Hooksの使い方
- Claude Code Skillsおすすめ
- Reactで外部APIを取得して表示する方法
- GitHub FlowとPull Request
- Codex GitHub Actionの使い方
よくある質問
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と人間のレビューに残してください。