LoginSignup
3
1
お題は不問!Qiita Engineer Festa 2023で記事投稿!

【Playwright】一つのテスト内で2つの連続するスクリーンショットを比較・差分検出する

Last updated at Posted at 2023-07-17

はじめに

E2Eテストのテスティングフレームワークとして、Playwrightを2023年に入ってから実務に導入しています。

Locator周りのヘルパー関数から、Auto-wait、トレース、並行処理にuiモード、至れり尽せりのPlaywrightは、Visual Comparison、すなわちスクリーンショットの比較もout-of-the-boxでできます。

...できますが、実際にやってみると、豊富な機能の森に迷って、簡単なことで意外に手こずることもあります。


先日、本番環境とステージング環境1で修正の戻りがないか、すなわちVisual Regressionテストを行おうとしました。

実装したい手順は、

1. ステージング環境にアクセス
2. ステージング環境のスクリーンショット
3. 本番環境にアクセス
4. 本番環境のスクリーンショット
5. アサーション

と明瞭です。

が、この実装に詰まりました。

ということで、Playwrightを使って一つのテスト内で2つの連続するスクリーンショットを比較する方法を、NG例を添えて記録します。

結論

  • 1回目のpage.screenshotのpathにtestInfo.snapshotPathを用いて保存場所を指定する。
  • MatcherのtoMatchSnapshotに引数をつける。
コード例
test("ページAとページBのスクリーンショット比較", async ({ page }, testInfo) => {
  await page.goto("<ページAのURL>")
  await page.screenshot({ path: `${testInfo.snapshotPath("result.png")}`, fullPage: true })

  await page.goto("<ページBのURL>")
  expect(await page.screenshot({ fullPage: true })).toMatchSnapshot({ name: "result.png" })
})

ポイント

testInfoで保存先のPathを指定する

今回の実装の要は、1枚目と2枚目のスクリーンショットを同一のファイル名にすること。

単純に

await page.screenshot({ path: "result.png", fullPage: true })

expect(await page.screenshot({ fullPage: true })).toMatchSnapshot({ name: "result.png" })

とすると失敗します。screenshottoMatchSnapshotでは引数に与えたパスの扱いが異なるからです。

つまり上記の例だと、page.screenshotはプロジェクトルートに画像を作成するのに対し、toMatchSnapshotはデフォルトで、example.spec.ts-snapshot/testName-chromium-darwinと、<テストファイル名>-snapshot/<テスト名>-<ブラウザ>-<環境名>を参照します。

ここで使えるのが、「testInfo」です。

testInfosnapshotPathプロパティを使うと、Matcherで参照しているパスが取得できます。

なお別解として、Matcherが参照するパスはplaywright.config.tssnapshotPathTemplateで指定できるので、こちらを編集することも考えられます。

Matcher

アサーションのMatcherにはtoMatchSnapshotを使いましたが、toHaveScreenshotで代置可能です。ただし、引数と同期非同期が異なります。

toMatchSnapshotの場合

1枚目のスクリーンショットのpathに合わせて、nameプロパティを引数として渡します。

expect(await page.screenshot({ fullPage: true })).toMatchSnapshot({ name: "result.png" })

toHaveScreenshotの場合

こちらでも可能です。引数の形が異なるので注意してください。Pageクラスに対するアサーションになります。
また、toHaveScreenshotはPromiseを返すweb-specific matcherなので、expectの前にawaitが必要です。

await expect(page).toHaveScreenshot("result.png", { fullPage: true });

以下、試してみてダメだったNG例を供養します。なむなむ。

NG集

toMatchSnapshot / toHaveScreenshotを引数なしで2回実行

await expect(page).toHaveScreenshot()
// ページ遷移
await expect(page).toHaveScreenshot()

or

expect(await page.screenshot()).toMatchSnapshot()
// ページ遷移
expect(await page.screenshot()).toMatchSnapshot()

これら二つのMatcherは同一テスト内で連続して引数なしで実行されると、

  • <テストファイル名>-snapshot/<テスト名>_1-<ブラウザ>-<環境名>
  • <テストファイル名>-snapshot/<テスト名>_2-<ブラウザ>-<環境名>

と序数がついて、別ファイルとして保存され、比較はされません。

toMatchSnapshot / toHaveScreenshotを引数ありでパス指定して2回実行

await expect(page).toHaveScreenshot("result.png")
// ページ遷移
await expect(page).toHaveScreenshot("result.png")

or

expect(await page.screenshot()).toMatchSnapshot({ name: "result.png" })
// ページ遷移
expect(await page.screenshot()).toMatchSnapshot({ name: "result.png" })

引数で画像名を指定することで、連続するスクリーンショットを比較対象にすることができます。

ただし、2つのMatcherは初回実行など、画像が存在しない場合は画像を作成しますが、すでに存在している場合は、最初のexpectで比較が行われてしまいます。

したがって、beforeEach等で画像の削除をしたり、都度--update-snapshotsフラグをつけたりする必要がありそうでNGとなりました。

最も正しそうなやり方に見えるので、何らかスマートな設定方法があるかもしれません。

screenshot(画像名指定) => toMatchSnapshot / toHaveScreenshot

await page.screenshot({ path: "result.png" })
// ページ遷移
expect(await page.screenshot()).toMatchSnapshot({ name: "result.png" })

自然に見えますが、前述の通り、page.screenshotpage.toHaveScreenshotでデフォルトのパスが異なるのでNGです。1回目と2回目で別のパスを参照してしまいます。

BufferをtoEqualで比較

page.screenshotはBuffer(正確にはPromise)を返します。これを比較するのも一案。

const production:Buffer = await page.screenshot()
// ページ推移
const staging = await page.screenshot()
expect(staging).toEqual(production)

試してみましたが、一致する場合は問題なくテストできましたが、一致しない場合にフリーズしてしまいました。

下記IssueのコメントによるとBufferの差分が出るとのことですが、いずれにせよ差分の場所がわからないので、実用に堪えません。

参考文献

Playwrightドキュメント

Issue

  1. ここでは本番と同じDBを参照している開発環境、を指します。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1