jest-puppeteer を使用したエンドツーエンドテストの簡単ガイド
この記事では、jest-puppeteer を使用した効率的なエンドツーエンドテストを書くための簡単なガイドを提供します。セットアッププロセス、よく使われる API、そして簡単な to-do アプリを例に用いた実際のテストシナリオに焦点を当てています。
Logto の品質を確保し、継続的に改善するための取り組みの一環として、エンドツーエンドの自動テストには jest-puppeteer を採用しています。これにより、干渉を受けずに Logto の開発を迅速に反復できるようになります。
この記事では、簡単な to-do アプリを例に取りながら、効果的な jest-puppeteer テストスクリプトを書くための経験を共有します。目的は、jest-puppeteer を使用したテストコードを自身のプロジェクトで迅速に書き始めるのを手助けすることです。
jest-puppeteer を使用したエンドツーエンドテストの紹介
エンドツーエンドテストは、ユーザーの視点からアプリケーションが正しく動作していることを確認するための方法です。これを実現するために、Jest と Puppeteer という 2 つの基本的なツールを使用します。
Jest は、テストとアサーションを記述するためのユーザーフレンドリーな API を提供する、人気の JavaScript テストフレームワークです。ユニットテストや統合テストに広く使用されています。
Puppeteer は Chrome チームによって開発された Node.js ライブラリで、ヘッドレス Chrome または Chromium ブラウザを制御するためのハイレベルな API を提供します。そのため、エンドツーエンドテストでのブラウザ操作を自動化する のに理想的な選択です。
jest-puppeteer は、Puppeteer を使用したエンドツーエンドテストを可能にする Jest のプリセットです。新しいブラウザインスタンスを起動し、それらを通じてウェブページと対話するための簡単な API を提供します。
基本的なツールの理解ができたところで、テスト環境のセットアップに進みましょう。
テスト環境のセットアップ
jest-puppeteer を使用したエンドツーエンドテストのテスト環境のセットアップは、簡単な 3 つのステップで構成されています。
- 依存関係のインストール
プロジェクト内(または新しいプロジェクトを作成して)ターミナルを開き、次のコマンドを実行します:
次に node_modules/puppeteer
に移動し、Puppeteer に必要な Chromium をインストールします:
- Jest の設定
次に、Jest を Puppeteer とシームレスに動作させるために設定する必要があります。
プロジェクトのルートディレクトリに Jest 設定ファイル(例: jest.config.js
)を作成します。
必要に応じてこのファイルで他の Jest 設定をカスタマイズすることができます。Jest 設定のカスタマイズの詳細は、Jest 設定 を参照してください。
- テストを書く
プロジェクト内に .test.js
拡張子のテストファイルを作成します。Jest はこれらのテストファイルを自動的に検出して実行します。
Jest のドキュメント からの例は以下の通りです:
次にテストを実行するためのコマンドを入力します:
これらの 3 ステップを踏むことで、jest-puppeteer を使用したエンドツーエンドテストを実施するための適切なテスト環境が整います。
ただし、これは基本的な例に過ぎません。環境設定の詳細情報については、以下の関連文書を参照してください:
よく使われる API
次のステップでは、テストを支援するために Jest、Puppeteer、jest-puppeteer が提供する API を利用します。
主に、Jest はテストの構造化と予期した結果のアサーションに役立つ API を提供します。具体的な詳細は ドキュメント にて参照できます。
Puppeteer の API は主にブラウザとの対話のために設計されており、それらが提供するテストに対するサポートはそれほど単純ではないかもしれません。Puppeteer が提供する機能を理解するために、Puppeteer ドキュメント を参照できます。次のテスト例では、いくつかの一般的な使用例について説明します。
Puppeteer の API は元々テスト用に設計されていないため、それらを使ってテストを書くには挑戦が必要です。Puppeteer テストを書くプロセスを簡略化するために、jest-puppeteer には expect-puppeteer
という組み込みライブラリが含まれています。これは、簡潔でユーザーフレンドリーな API のセットを提供します。ライブラリーの詳細は、各例を示す ドキュメント にて紹介されています:
次の例では、これらのライブラリーが提供する API を組み合わせてテストシナリオを完成させます。
では、簡単な to-do アプリを使ってテストコードの書き方を学び始めましょう。
簡単な to-do アプリ
「簡単な to-do アプリ」が http://localhost:3000
で動作していると仮定します。このアプリの主な HTML コードは次のとおりです:
エンドツーエンドテストを行う際、次のシナリオをカバーすることを目指しています:
- アプリの URL にアクセスする際、アプリとそのデータが正しく読み込まれるべきです。
- 項目のチェックボタンがクリックできるべきです。
- 項目ノートの内部にある外部リンクをクリックすると、新しいタブでリンクが開かれるべきです。
- フォームから項目を追加できるべきです。
後で、これらのシナリオを検証するためのテストコードを書きます。
アプリとそのデータが読み込ま れたと予期すること
アプリが次の条件を満たしている場合、正常にロードされていると考えています:
- アプリの URL にアクセスした後、ページに ID "app" を持つ要素が存在する。
- アプリ内部のデータが正しくレンダリングされる。
そのため、次のテストコードを記述しました:
このコードでは:
page.goto
はブラウザで "http://localhost:3000" に入力するのと同じで、任意の URL にナビゲートできます。page.waitForSelector
は特定の CSS セレクターが特定の要素にマッチするのを待つために使用します(デフォルトの待ち時間は 30 秒です)。expect(page).toMatchElement
はexpect-puppeteer
ライブラリから来ており、page.waitForSelector
に似ていますが、要素の内部テキストもマッチングするサポートが含まれています。さらに、toMatchElement
のデフォルトタイムアウトはわずか 500ms です。
一見完璧に見えますが、テストを実行して CI 環境にデプロイすると、複数回の実行後に稀に失敗します。失敗メッセージには次のように記されています:
失敗情報とこのテストが大半の時間で成功する事実に基づいて、アプリが読み込まれた後の最初の 500ms 以内にデータが必ずしも返ってくるとは限らないと推測できます。そのため、アサーションを行う前にデータが読み込まれるまで待ちたいと考えます。
通常、この目的を達成するためのアプローチは 2 つあります:
- アサーションの待ち時間を増やす
エラーメッセージからわかるように、toMatchElement
のデフォルトの待ち時間は 500ms に設定されています。この関数に timeout
オプションを追加して待ち時間を増やすことができます:
このアプローチは失敗テストの発生をある程度減らせるかもしれませんが、データが取得されるまでに必要な時間が不明であるため、問題を完全に解決するわけではありません。
したがって、一定の待ち時間が必要だと確信しているシナリオ、例えば「要素に 2 秒以上マウスをホバーするとツールチップが表示される」などのシナリオにのみ、このアプローチを使用します。
- アサーションを行う前にネットワークリクエストの完了を待つ
これは正しいアプローチです。データリクエストにどれだけの時間がかかるかはわからないかもしれませんが、ネットワークリクエストの終了を待つのは常に安全な選択です。この時点で page.waitForNavigation({ waitUntil: 'networkidle0' })
を使用して、ネットワークリクエストが完了するのを待つことができます:
このようにして、アプリとその読み込まれたデータに対するアサーションを実行する前に、ネットワークリクエストが既に終了していることを確実にできます。これによりテストが常に正しい結果を生み出すことが保証されます。