🖥

まとめ – Playwright による E2E テスト + VRT差分レポートを活用する

2024/05/08に公開

前提

  • フロント ( Nuxt React など )
  • バックエンドAPI ( Rails Node など )

というようなフロントバックエンド構成のWebサービスの話

E2E テストは何が良いのか?

世の中のテストの種類は

  • E2E 的なテスト
  • 部品のテスト ( 関数やComponent など )

に大きく分かれると思うが、自分の場合はサービスの最終的な品質保証がしたいので、後者のE2Eテストを重視している

そもそものE2Eテストのメリットだが、何より「ほとんど全てをテストできる」というところが良い。
フロント・バックエンド構成で言えば、その全てを合わせた結合テスト、一連のテストが出来るのだ。

Playwright で E2E テストをしよう

E2E テストには Playwright というテストフレームワークを使っている

https://playwright.dev/

ヘッドレスでブラウザ操作をしてその結果をテストしたり、画面のスクリーンショットを撮影したり、操作動画を撮影したりできる

たとえば

  • URL指定でログインフォームにアクセスする
  • ログインフォームに正しくアクセスできていることを検証するために、画面内に表示されるべきテキストが表示されているかを検証する
  • メールアドレスとパスワードを入力してログインボタンを押す
  • ログイン後の画面に期待通りの文字列が表示されているかを検証する

というようなテストを組めるのだ
これがPlaywrightによる基本的な検証だ

ログイン部分のコード例:

await page.goto(`http://example.com/login_form`)

await expect(page.getByText('これはログインフォームです')).toBeVisible()

await page.getByLabel('メールアドレス').fill('メールアドレス')
await page.getByLabel('パスワード').fill('パスワード')
await page.getByRole('button', { name: 'ログイン' }).click()

await expect(page.getByText('あなたはログインに成功しました')).toBeVisible()

他のテストフレームワークに比べて何が良いのか?

自分の場合は他には Ruby の capybara ぐらいしか使ったことがなかったが、何かと不安定でトラブルが多かった気がする。
Playwrightはそれに比べるとトラブルが少なくて、コードも書きやすい印象がある。

あとは

  • テストケースファイルを Typescript に出来るので、型管理されてる状態でテストコードが書ける。何が良いかというと、やはり実行前に間違いが分かるので書きやすい。
  • ヘッドレスで実行しておいて、だが動画キャプチャも撮影するということができる。デバッグがしやすい。

どのような環境に対して実行できるか?

  • グローバルな環境に対して実行できる。たとえば http://example.com に対するテストは実行できる。
  • localに対して実行できる。 あらかじめ http://localhost/ とかにWebサービスを起動しておけば、そのlocalhostに対してそのままテストが実行できる。
  • BASIC認証がかかっているサイトに対しても実行できる。( 設定でユーザ名・パスワードを指定する )
  • IP制限がかかっているサイトに対しては、Playwright 実行元マシンのIPが許可されていれば普通に実行できる。

フロントテストは壊れやすくないか?

たまに工夫が必要な場合があるが、そのたび対処している。

特にテスト内でスクリーンショット撮影+VRTでの差分レポート出力をする場合は、スクリーンショット画像の揺れに対処するのにややコストがかかる場合があった。

要素指定などがやりづらくないか?

data-testid というものをフロント実装のコードに割り振っておけば、その id でボタンや他要素を指定できる。

例:

フロント実装のコード

<button data-testid="directions">Itinéraire</button>

Playwright のテストコード

await page.getByTestId('directions').click()

なので画面上に例えば「送信」というボタンが複数ある場合でも、 data-testid で別のIDを割り振っておけば簡単に片方のボタンを特定できる。
テストのために実装のコードを少し変える必要があるが、実装自体に影響があるわけではないので、許容範囲だろう。

また「同じdata-testidが割り振られている中で最初の要素」「2番目の要素」などという柔軟な指定もできる。

参考: https://playwright.dev/docs/locators

ファイルアップロード操作はできるか?

できる。FileChooserのサンプルを参考に。

参考: https://playwright.dev/docs/api/class-filechooser

ログイン認証が必要なページをテスト対象にする場合、テストケース単位で毎回ログインが必要か?

ステートファイルというものにログイン状態 (Cookieなどの状態) を記録しておけば、ログイン処理は1回だけで済む。

残りのテストでは記録済みのステートファイルを呼び出すだけで良い。

コード例:

書き込み

