fixing-flaky-e2e-tests について
再現可能な Playwright E2E テストの不安定さを診断し、修正するスキルです。ローカルでの再現、最小限の変更、複数回の検証を重視します。
主な機能
- 01CI データから不安定なテストを特定
- 02ローカルでの再現を必須化
- 03トレースとスナップショットの調査
- 04タイミングやブラウザー問題への指針
- 05複数回の実行による修正確認
活用シーン
- 01Playwright テストの断続的な失敗の調査
- 02ブラウザー固有のスナップショット不一致のデバッグ
- 03タイミング問題を修正してテストを安定化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ streamlit/ streamlit --skill fixing-flaky-e2e-testsインストール後、Claude Code で/fixing-flaky-e2e-testsを入力して読み込まれたか確認します。
詳細
不安定な E2E テストの修正
e2e_playwright/ にある不安定な Playwright E2E テストを診断して修正します。
使用するタイミング
- テストが断続的に失敗する(成功したり失敗したりする)
- タイムアウトエラー(
TimeoutError: wait_until timed out) - ピクセル差分を伴うスナップショットの不一致
- ブラウザ固有の失敗(firefox、webkit、chromium)
- CI から上位の不安定なテストを修正するようユーザーが依頼した場合
上位の不安定なテストの特定
スクリプトを実行して、最近の CI 実行から最も不安定なテストを特定します。
uv run scripts/fetch_flaky_tests.py
オプション:
--days N: N 日前までさかのぼって確認します(デフォルト: 4)--top N: 上位 N 件の不安定なテストを返します(デフォルト: 10)--min-reruns N: 含める最小の再実行合計回数(デフォルト: 2)--json: プログラムで利用するために JSON として出力します
このスクリプトは、成功した playwright.yml の実行から playwright_test_stats アーティファクトをダウンロードし、再実行が必要だったテストを集計します。
修正対象のテストの絞り込み
すでに @pytest.mark.flaky でマークされているテストはスキップします---これらは別途追跡されている既知の不安定なテストです。
# Check if a test file has the flaky marker
grep -l "pytest.mark.flaky" e2e_playwright/<test_file>.py
調査ワークフロー
1. 不安定さをローカルで再現する(必須)
重要: ローカルで失敗するテストのみ修正を試みてください。25 回実行しても不安定さを再現できない場合は、修正を試みないでください—そのテストはローカルでは対処できない CI 環境の要因によって不安定になっている可能性があります。
影響を受けるブラウザでテストを最大 25 回実行します。ループは最初の失敗で中断し、完全な出力をキャプチャします。
for i in {1..25}; do
result=$(make run-e2e-test e2e_playwright/test_file.py::test_name -- --browser firefox 2>&1)
if echo "$result" | grep -q "FAILED"; then
echo "=== FAILURE ON RUN $i ==="
echo "$result"
break
fi
echo "Run $i: PASSED"
done
25 回すべてが成功した場合は、このテストをスキップして次に進みます。
2. テストアーティファクトの確認
失敗後、以下を確認します:
e2e_playwright/test-results/- トレース、スクリーンショット、動画e2e_playwright/test-results/snapshot-updates/- 実際のスナップショットと期待されるスナップショット
スナップショットの不安定さが続く場合: スナップショットの不一致によりテストが失敗し続ける場合は、e2e_playwright/test-results/snapshot-updates/ にある実際の画像と期待される画像を比較します。以下に注目します:
- ピクセルレベルの差分(画像差分ツールやオーバーレイを使用)
- 微妙なレイアウトのずれ、フォントレンダリングのばらつき、タイミングによるアーティファクト
- ブラウザ固有のレンダリングの癖(特に Firefox のサブピクセルの問題)
これは、不安定さがタイミング(コンテンツが読み込まれていない)、アニメーションの状態、ブラウザのレンダリングの違いのいずれに起因するかを特定するのに役立ちます。
一般的な原因と修正
タイミングの問題(最も一般的)
症状: 要素が完全にレンダリングされる前、アニメーションが完了する前にスクリーンショットが撮影される。
修正: 操作やスクリーンショットの前に明示的な待機を追加します:
# Before
element.click()
assert_snapshot(element, name="snapshot")
# After
element.click()
expect(element).to_be_visible() # Wait for visibility
assert_snapshot(element, name="snapshot")
アニメーションするポップアップ/モーダル/カレンダーの場合:
calendar = page.get_by_test_id("stDateInputCalendar").first
expect(calendar).to_be_visible() # Wait for animation to complete
assert_snapshot(calendar, name="calendar-snapshot")
ブラウザのリトライによる余分なイベント
症状: アサーションが正確な件数を期待しているが、それより多く取得される(例: assert 44 == 41)。
修正: ブラウザが失敗した操作をリトライする可能性がある場合は、== の代わりに >= を使用します:
# Before
assert error_count == expected_count
# After - browsers may retry failed image loads
assert error_count >= expected_count
タイムアウトが短すぎる
症状: 低速なブラウザで TimeoutError が発生する。
修正: 遅くなる可能性がある操作のタイムアウトを増やします:
# Before
wait_until(app, lambda: check_condition(), timeout=10000)
# After
wait_until(app, lambda: check_condition(), timeout=20000)
タイミングによるスナップショットの不一致
症状: Snapshot mismatch for ... (X pixels difference)。
原因:
- スクリーンショット撮影時に要素がまだアニメーション中
- フォントレンダリングが完了していない
- 非同期コンテンツが読み込まれていない
- 画像が完全に読み込まれていない/デコードされていない(特に webkit)
修正: スクリーンショットの前に要素が安定していることを確認します:
element = page.locator(".my-element")
expect(element).to_be_visible()
# For elements with animations, wait for specific CSS state:
expect(element).to_have_css("opacity", "1")
assert_snapshot(element, name="snapshot")
画像を含む要素の場合は、画像が完全に読み込まれてデコードされるまで待機します:
from e2e_playwright.shared.app_utils import wait_for_images_loaded
element = page.locator(".my-element")
wait_for_images_loaded(element) # Waits for load + decode
assert_snapshot(element, name="snapshot")
ブラウザ固有の考慮事項
| ブラウザ | 一般的な問題 |
|---|---|
| Firefox | コンソールログが遅い、失敗したリクエストをリトライする可能性がある、サブピクセルのレンダリングの違い |
| Webkit | レイアウトのタイミングの違いがある可能性がある |
| Chromium | 一般に最も信頼性が高く、ベースラインとして使用 |
Firefox のサブピクセルレンダリングによる不安定さ
症状: Firefox のスクリーンショットは、サブピクセルレンダリングのばらつきにより 1 ピクセルの差異で不安定になります。
修正: テスト対象の要素の上に 1 行の Markdown 要素を追加します。これにより、サブピクセルの位置がより安定した値にシフトします。
# In the test app (.py file)
st.markdown("---") # Stabilizes subpixel rendering for elements below
st.date_input("Pick a date")
これは Firefox のサブピクセルレンダリング動作に対する回避策であり、他のタイミング修正が効かない場合にスナップショットの不安定さを軽減できます。
タイミング修正を使い果たしても、既知のブラウザの制限(テストのバグではない)が原因で特定のブラウザでのみ不安定さが続く場合、最後の手段として skip_browser が適切かもしれません。
# Only use after confirming this is a browser-level limitation, not a fixable timing issue
@pytest.mark.skip_browser("webkit", reason="Webkit has known layout timing issues with this element")
def test_problematic_on_webkit(app: Page):
...
重要: skip_browser の使用には正当な理由が必要です。まず根本的なタイミングの問題を修正することを優先してください。スキップが許容される場合のガイダンスについては「ルール」セクションを参照してください。
検証
修正を適用した後、複数回実行して検証します。
# Run 10+ times to ensure stability
for i in {1..10}; do
make run-e2e-test e2e_playwright/test_file.py::test_name -- --browser firefox 2>&1 | grep -E "(PASSED|FAILED)"
done
目標: 修正完了とみなす前に 10/10 回成功。
主要なユーティリティ
e2e_playwright.conftest より:
wait_for_app_run(page)- Streamlit スクリプトの実行を待機しますwait_for_app_loaded(page)- アプリの初期読み込みを待機しますwait_until(page, fn, timeout)- 条件が真になるまでポーリングします
e2e_playwright.shared.app_utils より:
expect_no_skeletons(element)- 読み込み中のスケルトンが消えるまで待機しますreset_focus(page)- 外側をクリックして blur イベントをトリガーしますreset_hovering(locator)- マウスを要素から離しますwait_for_images_loaded(element)- 画像が読み込まれてデコードされるまで待機します(webkit で重要)
完全なワークフロー
-
不安定なテストを取得:
uv run scripts/fetch_flaky_tests.py --top 10 -
マークされたテストを除外:
@pytest.mark.flakyが付いたテストはスキップします -
残りの各テストについて:
- テストコードを読み、何をテストしているかを理解します
- ローカルで不安定さを再現します(最大 25 回の実行)
- 再現しない場合はスキップ: 25 回すべて成功する場合は、次のテストに移ります
- 根本原因(タイミング、ブラウザ固有など)を特定します
- 最小限の修正を適用します
- 影響を受けるブラウザで 10 回以上実行して検証します
-
チェックを実行: コミット前に
make checkを実行します
ルール
- まずローカルで再現: ローカルで再現できるテストのみを修正します(最大 25 回の実行)
- 最小限の修正: 問題を修正する最小の変更
- 正当な理由なくテストを無効化しない: 単に不安定さを「修正」するためにテストをスキップしないでください。
skip_browserが許容されるのは次の場合のみです:- すべてのタイミング/待機の修正を使い果たした
- 不安定さが文書化されたブラウザの制限によるものである(テストのバグではない)
- その理由を説明する明確な
reasonを含めている
- 徹底的に検証: 修正後、影響を受けるブラウザで 10 回以上実行します
- テストの意図を保つ: テストが何を検証しているかを理解します
- 原因を文書化: 待機/タイムアウトが必要な理由を説明するコメントを追加します