人間が寝ている間にClaude CodeがPlaywrightのE2Eテストを直してPRを出す
nightlyで実行しているPlaywrightのE2Eテストが落ちたとき、翌朝その原因を調べて直して、PRを出して...という作業が発生します。
人間が法的に働けない時間に失敗が積まれて、翌朝それを片付けるのは単純に時間がもったいない。
あと面倒。
だったらその時間にClaudeに修正案を考えてもらって第一案を練ってもらえばいい、と思いました。
全体の流れ
E2Eテストは毎晩GitHub Actionsのスケジュール実行でまわしています。テストスイートは現在85本のspecファイル。
ここで失敗が出ると、そのrunに引き続いて _analyze-and-fix.yml というワークフローが起動します。
夜間E2E run
↓ 失敗を検知
Playwright JSONレポートをartifactとして保存
↓
analyze-and-fixジョブ起動
↓
Claude Code CLIが分析・修正
↓
Draft PR作成
起きたらDraft PRができています。Happyだね!!
実装
_analyze-and-fix.yml は再利用可能ワークフロー(workflow_call)として実装した。夜間runから呼び出されるほか、workflow_dispatch で手動実行もできるようにしていて、run-idを指定しなければ最新の失敗runを自動で探しにいきます。
主要なステップはこんな感じ。
- name: Analyze failures and create fix PR
timeout-minutes: 25
env:
CLAUDE_CODE_API_KEY: ${{ secrets.CLAUDE_CODE_API_KEY }}
run: |
claude -p "$PROMPT" \
--allowedTools "Read,Write,Edit,Grep,Glob,Bash(git:*),Bash(gh:*),Bash(npm:*),Bash(npx:*),Bash(jq:*),Bash(cat:*),Bash(ls:*)"
--allowedTools で許可するコマンドを絞っています。Bash(git:*) や Bash(gh:*) のように接頭辞でスコープを限定することで、git操作やGitHub CLIは使えるが任意のbashコマンドは実行できない構成にしました。
サンプルとして「*」としているが、実際はさらに細かくコマンド制御する必要があります。
※もちろんコマンドだけでなく、リポジトリ側でも守るための設定をしっかり加えている。
プロンプトはざっくりこういう5ステップを指示しています。
-
playwright-report.jsonとgh run view --log-failedで失敗ログを収集 - instructionsのパターンに照らして「テストコードで直せるか / サーバー側バグか」を分類
- ブランチを切って最小限の修正を適用
-
npm run code-qualityで品質チェック(パスするまで繰り返す) - コミット → プッシュ → Draft PR作成
修正対象も specs/, pages/, utils/ のみとプロンプトで制限しています。
それ以外を触っていたとしても、Draft PRで上がってくるものを人間がApproveする必要があるので、正しいテストコードとして通過するリスクを抑えています。
QA知見をナレッジベース化する
さらにClaude Codeに渡すinstructionsファイル(.github/instructions/ci-failure-analysis.instructions.md)に、QAのナレッジを書き溜めています。
※過去のテストコードのPRに対し、QAが実際にレビューを行った内容やその結果をまとめている。
現在定義しているエラーパターンはこんな感じ。
| パターン | 症状 | 対応 |
|---|---|---|
| ポーリングタイムアウト | N回リトライ後に失敗 | 呼び出し箇所のmaxRetriesを調整(デフォルト値は変えない) |
| strict mode violation | 複数要素にマッチ |
{ exact: true } を追加 |
| テーブル行ロケーターエラー | rowspan構造に未対応 | XPathで兄弟行も含む検索に修正 |
| ページ遷移後の操作 | reload後に不安定 | Playwrightのauto-waitに任せる |
| 承認ワークフロー系 | モーダル内でstrict違反 |
getByRole('dialog') でスコープを限定 |
nth() は使わない、waitFor() をactionの前に追加しない、固定の waitForTimeout は入れないといった原則も書いています。
「こういう症状のときはこう直す」というパターンがナレッジベースとして育っていく仕組みにしています。
実際に直した例
先日発動したオートヒールの実例です。
失敗したのは、とある設定画面にあるテーブルで、エラーはテーブル行ロケーターでした。
このテーブルはカテゴリ列に rowspan を使っていて、「登録」操作はカテゴリと同じ <tr> にいるが、「削除」操作は次の <tr> に配置される構造になっていました。
| カテゴリ (rowspan=2) | 登録 |
| | 削除 | ← 別のtrにいる
旧ロケーターは ancestor::tr でカテゴリを含む最初の行のみを返していたので、「削除」行でフィルターが0件になっていたことが原因です。
Claudeが直したのがこのXPath。
// 旧: カテゴリのtrを起点に検索 → rowspanで分離した行は拾えない
getByText(category).locator('xpath=ancestor::tr').filter({ hasText: operation })
// 新: 操作テキストのtrを起点に検索 → 同行 or 直前兄弟行にカテゴリがあればヒット
locator(`xpath=//tr[
td[normalize-space(.)="${operation}"]
and (
td[normalize-space(.)="${category}"]
or preceding-sibling::tr[1][td[normalize-space(.)="${category}"]]
)
]`)
発想を逆転させて「操作テキストのある <tr> を起点にして、カテゴリを同行または1つ前の兄弟行に探しにいく」構造にしていました。
これで「登録」(同一行にカテゴリあり)と「削除」(直前の兄弟行にrowspanのカテゴリあり)の両方に対応できます。PR本文にも分析結果と修正理由がまとまっていて、レビュアーが内容を追いやすくなっています。
運用フロー
Draft PRが作成されると、QAで持ち回りしているPlaywrightエラーチェック担当がレビューしてマージします。いまいまは自動マージはしていません。
Claudeが修正に失敗した場合(分析途中でエラー終了など)は、GitHub Issueが自動作成されてフォールバックします。「手動確認が必要です」というIssueと失敗したrunへのリンクが残るので、朝気づかず放置されることにはなりにくい状態です。
これまで発動したオートヒールは2件。どちらもそのままマージできるレベルの修正でした。
もともと「flakyにならないようプロダクトとテストを設計する」というアプローチをとっているので、夜間runが頻繁に落ちる状況ではありません。夜間に問題が出るとしたらだいたいプロダクト側の意図しない変更か、rowspanのような構造的な考慮漏れか…。
難しいところ
タイムアウトを伸ばすだけの修正が出てくる問題。
Claudeはログを見て「タイムアウトが足りない」と判断すると、タイムアウト値を増やす修正を出してくることがあります。この修正は一見正しいように見えるが、根本原因を隠蔽しているだけのことが多いです。
instructionsファイルにポーリングのデフォルト値は変えないことを明記してあるが、完全には防げません。ここだけはQAの知見やレビューで補うしかないなと。
プロダクトコードを参照できない。
このジョブからはPlaywrightレポートとテストコードしか見られません。プロダクトのコードには触れないので、UIの構造変化に起因する失敗は判断が難しい…。
ただ、テストコードが保守しやすい構造になっているおかげで(fixtures設計の話)、Page Objectからロケーターをたどって原因にたどり着けるケースがほとんど。プロダクトコードを見なくてもPlaywrightのレポートに十分な情報が含まれていれば解決できます。保守しやすいテスト構造が、オートヒールの成功率にも貢献しています。
今後
PR複雑度評価と組み合わせて、低複雑度の修正PRに対してはauto approveも検討しています。
前述した「実際に直した例」では1ファイル・数行の修正で complexity:low 相当。このレベルなら人間のレビュー省略も現実的かなと。
ただし「低複雑度 = 安全」というわけでもないのがテストコードのむずかしさで、Page Objectは数行でも多くのテストに波及します。そのあたりの判断基準はもう少し詰めてから動かしたいです。
(複雑度評価ではこの影響度合いも加味したスコアリングも行っていますが、いまだ適切に出ているかという微妙…もうちょっとブラッシュアップが必要かなと思ってます。)
「人間が法的に働けない時間がもったいない(いや、単純に直すのが面倒なだけかもw)」という至ってシンプルな動機から作った仕組みですが、朝起きたらDraft PRが待っている体験はなかなか良いです。
心もテストもHappy!!
本記事を含む実践ガイド本を出しました
本記事のオートヒール pipeline をゼロから組み立てるところまで地続きで届くハンズオン本を Zenn books で公開しています。POM + Fixtures Pattern の設計から、beforeAll の罠、認証 4 パターン、Claude Code との連携 (CLAUDE.md と Skills)、そして本記事の auto-heal 章までを一気に通れる構成です。
docker compose run で動くテンプレートリポジトリ (tomodakengo/playwright-fixture) を clone しながら章ごとに進められるようにしました。Playwright 未経験の QA エンジニアが「明日から自社で導入できる」状態に届くところまで連れていく本です。本記事の auto-heal を組織で始めたい人は、第 9 章にそのまま運用に乗せるための Day 1〜Week 3 の最小構成も書きました。
書籍やテンプレートに対する質問・修正依頼・議論はテンプレリポの GitHub Issues で受け付けています (3 種類のテンプレ用意してます)。 → Issue 作成画面
弊社テックブログも覗いていってね!!
Discussion
たくさんの方にご覧いただきありがとうございます。
続編…とまではいかないですが、QAナレッジをinstructionsにしているという点をこの土日で少し深堀りしてみました。
instructionsの更新があまりにもエンジニア寄りのナレッジ蓄積である問題から、issue起票→triage→instructions promoteのフローを組むのはどうかという案を考えています。
よろしければ、こちらもご参照ください。