QR Send

スマホとPCのChrome間で、URLやテキストをQRコード1回のペアリングで送り合える自作Chrome拡張。アカウント不要・無料。

使ってみる →

Claude CodeでE2Eテストを自動生成させようとしてルールとhooksをブチこみまくったら、逆に仕事してくれなくなった話

Selenium・自動化

今年の初夏くらいから、かなり夢中でClaude Codeを触っていました。作りたかったのは、Playwright + TypeScriptのE2Eテストを、AIにかなりの部分まで自動生成させる仕組みです。

最初にやったのは、とにかく失敗を減らすことでした。過去に踏んだエラー、プロジェクト固有のルール、テスト実行手順、レビュー観点、禁止事項。そういうナレッジをCLAUDE.mdやhooksにどんどん詰め込んで、二度と同じミスをしないClaude Codeを作ろうとしていました。

以下は「仮にこういう構成だったとしたら」という体で書いています。具体的な設定例はあくまで再現ですが、起きたこと・学んだことの大筋はだいたい合っています。

Claude Codeには、プロジェクトごとの永続的な指示を書くCLAUDE.md、作業の前後でコマンドを走らせるhooks、繰り返し使う手順を切り出せるskills、専門化したsubagentsがあります。

hooksを増やせば増やすほど安全になる、と思っていた

hooksは.claude/settings.jsonに書けます。仮に、EditやWriteのあとにテストを走らせるhooksを作ったとします。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
            "args": [],
            "async": true
          }
        ]
      }
    ]
  }
}

これでEditWriteのあとに、テストやlintを走らせることができます。公式ドキュメントでもhooksはPreToolUsePostToolUseStopなどのイベントに対して設定できると書かれています。

最初の設計では、Claude Codeが間違えそうなところを全部hooksで止めようとしました。危ないコマンドを止める。テスト未実行なら止める。ドキュメント未更新なら止める。特定のファイルを触ったら追加チェックを走らせる。

“Unlike CLAUDE.md instructions which are advisory, hooks are deterministic and guarantee the action happens.”
“Use hooks for actions that must happen every time with zero exceptions.”
Best practices for Claude Code

つまり公式の位置づけでは、CLAUDE.mdはあくまで「助言」で、hooksは「必ず実行される決定的な処理」用です。この時点で私は、hooksを「Claude Codeの判断ミスを防ぐガードレール」くらいの気持ちで使っていたので、そもそも用途の格が違ったのかもしれません。

本当にhooksを増やせば増やすほど安全になるのでしょうか。実際にやってみると、むしろ逆でした。

hooksで縛りすぎると、仕組みが仕組みを止める

E2Eテスト生成では、Claude Codeにこういう流れを任せたいわけです。

npx playwright test
npx playwright show-report
npx playwright codegen http://localhost:3000

Playwright側にも公式のtest generatorがあり、npx playwright codegenでブラウザ操作からテストコードを生成できます。Playwrightはlocatorを選ぶときにrole、text、test idなどを優先していて、壊れにくいlocatorを作ろうとしてくれます。

ただ、AIにE2Eを作らせる場合、単にcodegenの出力を保存すれば終わりではありません。実際には、生成されたテストを読んで、不要な待機を消し、locatorを調整し、expect(locator).toBeVisible()のようなweb-first assertionsに寄せ、再実行して落ちる原因を調べる必要があります。

ここでhooksを強くしすぎると、Claude Codeが探索する前に止まってしまいます。一時的に落ちるテストを観察したいだけなのに、失敗した時点で制御が返ってこない。修正途中のコードに厳密なチェックが走って、まだ判断材料が揃っていない段階で失敗と見なされてしまいます。

“Claude Code overrides the hook and ends the turn after 8 consecutive blocks.”
Best practices for Claude Code

Stop hookで無限に足止めできてしまうと危険なので、公式側も「8回連続でブロックしたら強制的にターンを終わらせる」という上限を用意しているくらいです。裏を返せば、それだけhooksで縛りすぎるのはAnthropic自身も織り込み済みの落とし穴、ということだと思います。

要するに、AIの作業には失敗を観察する時間が必要でした。これに気づくまで、けっこう遠回りしています。

公式の枠組みに戻したら、横展開できるようになった

転機になったのは、作った仕組みの良い部分だけを別リポジトリに横展開しようとしたことでした。

そこで一度、独自ルールの山を分解しました。静的な知識はCLAUDE.mdに置く。繰り返しの手順はskillにする。危険な操作や必ず機械的に検査したい部分だけhooksにする。探索や判断はClaude Codeに任せる。

この整理をしたら、むしろ今までできなかったことができるようになりました。

“Keep it concise. For each line, ask: ‘Would removing this cause Claude to make mistakes?’ If not, cut it. Bloated CLAUDE.md files cause Claude to ignore your actual instructions!”
Best practices for Claude Code

「この1行を消したらClaude Codeがミスをするか?」を基準に削れ、と書いてあります。太らせたCLAUDE.mdは、逆に本当に守ってほしい指示ごと無視されるとまで明言されているんですね。ルールを詰め込むほど安全になる、というのは思い込みだったわけです。

公式ドキュメントには、何を書いて何を書かないかの目安も表になっています。

書く   : Claudeが読めば分かるコードの説明はしない。
       Claudeが推測できないbashコマンド、標準と違うコード規約、
       テストの回し方、リポジトリ独自の作法、環境の癖。
