こんにちは。システム開発一部の吉田です。
最近初めてパーマをかけてみたら元々の天パと掛け合わさって髪がモジャモジャになりました。気に入ってはいますけど取れるのがいつになるのか心配です。
絶賛開発中のフロントエンド領域でPlaywrightとmswを用いたE2Eテスト(End to End Test)の導入をしました。これらのツールの導入に至った経緯と簡単な使い方をご紹介します。
以前書いたモックサーバーの記事の内容も含んでいるので、ご覧になっていない方は以下を参照してみてください。
E2Eテストのスコープ
E2Eテストの概要はCircleCIの記事を見るとなんとなくわかった気になれます。
一言で言うと、E2Eテストとはユーザーが実際に操作する状況をシミュレートし、アプリケーション全体の動作を検証するテスト手法のことです。
アプリケーション全体がテスト範囲なので、結合環境などでフロントエンド~バックエンド~DBを接続した形でE2Eテストを実施することも可能です。 今回のプロジェクトでは実験的な導入のため、E2Eテスト実行時にバックエンドと接続することはしていません。モックのレスポンスを使い、単体環境でのフロントエンドアプリケーションの動作のみを検証することとしました。
Playwrightについて
Playwrightは、Microsoftが開発したE2Eテストツールで、Chromium、Firefox、Safariの各ブラウザで動作します。それぞれのブラウザに最適化されたAPIを提供し、高速で信頼性の高いテストを実現します。
Playwrightの他にもSelenium, Puppeteer, CypressなどE2Eテストツールには結構種類があります。 正直な話スケジュールにあまり余裕がなかったので、今回の導入の際に各ツールの比較をあまりしませんでした。 しかし、以下の理由からE2Eテストを導入するのであればPlaywright一択だと考えています。(2023年現在)
- 複数のブラウザでのテストが容易
Playwrightは、複数ブラウザに対応しており、同じコードで複数のブラウザでのテストを行うことができます。Puppeteer, CypressはChromiumベースのブラウザに特化しているため、他のブラウザでのテストが不得手です。 - 高速な実行速度
Playwrightは、モダンなJavaScriptエンジンを利用しており、他に比べて高速なテスト実行が可能です。テストの並列実行もサポートされていて、効率的にテストを実行することができます。 - 豊富なAPIと柔軟な設定
タッチやマウスでのドラッグ&ドロップなどのネイティブな操作をシミュレートする豊富なAPIを提供しており、ユーザビリティやアクセシビリティのテストにも適しています。また、ネットワークやデバイスのエミュレーションもサポートしています。Playwrightに比べるとPuppeteerはAPIが限定です。 - 単体でVRT(ビジュアルリグレッションテスト)ができる。
PlaywrightのAPIを利用することで、簡単にスクリーンショットを取得し、以前のバージョンと比較して差分を検出することができます。これにより、CSSやレイアウトの変更が意図しない影響を及ぼしていないか、また新しい機能の追加が他の部分に悪影響を与えていないかなどを確認することが可能です。(私たちのプロジェクトではVRTは実施していません。)
決め手となったのは1の複数ブラウザでのテスト実施が可能という点。マネックスが定義しているブラウザの推奨環境にはFirefox, Safariも含んでいるので、複数ブラウザでのテストが必須条件でした。
2と4の理由についてはこちらの記事の解説がわかりやすかったです。
既存のモックサーバーの落とし穴
検証としていくつかテストケース作成し、導入当初はこれといった問題もなくテスト実行できていました。この時はNuxt3のアプリとPrism + Caddyを使ったモックサーバーを起動した状態にしてPlaywrightのテストを実行する形としていました。
けれども、APIからエラーレスポンスが返ってきたときの動作、異常系のテストをするときに問題が発生。上記の構成だとテスト中に正常レスポンスからエラーレスポンスを返すように切り替えることができません。切り替えにはモックサーバーの定義の書き換えと再起動の手動対応が必要なためです。
ケースを正常系と異常系のテストを分けて...ということも考えましたが、ケース作成が煩雑になります。手動対応ってスマートじゃないですし。
特定のテストケースだけ、かつコードベースでAPIのレスポンスを書き換えるためにmsw(Mock Service Worker)を追加で導入することにしました。
mswについて
mswは、名前の通りService Workerを用いたモックライブラリです。Service WorkerとはWebページとブラウザの間に位置するスクリプトで、バックグラウンドで実行されるJavaScriptワーカーのことを指します。mswを使うことによってAPIリクエストを捕捉、インターセプトすることでカスタマイズされたレスポンスを返すことができます。これをPrism + Caddyのモックと組み合わせて使い、テストケース単位でレスポンスを書き換えていきます。
playwrightのテスト内でmswにアクセスする、テストコードからmswを操作するには以下のライブラリが必要なので注意。
Playwrightのテスト内で@playwright/testのテストをそのまま使っていた部分を書き換えます。任意のディレクトリにtestUtil.tsに定義します。
import { test as base, expect } from '@playwright/test'; import type { MockServiceWorker } from 'playwright-msw'; import { createWorkerFixture } from 'playwright-msw'; import handlers from './handlers'; export const test = base.extend<{ worker: MockServiceWorker; }>({ worker: createWorkerFixture(handlers), });
テストコードで↑をインポートするように書き替えます。
import { test } from './testUtil'; import { rest } from 'msw' test('GET /api/hello returns 400', async ({ page, worker }) => { // Define the response for the "/api/hello" route worker.use( rest.get('/api/hello', (req, res, ctx) => { return res( ctx.status(400), ctx.json({ error: 'Bad Request', }), ); }), ); // Run your test await page.goto('http://localhost:3000'); // ... do some actions, check the result });
上記はテストコード内でGET /api/helloにアクセスしたときに400のレスポンスコードが返すようにする例です。
ケース内でworkerを引数に追加して、use関数を使ってレスポンスを定義します。Playwrightのテスト実行中にヘッドレスPrismへのリクエストをService Workerがインターセプトしてレスポンスを返し、モックサーバーの再起動をすることなく異常系のテストを実行できます。
おわりに
モックサーバーにmswを加えたことで、手動の工程を挟むことなく、スマートにE2Eテストを実行できるようになりました。
今回紹介したPlaywrightとmswの組み合わせは、環境構築の一例です。 mswはE2Eテストにおいて便利ですが、レスポンスを自分で定義しないといけないという弱点があります。その点、PrismではOpenAPIの定義から自動的にモックサーバーを作ってくれるのでその手間が省けます。 加えて開発中にAPI定義とモックのレスポンスが乖離してしまう...ということもありません。mswを使う上で乖離を防ぐ仕組みは考慮する必要があります。
私の所属するチームでは今のところE2Eテストだけmswを使うように周知しています。チーム内の混乱を避けるために、ツールを導入する際には内部でその使い方について認識を合わせておくことが大切かもしれませんね。あとドキュメント化も。
VRTやCI/CDで環境をプロビジョニングしてE2Eテスト...などもPlaywrightを使ってやっていきたいと考えています。E2Eテストでの品質保証にご興味のある方もない方も、ぜひ以下の募集をご覧ください。