その「たまに落ちるテスト」、お前のコードが腐っている証拠だ。

その「たまに落ちるテスト」、お前のコードが腐っている証拠だ。

「昨日は通ったはずのCIが、今日は赤色に染まっている」。
この地獄のような通知に、お前はあと何回付き合うつもりだ?

「GitHub Actionsの環境が悪い」「ネットワークが重い」。そうやって環境のせいにしている間にも、お前のプロダクトの信頼性は確実に削り取られている。テストが「たまに落ちる」状態は、もはやテストではない。ただのノイズだ。

俺もかつては同じ沼にいた。だが、Playwrightの挙動を理解し、CIの限界を知った今なら言える。「Flakyなテスト」を放置するのは、メンテナンスを放棄しているのと同じだ。 今日でその負のループを断ち切るぞ。


1. 「固定待機」は悪魔の所業だと知れ

`page.waitForTimeout(3000)`。この一行を書いた瞬間、お前のテストは「運ゲー」に成り下がる。

なぜダメなのか? ブラウザのイベントループや、DOMのレンダリング完了タイミングは、お前のPCとCI環境で全く異なるからだ。3秒待っても終わらない重い処理が走った瞬間、テストは死ぬ。

正しいのは「状態を待つ」ことだ。PlaywrightのAuto-waitingは、DOMがアタッチされ、可視状態になり、アクション可能になるまでを自動で監視してくれる。

// 悪い例:固定待機。これ書いた時点で運任せのギャンブル
await page.waitForTimeout(3000);
await page.click('#submit-button');

// 良い例:状態を待つ。Playwrightの強力なWeb First Assertionsを信じろ
const submitButton = page.locator('#submit-button');
await expect(submitButton).toBeVisible();
await submitButton.click();

もし要素が非同期で動的に生成されるなら、`expect.poll`でポーリングを行うか、`locator`のタイムアウトを調整して「確実な状態」を待つのがプロの作法だ。


2. 認証のたびにログインAPIを叩くな

テストごとにログイン処理を走らせる? 愚かだ。DBにゴミが溜まるし、レートリミットで自爆する。俺はこれで、個人開発のサービスを自らダウンさせたことがある。

`storageState`こそが正義だ。 ログイン後のCookieを保存し、それを使い回す。これだけでテストの実行時間は劇的に短縮され、CIの安定性は跳ね上がる。

推奨されるパイプライン構成

GitHub Actionsで認証情報を扱うなら、シークレットにJSONを直書きして環境変数でパースするのはやめろ。エスケープ漏れで泣くことになる。`actions/upload-artifact`でファイルをやり取りするのが、最も堅牢でモダンな手法だ。

# .github/workflows/test.yml
jobs:
  auth:
    runs-on: ubuntu-latest
    steps:
      - run: npx playwright codegen --save-storage=auth.json
      - uses: actions/upload-artifact@v4
        with:
          name: auth-data
          path: auth.json

  test:
    needs: auth
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: auth-data
      - run: npx playwright test --storage-state=auth.json


3. リソース制限という「見えない壁」を突破せよ

フルスタックアプリを並列実行(`workers: 100%`)させて、CIがメモリ不足で落ちる経験は誰しも通る道だ。GitHub Actionsの無料枠(2コア・7GBメモリ)でブラウザを大量に回せば、そりゃあ悲鳴も上げる。

単なる制限から「分散実行(Sharding)」へ

単純に`workers`を絞るのも手だが、プロジェクトが巨大化すればそれでは間に合わない。`--shard`オプションを使って、テストを物理的に分割し、複数のRunnerで並列実行しろ。

// playwright.config.ts
export default defineConfig({
  // CI環境ではメモリ負荷を考慮してワーカー数を制限しつつ、再試行で安定性を担保
  workers: process.env.CI ? 2 : undefined,
  retries: process.env.CI ? 2 : 0, 
});

CIの設定で`npx playwright test --shard=1/3`のように実行単位を分ければ、どんなにテストが増えても実行時間は一定に保てる。これこそがスケールするCIの姿だ。


最後に:テストは「守り」ではなく「武器」だ

テストを書く目的は「バグを見つけること」だけじゃない。「自信を持ってデプロイし続けること」だ。CIが落ちるたびに「またか」と溜息をついているようでは、開発者は新しい機能を作る意欲を失う。

  • 固定待機を捨て、状態を待て。
  • 認証は使い回せ。
  • CIのリソースを理解し、分散実行で最適化しろ。
「ブラウザの気まぐれ」を許容する設計こそが、一流のエンジニアの証明だ。お前のコードが、明日の自分を救う武器になるか、それとも足を引っ張る枷になるか。それは今日、お前がどうテストを書くかの一点にかかっている。

さあ、その腐ったテストコードを今すぐリファクタリングしろ。