はじめに
こちらは バイセルテクノロジーズ Advent Calendar 2023 の 3 日目の記事です。
前日の記事は藤澤さんのアジャイルにおけるフロー効率を追い求めた結果、開発メンバーのエンゲージメントが低下したので改善した話でした。
少し早いですが、メリークリスマス!! テクノロジー戦略本部の尾沼です。
私が所属するチームでは、数ヶ月ほど前にPlaywrightによるE2Eテストを導入しました。今回は導入に関する話と、もしE2Eテストツール選定に迷っているならばPlaywrightがオススメであるということについて紹介したいと思います。
背景
私が所属するチームでは、出品管理SaaSを新規開発しています。システムは数多くの外部連携先を社外に有していて、APIを用いて連携しています。連携先の仕様に常に追随し続ける必要があるというのが特徴です。
そんな私たちのチームでは、いくつかの問題がありました。
1. 連携先の告知なしの変更に気づくことが遅れてしまう
私たちは、外部連携先の更新情報は常に追うようにしており、事前に告知があれば早めに対応しています。
しかし、連携先によっては告知が無い変更が発生することがありました。また、変更の告知はあるもののわかりにくく、キャッチしきれないといったことも発生していました。その結果として、対応が後手後手になっていました。
2. 外部連携部分のリグレッションを検知する仕組みがない
外部連携機能の中には、1処理の中で複数回社外の連携先とのAPI連携をしたり、外部サービスとの通信をするものがあります。そして、レスポンスの内容をもとに私たちのシステム上のデータを更新したり後続の処理に繋げます。
しかし、ユニットテスト内などでは連携部分はモック化しているため、外部連携先のAPIの挙動が変わったとしても検知できません。そのため、「ユニットテスト等では問題なく動くのに、実際に動かしてみたら動かない」ということが起きることがありました。
3. 個別のタスクの動作確認はしているが、他の箇所でのリグレッションを検知できないことがある
私たちは、Pull Requestのレビュー時にローカル環境での動作が問題なくできていることを示すルールを設けています。
しかし、この時点での動作確認はそのPull Requestの内容が問題ないことを担保する上で必要な最低限のものに留まっています。そのため、既存のテストで網羅しきれていない箇所に関しては、副作用としてリグレッションが発生してしまっても検知できない状態になっていました。
上記を解決するために、チーム内でどう解決するべきかの議論をしました。そして、解決策として「主要機能に対するE2Eテストの導入」を実施することにしました。毎日主要機能の動作、そして、その過程での外部連携が上手くいくことを確認し、常に正常にシステムが機能する状態を維持することを目指すことにしました。
E2Eテストに期待したこと
具体的にどのE2Eテストツールを導入するかを考える前に、E2Eテストを導入した後の理想状態を定義しました。
以下を実現した上で、
外部連携先の告知なし変更を検知する
主要機能に関して、フロントエンド〜バックエンドを通してリグレッションが発生していない
私たちのチームとシステムが以下の状態となっていることを期待し、選定に臨みました。
開発者がリグレッション対応やE2Eテスト環境維持に余計な工数取られない状態
私たちのシステムを用いて正常に出品管理業務を行えることを保証できている状態
どのE2Eテストツールを使うか
具体的なE2Eテストツールの選定を実施しました。E2Eテストツールは多種多様に存在しているため、選定候補のリストアップの基準を設ける必要がありました。今回は、社内での採用事例があるものに絞って選定することにしました。
実際に候補として考えたものは以下になります。
そして今回、私たちはPlaywrightを採用することにしました。
なぜPlaywrightにしたか
検討の際の観点はいくつかありました。結果として今回、私たちがPlaywrightを使おうと判断した理由をいくつか紹介したいと思います。
習熟が簡単そうであった
E2Eテストを長期的に運用していくにあたって、ツールの要件として習熟が簡単であることが挙げられました。そのため、ノーコードもしくは私たちが日常的に使用しているプログラミング言語で管理できることを観点とし選定に臨みました。私たちのチームはフロントエンドをTypeScriptで開発しています。また、負荷テストをJavaScriptを用いて実現した経験があります。従って、シナリオをコードで管理する形式のE2Eテストならば、TypeScriptもしくはJavaScriptで管理できることを条件とし、絞り込みを行いました。
そして、PlaywrightはJavaScriptやTypeScriptを用いてテストシナリオを記述できるため、私たちの要望に沿っていました。
画面の操作からシナリオのコードを自動生成できる
少しでもテストシナリオ作成の工数を減らしたいという視点でツールを選定していたため、ノーコードや自動コード生成、もしくはそれに相当する機能があることが望ましかったです。
PlaywrightにはTest generatorという、VSCodeを用いたコード生成機能があります。この機能を利用すればテストシナリオ実装者の工数を減らせるのではと考えました。
安い
Playwrightの利用にあたって発生する料金は、Playwrightを実行するインフラのみに発生します。SaaSのE2Eテストツールと比べ、この点は魅力に感じました。
上記のような理由に加え、チームのエンジニアにコードベースでメンテナンスをし続けるモチベーションがあったため、今回はSaaSではなくコードベースのE2Eテストツールを利用することにしました。最後はCypressとPlaywrightの2択で迷いましたが、Cypressのトレード・オフや社内でのヒアリング内容を考慮し、今回はPlaywrightを使うことに決めました。
構成紹介
Playwrightを採用した私たちE2Eテスト環境は以下のような構成となっています。
システム自体はGoogle Cloud上に構築しており、CI/CDプラットフォームはGitHub Actionsを採用しています。私たちのシステムのフロントエンドは特定のIPアドレスからのアクセスのみを許可するようにしているため、固定の外部IPを割り当てたCompute Engineを用いてself-hosted runnerを構築し、その中でPlaywrightを実行するようにしています。
E2Eテストは、GitHub Actionsの機能を用いて「業務開始前」と「メインブランチへのマージ」を発火条件とし、実行しています。
Playwrightの良かった点と惜しかった点
実際にPlaywrightを使ってみて「良かった点」と「惜しかった点」を紹介します。
良かった点
シナリオのコード化が自動生成によって効率良いものとなった
実際にTest generatorを用いて効率よくシナリオのコード作成を実施できました。
Test generatorはVSCode上から起動され、ブラウザを立ち上げつつレコーディングを開始します。起動したブラウザに対して、「クリックする」、「リンクを踏む」、「URLを直打ちしてページを開く」といった操作をすると、それらをPlaywrightのテストシナリオコードとして自動生成してくれます。
以下は実際にTest generatorを動かしてみた動画になります。動画内では、Playwrightの公式サイトにアクセスしています。ブラウザに対して操作をすると、それに応じたコードが同時に自動生成されていることがわかります。
※ 動画では最初からブラウザを立ち上げています。
私たちはブラウザを操作してコードを生成し、それを必要に応じてリファクタリングする形式でシナリオコードを作成しました。
エラーのデバッグが容易
Playwrightはテスト終了後にレポートを出力することができ、デバッグを容易に進めることができます。以下は先程のTest generatorのデモ動画にて作成したテストを実行した結果、出力されたレポートのキャプチャになります。
出力の設定をしておけば、レポートからTrace情報を得ることができます。Traceにはテスト実行時のブラウザに表示される情報が含まれているため、デバッグの役に立ちます。例えば、ブラウザのネットワークタブの情報は以下のように表示されます。
また、テスト結果をビデオとして出力することもできます。以下は先程Test generatorのデモ動画で作成したテストの結果を、ビデオとして出力したものです。エラーが起きた場合は、どこでエラーが発生したのかをビデオから直接確認することができます。
私たちのチームでは、エラーが起きたときにはビデオを残すようにしていて、E2Eテストが失敗した場合簡単にデバッグできるようにしています。
GitHub Actionsのワークフローに以下のようなステップを設置し、
# 省略 - uses: actions/upload-artifact@v3 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30 # 省略
Playwrightの設定として、失敗時にビデオを出力するように指定します。
export default defineConfig({ // 省略 video: 'retain-on-failure', // 省略 });
すると、失敗した際に以下のようにGitHub Actionsからレポートを得ることができます。
そして、レポートの中からビデオを得ることができます。
このおかげで、E2Eテストが失敗してもどこでエラーが起きたのかを迅速に突き止め、復旧させることができるようになりました。
惜しかった点
自動生成のコードの中には使いにくいものや、自動生成では対応できないシーンがある
便利なTest generatorですが、自動生成のコードをそのまま採用するのは少し厳しいと感じるシーンもありました。
私たちが経験した例を1つ紹介したいと思います。開発の過程で、以下のようなプルダウン群のうち「プルダウン3」をクリックするシナリオコードを自動生成したいというシーンがありました。
実際に「プルダウン3」をクリックすると、自動生成されるコードは以下のようになります。
await page.getByPlaceholder('選択してください').nth(2).click();
私たちは、インデックス番号を用いて要素を取得してしまっている点を問題と判断しました。ユーザーは何番目のプルダウンかを意識して操作を行うのではなくラベルを見て選択するはずです。また、インデックス番号での指定だと要素の順番が変わっただけでE2Eテストが成立しなくなります。そのため、E2Eテストがユーザーの動きを再現しつつも保守しやすくなるように、以下のようにラベルを指定しラベル配下の要素をクリックするように修正することにしました。
await page .locator('p', { hasText: 'プルダウン3' }) .locator('..') .locator('..') .locator('input') .click();
また、画面の要素が表示されるまで待機するシナリオコードなどは、自動生成では実現できないため自前で書く必要があります。
このように、自動生成のコードをそのまま使いにくいシーンがあることや、自動生成では補えない部分が存在することについては少し惜しいなと感じています。
結果
以下のように業務開始前にE2Eテストが実行されるようになりました。
私たちは、特に外部連携の発生する機能に対して優先的にテストシナリオを実装をしました。 そして、毎日外部連携処理が実行されることを担保できるようになったため、連携先が告知無しで変更したとしても1日以内に検知できるようになりました。 また、日々の開発の中でリグレッションが発生し、それをPull Requestを出した際の動作確認で検知しきれなかったとしても、E2Eテストという最後の砦のおかげで検知できうるようになりました。
PlaywrightのおかげでE2Eテスト環境のメンテナンスも快適に行えるようになり、結果として、当初掲げていたE2Eテストを導入した後の理想状態を実現できました。
終わりに
いかがでしたでしょうか? E2Eテストツールは様々な選択肢があると思いますが、もしPlaywrightを検討している人がいたら、私たちチームの例が参考になれば幸いです。ぜひPlaywrightを使ってみてください。
最後に、バイセルではエンジニアを随時募集しております。興味のある方はぜひ以下の採用サイトをご覧ください。
明日のバイセルテクノロジーズ Advent Calendar 2023は野口さんの手軽に Elasticsearch と RDB の型不整合を防ごう ~ dynamic templates を使った実装例 ~です、そちらもぜひ併せて読んでみてください!