await page.context().storageState({ path: 'path/to/state-file.json` })

呼び出し

test.use({ storageState: 'path/to/state-file.json` })

CI で実行できるか?

できる。

ひとつ問題になるのはデータベースやバックエンドAPIとのデータ通信部分をどうするかだろう。
これは理想としてはありのままが良いと思う。

つまり

  • データベースサーバーを起動する
  • APIサーバーを起動する
  • テストに必要な初期データを生成する
  • あとはPlaywrightで普通にフロントのテストをする

という流れだ

ただしこの場合、テストケースがそれぞれ依存し合う可能性があるため、テストケースの書き方は多少難しめになる。
たとえばテストケースA -> テストケースBの順番で実行した場合と、その逆の場合では、結果が異なる可能性があるからだ。

ここは「それがE2Eテストだ」と割り切って、E2Eテストとして適切なテストケースを書いて行くと良いだろう。

参考

Playwright + Github Actions で CI上の local Web 環境に E2Eテストを実行する例 ( フロント + バックエンドAPI構成 )
https://qiita.com/YumaInaura/items/68d4eae10c8c7ad11f0b

モック機能

次善の策として Playwright本体 の fulfill というモック機能を使えば、 リクエスト部分をモック出来る。

https://playwright.dev/docs/api/class-route#route-fulfill
https://playwright.dev/docs/api/class-route#route-fetch

全てのAPIリクエストをモックしておけば、データベースの起動もAPIサーバーの起動も必要なくなり、テストケース単位での依存もなくなる。
ただデメリットとしてモックの値の管理が大変なのと、モックしている分、品質保証のレベルは下がってしまい、片手落ちのテストになってしまう。

できればモックは極力使わずにあくまでE2Eとしてのテストを書いておきたいところだ。
(外部連携でモックしないと難しい部分は除外するとして)

テスティングトロフィーに反さないか?

E2Eテストを手厚くするのは、テスティングトロフィー的には良くないのではないか? という考えもあるだろう。
テスティングトロフィーというのは「ユニットテスト、結合テストを手厚くして、E2Eはおまけ」的な考え方だ。

しかし人の考えはあくまでも考えにすぎない。
自分の場合は「いくら部品のテストを積み重ねても最終的な品質は保証できない」という観点から、E2E寄りのテストを重視している。

https://rightcode.co.jp/blogs/40957

試してみよう

Playwrightnのインストール

npm install playwright

Playwrighの初期設定

npm init playwright@latest

質問には適当に答える

✔ Do you want to use TypeScript or JavaScript? · TypeScript
✔ Where to put your end-to-end tests? · tests
✔ Add a GitHub Actions workflow? (y/N) · false
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true
Initializing NPM project (npm init -y)…

Playwright 実行

初期設定時にサンプル用のテストケースファイルが用意されているので、そのまま実行できる

npx playwright test

結果レポートを見る

npx playwright show-report

動作撮影を試してみる

初期セットアップで作成された tests-examples/demo-todo-app.spec.ts' により豊穣なテストケースのサンプルが用意されてるので、それを実行してみる

デモ用のテストケースファイルをテスト対象ディレクトリにコピーする

cp tests-examples/demo-todo-app.spec.ts tests/demo-todo-app.spec.ts

Config ( playwright.config.ts ) を以下のように変える

  • 動画撮影をオンにする
  • 少し操作スピードを落とす ( slowMo )
  use: {
    trace: 'on-first-retry',
    video: 'on',
    launchOptions: {
      slowMo: 200,
    },

サンプルテストを実行する

npx playwright test tests/demo-todo-app.spec.ts

結果レポートを見る

npx playwright show-report

結果レポートで動画キャプチャを直接再生することができる


なお動画ファイルは ./test-results ディレクトリに記録されている

video.webm

他のTIPS

Playwright で HTML を直接書き換える例 ( DOM 操作 ) ( Evaluating JavaScript )

https://github.com/YumaInaura/YumaInaura/issues/3606

fullPage 指定で全画面のスクリーンショット撮影をすると、カーソル位置がぶれてスクリーンショット画像がぶれる場合がある。
Playwrighではブラウザサイズをやりくりして全画面用スクリーンショットを撮影している気がする。この対策としてスクリーンショット撮影前にカーソル位置を戻す処理を書いたりしている。

Playwright – FullPage指定のスクリーンショット撮影で意図しないマウスオーバー効果・ブラウザサイズ変更が起こってしまう

https://qiita.com/YumaInaura/items/38291bb4c8f81dc47446

追加でVRT(ビジュアルリグレッションテスト)利用する

Playwrightのテストが成功しただけでは、ビジュアル面でレイアウト崩れがあった時などには検知できない

このときのためにVRT(ビジュアルリグレッションテスト)もおこなっている

その方法は、まず Playwright のテストケース中で、任意のタイミングで操作中のスクリーンショットを撮影する。

例:

// ---  いろいろな操作 --- 

await page.screenshot({ path: 'screenshot1.png' })

//  --- いろいろな操作 --- 

await page.screenshot({ path: 'screenshot2.png' })

https://playwright.dev/docs/screenshots

そのスクリーンショット画像群を reg-suit というVRT用のライブラリで比較して、差分レポートを作成するという方法だ

https://reg-viz.github.io/reg-suit/

スクリーンショット画像群が2個あれば差分レポートが生成できる。
つまり、まずはあるタイミングでスクリーンショット群を撮影しておいて、なにか実装に変更を加えた後に、もういちどスクリーンショット群を撮影すれば良い。

この差分レポートでは BEFORE / AFTER ( EXPECTED / ACTUAL ) で画像の差分を比較して、差分が検知された画像だけを教えてくれるし、差分があった場合は何が変化しているのかが一目瞭然でわかる。

なお、正常系のデザイン変更をした時にも差分が検知されるが、これは人が目で見て「問題のない差分だ」と判断すれば良い。

公開日時

2024-05-02

Discussion