PlaywrightでWordPressのe2eテストを行う

前回に引き続き、タロスカイ社内用の教育文書を世に放つシリーズです。今回の登場人物はe2eテストとPlaywrightです。
e2eテストとは?
e2eでEnd to Endの略語で、エンドユーザー(?)の操作に始まり最終的な出力(エンド?)を確認すること、要するに実際のユーザーの操作と表示される結果(画面)までをテストすることを指します。語源についてはあまり自信がないです。
以前はシナリオテストなどと呼ばれることも多かったですが、少なくともソフトウェアの世界で自動化されたシナリオテストはe2eテストと呼ぶことが多い印象です。たとえば、ECサイトにおいて「ユーザーが商品をカートに追加してから購入を完了するまで」をテストするのがe2eテストです。
このテストの過程では実際にブラウザが動作するのですが、Headlessブラウザ(画面を立ち上げないで裏側で動作するブラウザ)が登場したことで劇的に使いやすくなりました。2017 年ぐらいでしょうか、Chrome Headlessが登場したことをきっかけにFirefoxやWebkitもHeadlessモードが動くようになります。これ以降、気軽にe2eテストが行えるようになりました。
Playwrightとは?
e2eテストのフレームワークは色々あり、古くはSeleniumやBehatなどがあったのですが、なんにせよセットアップが大変な上にすぐ動かなくなるのでテストをメンテするのも一苦労でした。Javaやらを入れないとならず、動作しているときもFirefoxがガチャガチャ動いて怖かったです。とにかく根性が必要でした。
ヘッドレスブラウザを使えるようになったPhantomJSあたりからだんだん敷居が低くなり、現在はPlaywrightが一番人気っぽいです。SeleniumはPythonと一緒によく使われてるみたいですが。
また、WordPress開発者がPlaywrightを使うメリットとしては、WordPress公式に採用されているからです。これは後述します。
使い方としてはNPMでパッケージをインストール npm install --save-dev @playwright/test
したら、 testsディレクトリに なんちゃら.spec.js
という名前でファイルを置いておきます。内容的には以下のような感じです。
const { test, expect } = require( '@playwright/test' );
test('basic test', async({page}) => {
// ページに移動して……
await page.goto( 'https://playwright.dev/' );
// titleタグにPlaywrightが含まれているか確認
await expect(page).toHaveTitle(/Playwright/);
} );
このように、ブラウザの操作をJavaScriptで書いて、実行します。たとえば、「DOM上に特定の要素があることを保証したい」となると、次のような感じになります。
test('セレクターで要素の表示確認', async ({ page }) => {
await page.goto('https://playwright.dev/');
// セレクターでheaderタグを取得する
const headerByTag = page.locator('header');
// 表示されていることを確認
await expect(headerByTag).toBeVisible();
} );
このメソッドにどんなものがあるのかはAPIリファレンスで確認できます。なんなら私より生成AIの方が詳しい可能性があるので、生成AIに聞くとよいでしょう。
こんな感じで色々と書いていこうと思ったのですが、ちょっと大変なので、タロスカイの学習用リポジトリ tarosky/my-first-playwright をご覧ください。CLINEとのコラボで2時間ぐらいかけて作りました。クローンして一個ずつテストを実行してみると、動作の感じがわかると思います。
WordPressはPlaywrightをどのように実行しているか?
WordPressのGutenbergプロジェクトでは、e2eテストをすべてPlaywirghtに移行し、そのためのユーティリティツール @wordpress/e2e-test-utils-playwright もメンテナンスしています。WordPressにログインしたり、エディターでブロックを挿入したりといった複雑な操作を行うためのユーティリティツールになっています。
Gutenbergリポジトリでは、test/e2e/specs/editor/blocks あたりが参考になるでしょう。たとえば見出しブロックのテストなどはこちらになります。たかだか見出しブロックで500行以上もテストを書くということの重みについては後述します。
コア・コントリビューターの浜野さんも紹介記事「ブロック開発にe2eテストを導入してみる」を書いているので参考にしてください。
GitHub Actionsやローカル環境ではどうやって実行するか?
さて、このテストは継続的に行われないと意味がないのですが、基本的には「WordPressが動く環境」が必要です。ローカル環境ならなんでもよいのですが、チームなどで共有する場合、たとえばLocal(この名前どうにかないか)なら「チームメンバーでローカルドメインを揃える」などの作業が必要になります。また、GitHub ActionsのようなCI/CDツールを利用する場合、「CI/CDのためのWordPress環境」を作ってあげないといけません。これはめんどくさいですね!
そう考えると、いまのところは「自前でDockerを作る」「@wordpress/env(wp-env)を使う」の2択かなぁ、と愚考します。この「wp-envを立ち上げてGitHub Actionsでテストを走らせる」の部分は先ほど紹介したサンプルコードで試しているので、動作確認してみてください。プラグイン開発ならwp-envで十分だと思います。
e2eテストはどんな粒度で行うべきか?
まず、e2eテストはテストコードを考えるのも書くのも大変な上に、テスト自体に時間がかかります。「WordPressと実行に必要なミドルウェアを用意して立ち上げる」「ブラウザをインストールして起動する」の2つだけでもそりゃ大変だろうな、と思いますね。で、このテストを毎時間実行してよいかというと、そうもいかないですね。以下のようなことを意識するとよいようです。
- クリティカル・パスを決定する
- スモーク・テストを行う
- ユニットテストで代替できないか検討する
まず、クリティカル・パスとは決定的な動線のことです。ユーザーがそのサービスなりサイトなりを利用する上で絶対に外せない重要な点をまずe2eテスト対象とします。ECサイトなら「購入が完了すること」で、会員制メディアサイトなら「会員登録できること」「会員登録していればコンテンツを閲覧できること」になります。これらのクリティカル・パスからカバーしていけば、プロダクション・デプロイメントも安心して行えます。
続いて、テスト全体だけではなく、致命的なものだけをテスト対象とする「スモークテスト」を採用します。この概念はハードウェア業界から来たようで、「電源を入れて煙が上がらなければオッケー」というレベルのテストを指すようです。回路系ではハンダが燃えるらしいですね。たとえば、masterブランチにプルリクエストをマージするときはスモークテスト、プロダクションにリリースする前に完全な回帰テスト(フル・リグレッションテスト)を行う、など粒度を分けるとよいでしょう。
そして、もう一つ大事なのが、「e2eテストを導入すれば全部オッケー!」と考えないようにすることです。たとえば、PHPUnitやJestなどのユニットテストを使うことで、致命的な誤りは防ぐことができます。コロナ禍のときに「スイスチーズ・モデル」という概念が流行りましたが、プログラミング開発も同じです。カバレッジにこだわることなく、多様な視点から品質を担保しましょう。
*
以上、WordPress開発でe2eテストを導入する方法を紹介しました。「自動テストの結果だけ届いてオールグリーン」は万能感がすごいので、自己肯定感が爆上がりすること請け合いです。