フレーキーテストを自動修復する
ユースケース: テストスイートの2%がフレーキーで、赤いビルドを見ても「本物の失敗かどうか」判断できない。リトライは問題を隠すだけで、人間は同じ3パターンのフレークを延々と直し続けている。この「修正」自体を自動化したい。
なぜ難しいのか
Section titled “なぜ難しいのか”フレーキーテストはコード変更なしに失敗するため、失敗そのものからは「何を直せばいいか」が分かりません。原因の多くはスクリーンショットには写らないタイミングの問題です: 遅れて流れ込んでくるコンテンツ、クリックを横取りするトースト、locator() と click() の間にボタンを動かす段階的レンダリング。診断には画面が時間とともにどう動いたかの証拠が必要で、その先に修正候補の生成と、修正が効いたことの統計的な証明が要ります。
ループはこうです: 検出 → 証拠収集 → 診断と修正(エージェント) → 反復実行で検証 → PR。
1. 検出: リトライをフレークのセンサーにする
Section titled “1. 検出: リトライをフレークのセンサーにする”retries を有効にしていれば、「リトライで通った」こと自体がフレークの信号です。カスタムレポーターで拾います:
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter';
export default class FlakyReporter implements Reporter { private flaky: string[] = []; onTestEnd(test: TestCase, result: TestResult) { if (result.status === 'passed' && result.retry > 0) this.flaky.push(test.id); } onEnd() { require('node:fs').writeFileSync('flaky.json', JSON.stringify(this.flaky)); }}2. 診断に値する証拠を集める
Section titled “2. 診断に値する証拠を集める”トレースが示すのはPlaywrightが何をしたかであって、アクションの合間に画面が何をしていたかではありません — そしてフレークが棲んでいるのは後者です。agent-screen-observer のパッシブモードは、テスト実行中は待ち時間ゼロでフレームを記録し、失敗したときだけ解析して、視覚イベント(遅発のレイアウトシフト、クリック地点に重なったトースト、段階的レンダリングのバースト)をコンパクトなテキスト+切り抜き画像として添付します:
// playwright.config.ts — 通常のtrace設定に加えてimport { test } from 'agent-screen-observer/playwright';test.use({ screenObserverOptions: { mode: 'passive', attach: 'retain-on-failure' },});「画面が落ち着いたように見えた後、+1.8秒でレイアウトがシフトした」というレポートが得られれば、「謎のタイムアウト」は名前のついた修正可能な欠陥に変わります。
3. エージェントで診断とパッチ
Section titled “3. エージェントで診断とパッチ”コーディングエージェント(ヘッドレスモードのClaude Codeなど)に、証拠を詰めた簡潔なプロンプトを渡します:
claude -p "テスト '$TEST_ID' はフレーキーです(リトライで成功)。証拠:- 失敗した試行のエラー + トレース要約: trace.txt- 画面観察レポート: observer-report.json最も可能性の高い原因を特定し、テストに最小の修正を適用してください(ロケーターが曖昧な場合はアプリへの data-testid 追加も可)。ルール: waitForTimeout 禁止。web-firstアサーション優先。テストを弱めないこと。"修正の探索空間は絞り込みます — 典型的な修復は小さく機械的です:
| フレークのパターン(証拠から) | 機械的な修正 |
|---|---|
| 遅発のレイアウトシフトがクリック対象を動かす | まず原因をアサート(画像やリストのロード完了)してからクリック |
| トーストがボタンに重なる | 一時表示要素の消滅を待つ(領域はレポートに載っている) |
| データロードとの競合 | テキストのアサートを expect.poll / toPass によるAPI状態の確認に置き換え |
| 曖昧なロケーターが2要素にマッチ | role+name に絞るかテストIDを付与 |
4. 反復実行で検証 — 省略不可のステップ
Section titled “4. 反復実行で検証 — 省略不可のステップ”反復実行による証明のないフレーク修正は、ただの迷信です:
npx playwright test $TEST_FILE --repeat-each=30 --workers=4これをPRのゲートにします。30回連続グリーンなら、証拠を添えてPRを作成します:
gh pr create --title "fix(flaky): stabilize $TEST_ID" \ --body "原因: 画面観察レポートによる遅発レイアウトシフト(+1.8s)。30/30リピートでグリーン。"人間がレビューするのは検証済み・証拠付きの1行diffです — 再現に費やす午後ではなく、数分で済みます。
- ループには上限を設けます。エージェントの修正が
--repeat-eachを通らなければ、延々とイテレーションせず、集めた証拠とともに人間へエスカレーションします。 - 「フレーキーテスト」の一部はアプリ側の本物のレースです。オブザーバーの証拠を見ればどちら側のバグかは大抵分かります — そちらはバグトラッカーに回し、テストを「安定化」してバグを握りつぶさないこと。
- 修復はCI上でスケジュール実行(
flaky.jsonを対象にナイトリー)し、PRビルドの中では回さないこと — 人間のためのフィードバックループは速いまま保ちます。
- トーストとちらつきを捕まえる — このレシピが依存する観察レイヤー。
- E2Eテストを単体テストに置き換える — 最良のフレーキーE2Eテストとは、もはやE2Eである必要のないテストです。