書かない: 標準的な言語の作法、詳しいAPI仕様(リンクで十分)、
       頻繁に変わる情報、長い説明やチュートリアル。

たとえば、E2E自動生成用のskillには、こんな手順だけを書いたとしましょう。

1. 既存のPlaywright構成を読む
2. 対象画面の主要なユーザーフローを洗い出す
3. role / text / test id ベースのlocatorを優先する
4. waitForTimeoutを避け、web-first assertionsを使う
5. 生成後に npx playwright test で確認する
6. 失敗した場合は trace / screenshot / report を見て修正する

ちなみに、以前は.claude/commands/にコマンドを置いて/コマンド名で呼ぶやり方もありましたが、これは今はレガシー扱いで、公式には.claude/skills/<name>/SKILL.mdへの統合が推奨されています。動くには動くのですが、新しく作るならskillsに寄せておいたほうがよさそうです。

一方で、hooksは最小限にします。「.envを読みに行くような危険操作を止める」「編集後に軽いlintだけ非同期で走らせる」「完了時にテスト結果のサマリを残す」くらいで十分でした。

Claude Codeはclaude -pを使うと非対話で実行でき、--output-format jsonstream-jsonで結果を構造化して扱えます。CIやスクリプトに組み込む場合は、公式ドキュメントでも--bareによってhooks、plugins、auto memory、CLAUDE.mdなどの読み込みを抑える方法が紹介されています。これ、意外と使えます。

横展開したいなら、全部入りの巨大な個人設定ではなく、次の3層に分けるのがよかったです。

CLAUDE.md  : プロジェクトの事実
skills     : 再利用できる作業手順
hooks      : 機械的に止める・記録する・検査する処理

役割さえ分けておけば、別のリポジトリに持っていくときもこの3つをコピーするだけです。簡単ですね。

AIの賢さに依存する部分を、あえて残す

今回いちばん大きかった教訓は、AIを完全に縛るより、AIが判断できる枠組みを整えるほうが強いということでした。

これは、最近のソフトウェア工学系のLLM研究とも感覚が近いです。LLMによるテスト生成の研究では、単発生成よりも、コンテキスト付与・実行・失敗観察・修正というループが重要だと整理されています。

“Letting Claude jump straight to coding can produce code that solves the wrong problem.”
“Give Claude a check it can run: tests, a build, a screenshot to compare. It’s the difference between a session you watch and one you walk away from.”
Best practices for Claude Code

公式が勧めているのは、いきなりコードを書かせず「探索→計画→実装→コミット」の順に進めることと、自分で判定できる検証手段(テストやビルド)を渡すことでした。hooksで縛るより、探索の余地とチェック手段を渡すほうが本筋だった、ということなんだと思います。

現時点のAIエージェントは、まだ最後を人間が確かめる必要があります。特にE2Eテストは、通ることと意味のある検証になっていることは別物です。

ログインできた、ボタンが押せた、画面が表示された。それだけでは、本当に守りたいユーザーフローを守れているとは限りません。

だから、自分の中ではこう整理しました。

AIに任せること:
- 既存コードの読み取り
- テスト候補の生成
- locatorの初期選定
- 失敗ログからの原因仮説
- 修正案の反復

人間が見ること:
- そのテストが本当に価値あるユーザーフローか
- assertionが弱すぎないか
- 失敗を握りつぶしていないか
- 仕組みがメンテナンス可能か

完璧な自動化を目指すより、AIが賢くなるほど自然に伸びる構造にしておく。これがかなり大事だったと思います(このあたりの線引きは、正直まだ試行錯誤中です)。

ドキュメントも、AI前提で書き直せる

もうひとつ感じたのは、ドキュメントの役割が変わってきたことです。

人間だけで運用するドキュメントは、だいたい古くなります。最初は丁寧に書くのですが、実装が変わり、コマンドが変わり、そのうち誰も更新しなくなる。私も何度もこれをやらかしています。

しかし、Claude Codeのようなツールを前提にすると、ドキュメントは人間が読む説明だけでなく、AIが次回の作業で参照する実行時コンテキストになります。

だから、長い思想文よりも、短く、具体的で、検証可能な事実のほうが効きます。

このリポジトリのE2EはPlaywright Testを使う。
テスト実行は npx playwright test。
locatorはrole / text / test idを優先する。
waitForTimeoutは原則使わない。
失敗時はHTML reportとtraceを確認してから修正する。

このくらいの粒度のほうが、Claude Codeには伝わりやすいです。そして、人間にとってもメンテナンスしやすいです。

今のところ、こう落ち着いています

今回の結論は、AIに全部やらせることではありません。逆に、AIを信用しないで全部をルールで縛ることでもありません。

AIが得意な探索と反復を活かす。人間が見るべき意味と責任は残す。その間に、CLAUDE.md・skills・hooks・Playwrightの実行結果を置く。

これが、今のところ自分の中でいちばんしっくりきているClaude CodeでのE2E自動生成の形です。同じように「hooksを盛りすぎて逆に動かなくなった」という人がいたら、コメントで教えてもらえると嬉しいです。

hooksそのものの使い方をもう少し詳しく知りたい方はClaude Codeのhooksでコマンドをブロック・通知する記事、AIにPlaywrightのテストを書き換えさせる話はcodegenの録画をgetByRole/getByLabelへ書き換えてもらう記事、複数セッションでの役割分担はAgent Teamsとsubagent・git worktreeの使い分け記事もあわせてどうぞ。

コメント

タイトルとURLをコピーしました