📝

Playwright導入で躓いた点とその解決策

2025/01/25に公開

はじめに

先日、自分が担当しているプロジェクトにPlaywrightを導入しました。
とても便利で使い勝手が良いと感じましたが、導入の際に躓いた点もいくつかありました。
そこで備忘録も兼ねて皆さんに共有したいと思います!💫

前提

  • Playwrightを導入したプロジェクトは至ってシンプルなフォーム
  • CIでテストを実行する

①実行時間が長すぎる!

シンプルなプロジェクトですが、テストケースは300件以上にもなりました。
これをそのままGitHub Actionsで実行すると、処理に膨大な時間がかかりました。。
(約40分間動きづけていたため、堪らずキャンセルしました笑)

そこでこれを解消するためにShardingを設定しました。
そもそもShardingとは、複数のマシンやプロセスで同時にテストを実行し(並列化)することで、全体の実行時間を短縮する手法です。

Playwrightでは、テストを「シャード」と呼ばれる小さな単位に分割します。
各シャードは独立して実行できるため、並列化が可能です。

今回の例では、テストを4つのシャードに分割し、それぞれを独立したジョブとして実行させます。
最後に、それぞれのシャードから出力されたテスト結果を統合し、最終的なHTMLレポートを生成します。

実装手順

まずは、テスト結果のレポート出力形式をCI時のみblobに変更します。
これはシャーディングによって4つに分割されたシャードで、blobレポートを出力する必要があるためです。

// playwright.config.ts
export default defineConfig({
  testDir: './tests',
  reporter: process.env.CI ? 'blob' : 'html',
});

あとはgithub actionsのワークフローを作成します。

name: Playwright

# ワークフローを手動でトリガーするためにworkflow_dispatchを指定
on:
  workflow_dispatch: {}

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    defaults:
      run:
        working-directory: application
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          # Node.jsのバージョンをpackage.jsonで指定
          node-version-file: application/package.json
          cache: "npm"
          cache-dependency-path: "**/package-lock.json"

      # 依存関係をインストール
      - name: Install dependencies
        run: npm ci

      # Playwright browsersをインストール
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps

      # Playwrightのテストを実行
      - name: Run Playwright tests
        run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

      # Playwrightのテスト結果レポートをアーティファクトとしてアップロード
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: blob-report-${{ matrix.shardIndex }}
          path: ./application/blob-report
          retention-days: 30 # アーティファクトの保存期間を30日に設定
  merge-reports:
    # Merge reports after playwright-tests, even if some shards have failed
    if: ${{ !cancelled() }}
    # testが完了した後に実行させる
    needs: [test]

    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: application
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          # Node.jsのバージョンをpackage.jsonで指定
          node-version-file: application/package.json
          cache: "npm"
          cache-dependency-path: "**/package-lock.json"
      - name: Install dependencies
        run: npm ci

     # blobファイルをダウンロード
      - name: Download blob reports from GitHub Actions Artifacts
        uses: actions/download-artifact@v4
        with:
          path: ./application/all-blob-reports
          pattern: blob-report-*
          merge-multiple: true

        # レポートを生成
      - name: Merge into HTML Report
        run: npx playwright merge-reports --reporter html ./all-blob-reports

        # 生成したレポートをアップロード
      - name: Upload HTML report
        uses: actions/upload-artifact@v4
        with:
          name: html-report--attempt-${{ github.run_attempt }}
          path: application/playwright-report
          retention-days: 30

GitHub Actionsのワークフローの説明

  1. ワークフローのトリガー
    今回は実装やユニットテストが完了した時のみワークフローを実行させれば良いので、トリガーをworkflow_dispatch(手動)に設定しています。

  2. Shardingの設定
    以下の設定で、4つのシャードを作成しています。

    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    
    • shardIndex: シャードの番号を指定(1~4)。
    • shardTotal: 全体のシャード数を指定(今回は4)。
  3. シャードの実行
    各シャードが独立してテストを実行します。シャードごとにblob形式でレポートを出力する設定になっています。

  4. HTMLレポートの生成
    全てのシャードの実行が完了した後に、merge-reportsジョブを実行し、各シャードのblobレポートを結合して1つのHTMLレポートを作成します。
    この順序を守るために、needsを使って依存関係を指定しています。

    merge-reports:
      # Merge reports after playwright-tests, even if some shards have failed
      if: ${{ !cancelled() }}
      # test jobが完了した後に実行
      needs: [test]
    

完成

40分程度動き続けていたtestsを8分弱に短縮することができました!

html-report--attempt-1をダウンロードして結果を確認することもできます。

https://playwright.dev/docs/next/test-sharding

②flakyが解消できない!

テストのステータスには、passfailed、そして flaky の3種類あります。
この flaky とは テストが不安定で、断続的に失敗している状態 を指します。
主にflakyが発生する要因は以下の通りです。

  • タイミングの問題: 非同期処理やレースコンディションが原因で発生するケース
  • 環境の変化: テスト環境やデバイスの違いによる影響
  • 外部依存: 信頼性の低いサードパーティサービスやAPIへの依存
  • 状態管理: テストケース間でのアプリケーション状態の不整合

僕の場合、エラーメッセージの表示に関するテストがflaky(CI環境のみ)になっていました。
調査した結果、原因はエラーメッセージが確実に表示されている保証がない状態でアサーションを行っていたことでした。

この問題を解消するには、条件を満たすまで待機させる必要があります。

// 🙅‍♂️期待する条件を待たずにアサーションしてしまう。
expect(page.locator('#helper-text')).toHaveText('入力してください');
// 🙆‍♂️期待する条件を待ってからアサーションする。
await expect(page.locator('#helper-text')).toHaveText('入力してください');

ちなみに、Playwrightには条件を自動的に待機するアサーションが含まれているため、明示的な waitFor/waitForElementToBeRemovedの呼び出しは必要ないとのことです。

Playwright includes assertions that automatically wait for the condition, so you don't usually need an explicit waitFor/waitForElementToBeRemoved call.

https://playwright.dev/docs/testing-library#replacing-waitfor

最後にflakyを解消する際、参考にしたサイトを貼っておきます。

https://blog.magicpod.com/7-key-playwright-techniques-to-eliminate-test-flakiness-and-boost-reliability

③あれ?この環境はエミュレートできないのか!

Playwrightにはブラウザを仮想的にエミュレートする機能があり、特定の環境(OSやブラウザ)をシミュレーションできます。
ただし、すべての環境に対応しているわけではなく、対応範囲は限られています

王道(Windows/Chromeなど)は基本的にサポートされていますが、プロジェクトの仕様とエミュレーション可能な環境が一致しているかを事前に確認するべきでした。
https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json

僕が担当しているプロジェクトでは、対応していないmacOS/Chrome環境でe2eテストをする必要があったので、macOS/Chrome環境のみ手動でテストを行いました。

終わりに

ローカルではテスト通るけどCI/CDでは通らない時が1番困るんだよな〜〜

Discussion