← 記事一覧に戻る

Playwright テストが CI だけ落ちる・ローカルで通るのに GitHub Actions で失敗する5つの原因と対策

はじめに

「ローカルでは全部テストが通るのに、GitHub Actions(CI)にプッシュしたら突然落ちる」——Playwrightを使い始めた開発者が最初にぶつかる壁がこれです。

この記事では、CIでPlaywrightテストが落ちる5つの原因と、それぞれの対処法を解説します。


症状チェック:こんな症状はありませんか?

  • ローカル(npx playwright test)では全テストがパスする
  • GitHub Actionsのログに TimeoutErrorError: page.goto が出る
  • スクリーンショット比較テストが snapshot doesn't match で落ちる
  • Page closedTarget closed エラーが突然出る
  • 同じコミットでもCIを再実行すると通ったり落ちたりする(フレーキー)

原因1:ブラウザのインストール忘れ

症状

Error: browserType.launch: Executable doesn't exist at /home/runner/.cache/ms-playwright/chromium-xxx/chrome-linux/chrome

原因

GitHub Actionsのランナーにはブラウザがプリインストールされていません。npx playwright install を実行しないと、ブラウザが存在しないためテストが起動すらしません。

対策

# .github/workflows/playwright.yml
- name: Install Playwright Browsers
  run: npx playwright install --with-deps

--with-deps フラグが重要です。これがないと、ブラウザの実行に必要なシステム依存パッケージ(libgbmlibasound2 など)がインストールされず、ブラウザが起動しません。


原因2:タイムアウトが足りない(CI環境はローカルより遅い)

症状

TimeoutError: page.click: Timeout 5000ms exceeded.

原因

CIのランナーはローカルマシンよりCPUが遅く、ネットワーク遅延も大きいです。ローカルでは100ms以内に応答する要素が、CI上では2〜3秒かかることがあります。

デフォルトのタイムアウト(5000ms)だとCI上でギリギリ足りないケースが多発します。

対策

playwright.config.ts でタイムアウトを伸ばします:

// playwright.config.ts
import { defineConfig } from '@playwright/test'

export default defineConfig({
  timeout: 30000,       // テスト全体のタイムアウト(デフォルト: 30000ms)
  expect: {
    timeout: 10000,     // expect() のタイムアウト(デフォルト: 5000ms)
  },
  use: {
    actionTimeout: 15000,     // click等のアクションのタイムアウト
    navigationTimeout: 30000, // page.goto() のタイムアウト
  },
})

CIのみ設定を変えたい場合:

export default defineConfig({
  timeout: ({}).CI ? 60000 : 30000,
  use: {
    actionTimeout: ({}).CI ? 30000 : 10000,
  },
})

原因3:スナップショット(ビジュアルリグレッション)のベースラインが違う

症状

Error: A snapshot doesn't exist at tests/screenshot.spec.ts-snapshots/homepage-linux.png, writing actual.

または

Screenshot comparison failed: 1234 pixels are different.

原因

Playwrightのスナップショットテスト(expect(page).toHaveScreenshot())は、OSとブラウザのバージョンごとにベースライン画像が異なります

ローカル(macOS)で生成したベースライン画像をコミットしても、CI(Linux)では使えません。フォントのレンダリング、アンチエイリアス、スケーリングが違うため、ピクセル差が必ず発生します。

対策

方法1: Linux向けのベースラインをDockerで生成する

# Docker経由でLinux用のスナップショットを生成
docker run --rm \
  -v $(pwd):/work \
  -w /work \
  mcr.microsoft.com/playwright:v1.44.0-jammy \
  npx playwright test --update-snapshots

生成された -linux.png をコミットすればCIで一致します。

方法2: スナップショットを許容範囲で比較する

await expect(page).toHaveScreenshot({
  maxDiffPixels: 100, // 100ピクセルまで差異を許容
  threshold: 0.1,     // 10%の色差まで許容
})

原因4:環境変数・認証情報がCIにない

症状

  • テストが認証エラーで落ちる
  • ({}).API_URLundefined になる
  • APIモックが機能せず実際のAPIにアクセスしてレート制限に引っかかる

原因

ローカルの .env ファイルに書いた環境変数は、CIに自動コピーされません。

対策

GitHub Actions の secrets を使う:

# .github/workflows/playwright.yml
- name: Run Playwright tests
  run: npx playwright test
  env:
    API_URL: ${{ secrets.API_URL }}
    AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
    BASE_URL: http://localhost:3000

playwright.config.ts で baseURL を明示する:

export default defineConfig({
  use: {
    baseURL: ({}).BASE_URL ?? 'http://localhost:3000',
  },
})

APIをモックしてCIの外部依存を排除する:

// tests/example.spec.ts
test('ユーザー一覧が表示される', async ({ page }) => {
  // 外部APIをモックする
  await page.route('/api/users', (route) => {
    route.fulfill({
      status: 200,
      body: JSON.stringify([{ id: 1, name: 'テストユーザー' }]),
    })
  })

  await page.goto('/')
  await expect(page.getByText('テストユーザー')).toBeVisible()
})

原因5:開発サーバーが起動していない・ポートが違う

症状

Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:3000

原因

Playwrightはテスト実行前に開発サーバーが起動していることを前提とします。ローカルでは npm run dev を先に起動してからテストしますが、CIでは何もしていないとサーバーがいません。

対策

playwright.config.tswebServer オプションで自動起動します:

// playwright.config.ts
export default defineConfig({
  webServer: {
    command: 'npm run build && npm run preview',
    url: 'http://localhost:4173',
    reuseExistingServer: !({}).CI,
    timeout: 120 * 1000, // サーバー起動待ちのタイムアウト(2分)
  },
  use: {
    baseURL: 'http://localhost:4173',
  },
})

reuseExistingServer: !({}).CI とすることで、ローカルでは既存サーバーを再利用(速い)し、CIでは毎回新しく起動します。

開発サーバー(npm run dev)ではなくビルド済みプレビュー(npm run build && npm run preview)を使うと、本番に近い環境でテストできます。


まとめ:CIでPlaywrightが落ちる原因チェックリスト

原因 エラーの特徴 対策
ブラウザ未インストール Executable doesn't exist npx playwright install --with-deps をworkflowに追加
タイムアウト不足 TimeoutError が出る playwright.config.ts でCI用にタイムアウトを延長
スナップショット不一致 snapshot doesn't match Linux環境でベースラインを再生成、または許容差を設定
環境変数なし 認証エラー・undefined GitHub Secrets経由で注入、またはAPIをモック
サーバー未起動 ERR_CONNECTION_REFUSED webServer オプションでCI内でサーバーを自動起動

CIでPlaywrightが落ちる原因の9割以上はこの5つです。GitHub Actionsのログをよく読んで、エラーメッセージから原因を絞り込んでください。


GitHub Actions の完全なワークフロー例

name: Playwright Tests

on:
  push:
    branches: [main, production]
  pull_request:
    branches: [main]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright Browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test
        env:
          CI: true
          BASE_URL: http://localhost:4173

      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

失敗時にレポートをアーティファクトとしてアップロードするとデバッグが楽になります。HTMLレポートにはスクリーンショット・動画・トレースが含まれており、原因特定に役立ちます。