Playwright運用の「地獄」から生還した話:CI/CDを鉄壁にするための技術的最適解

Playwright運用の「地獄」から生還した話:CI/CDを鉄壁にするための技術的最適解

朝起きたらGitHub Actionsのログが真紅に染まり、AWSの請求額が普段の10倍になっていた。原因はPlaywrightのテストコードに仕込んだ無限ループ。CIが終了条件を見失い、無駄なリソースを食い続けた結果だ。

「テスト自動化」は甘美な響きだが、設計を怠ればそれはただの「金食い虫」と化す。この記事では、数々の地獄を見てきた私が、現場で生き残るために叩き込んだPlaywright運用の最適解を共有する。


1. 環境変数の型安全な管理:Zodによるバリデーション

「`.env`を誤ってプッシュした」なんて笑えないミスは、仕組みで防ぐ。環境変数をそのまま読み込むのは危険だ。`zod`を使って、アプリ起動時に環境変数が欠損していないか、型が正しいかを強制的にチェックする。

// env.ts
import { z } from 'zod';

const envSchema = z.object({
  BASE_URL: z.string().url(),
  TEST_API_TOKEN: z.string().min(1),
});

export const env = envSchema.parse(process.env);

`playwright.config.ts`では、この`env`オブジェクトを参照する。これで「CI上で環境変数が足りない」という初歩的なエラーを、テスト開始の瞬間に検知できる。


2. Flakyテストを撲滅する「Auto-waiting」の真髄

`page.waitForTimeout(5000)`を書いていないか? それは「祈る」のと同じだ。テストが不安定(Flaky)になる最大の原因は、アプリの状態を正しく監視していないことにある。

ネットワークリクエストをフックし、APIのレスポンスを待機するのがPlaywrightの正攻法だ。

// APIレスポンスを待ってから操作を確定させる
await Promise.all([
  page.waitForResponse(resp => 
    resp.url().includes('/api/v1/data') && resp.status() === 200
  ),
  page.getByRole('button', { name: '保存' }).click(),
]);

また、CI側では「ジョブレベルのタイムアウト」を必ず設定すること。無限ループが起きても、自動的にジョブがKillされるようにしておくのが最低限の防衛線だ。

# .github/workflows/playwright.yml
jobs:
  test:
    timeout-minutes: 10 # これを入れないと財布が死ぬ
    runs-on: ubuntu-latest
    # ...


3. 並列実行の罠と「テスト独立性」の確保

テストが増えれば並列実行(Sharding)は必須だが、何も考えずに導入するとDBの競合で全滅する。テストAで作成したユーザーをテストBが消すような依存関係は、並列環境では致命的だ。

解決策は「APIによる動的ユーザー生成」だ。

1. `test.describe.configure({ mode: 'parallel' })` でファイル間並列化を有効化。
2. 各テストファイル内で、`test.beforeEach`を使用して一意なユーザーをAPI経由で作成する。
3. `test.afterEach`でそのユーザーをクリーンアップする。

// 各テストで独立した環境を作る
test.beforeEach(async ({ request }) => {
  const user = await createUniqueUser(request);
  // コンテキストに保持してテストで使用
});

この「泥臭い」データ分離を徹底するだけで、並列実行の安定性は劇的に向上する。


4. CI環境とローカルの「解離」を防ぐ設定

ローカルは日本語、CIは英語環境。この罠で`getByText`が落ちることは珍しくない。さらに、タイムゾーンの不一致で日付計算が狂うのも「あるある」だ。

`playwright.config.ts`で環境を強制的に統一せよ。

export default defineConfig({
  use: {
    locale: 'ja-JP',
    timezoneId: 'Asia/Tokyo',
  },
  // 再試行回数を制限し、無限ループ時の爆発を防ぐ
  retries: process.env.CI ? 2 : 0,
});


教訓:自動化は「魔法」ではなく「規律」だ

Playwrightは最強のツールだが、それを支えるのはエンジニアの泥臭い規律だ。

  • `waitForTimeout`は悪。 即刻削除し、状態監視に切り替えろ。
  • テストデータは使い捨て。 APIで生成・削除し、テスト間の依存を物理的に断て。
  • CIは「壊れるもの」として設計せよ。 タイムアウト制限と再試行回数の制御は、あなたの財布とメンタルを守る盾になる。
テストが落ちることを恐れるな。落ちる理由を突き止め、二度と同じ場所で躓かない仕組みを作ること。それこそが、シニアエンジニアとしての「矜持」だ。

デプロイボタンを押すときに震える必要はない。あなたが書いたテストが、確かな信頼を担保してくれるはずだ。