Nuxt3 で VRT を導入する(Playwright 編)
前提
今回の Nuxt3 における VRT ではコンポーネント単位の VRT ではなく、ページ単位の VRT になります。
そのため、コンポーネント単位で VRT を実施したい場合は、本記事はあまり参考にならないので、ご注意ください。
はじめに
現在フロント側のテスト戦略を検討しているのですが、そちらに対し、いろいろ検証が必要でした。
その検証の1つとして、今回の Playwrigth での VRT があり、そちらにとても苦労したので、記事にしようと思いました。
Nuxt3 での VRT はあまり記事がないので、もし参考にされる方は参考にしていただければと思います。
成果物
こちらは下記のリポジトリになります。
VRT 導入完了後の所感
正直に言って、Playwright での VRT 導入は、まだ早いかも? と思いました。
というのも、ハマりポイントが多すぎたためとなります。
どれほど多かったのかは、この後に説明させていただきます。
完了までの要点
こちらは下記になります。
- Playwright での VRT では Nuxt の SSR モードを off にする
- Playwright の Docker image を使用して VRT 用のスクリーンショット画像生成する
- Github Actions で VRT を実行する際に差分検知させないため
- VRT で使用する Rest API のデータは MSW で実装
- VRT の対象はページの画面単位
上記の要点を踏まえて、導入手順をまとめていきたいと思います。
Playwright のインストール
公式にある通りの手順でインストールを実施します。
npm init playwright@latest
自分の実行時は playwright のバージョンは v1.42.0
でした
上記コマンドを実行すると下記のファイル達が生成されます。(デフォルトで生成されるファイル達)
playwright.config.ts
package.json
package-lock.json
tests/
example.spec.ts
tests-examples/
demo-todo-app.spec.ts
デフォルトで、作成されるテストファイルなどは丸っと削除しました笑(元々テストのディレクトリ構成は決めていたため)
Playwright の設定ファイルを追加
設定は下記のようにしました。
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
/* ファイル内のテストを並行して実行する */
fullyParallel: true,
/* 誤ってソースコードにtest.onlyを残してしまった場合、CIでのビルドに失敗する。*/
forbidOnly: !!process.env.CI,
/* CIのみリトライ */
retries: process.env.CI ? 2 : 0,
/* CIで並列テストをオプトアウトする。*/
workers: process.env.CI ? 1 : undefined,
/* 使用するレポーター。https://playwright.dev/docs/test-reporters を参照。 */
reporter: 'html',
/* 以下の全プロジェクトで設定を共有。https://playwright.dev/docs/api/class-testoptions。 */
use: {
/* `await page.goto('/')` のようなアクションで使用するベースURL。 */
baseURL: 'http://127.0.0.1:3009',
/* 失敗したテストを再試行するときにトレースを収集します。https://playwright.dev/docs/trace-viewer を参照。 */
trace: 'on-first-retry',
/* ユーザーのタイムゾーンをエミュレートする。 */
timezoneId: 'Asia/Tokyo',
/* コンテキストのすべてのページで使用されるビューポート。 */
viewport: { width: 1_680, height: 1_050 }
},
/* 主要なブラウザ用にプロジェクトを構成する */
projects: [
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }
},
{
name: 'safari',
use: { ...devices['Desktop Safari'] }
},
/* ブランドのブラウザでテストする。 */
{
name: 'edge',
use: { ...devices['Desktop Edge'], channel: 'msedge' }
},
{
name: 'chrome',
use: { ...devices['Desktop Chrome'], channel: 'chrome' }
}
],
/* テストを開始する前に、ローカルの開発サーバーを実行する。 */
webServer: {
command: 'PORT=3009 npm run preview',
url: 'http://127.0.0.1:3009',
reuseExistingServer: !process.env.CI
}
})
この時点で、結構 Playwright に感動してました。
というのも Web Server の設定欄が設けられていたり、テストしたいブラウザの種類も豊富だったり、機能が充実しているなぁと思うことが多かったためです。
上記は Playwright の共通設定ファイルとしました。
Playwright は E2E なども実装できるので、今後そちらを導入した時に E2E 用の設定ファイルなどを作成できるようにするためとなります。
今回は VRT の設定ファイルになるので、下記のファイルを追加しました。
import { defineConfig } from '@playwright/test'
import commonConfig from './playwright.config'
export default defineConfig({
...commonConfig,
/* 設定しているファイルパターンにマッチするもののみテスト対象となる */
testMatch: 'index.vrt.ts',
/* VRT 実行時に保存するディレクトリとファイル名の定義 */
snapshotPathTemplate:
'{testFileDir}/__screenshots__{/projectName}/{arg}{ext}'
})
上記の設定における snapshotPathTemplate
に関しては、例えば下記のディレクトリにある VRT テストを実行した場合
import { test, expect } from '@playwright/test'
test.describe('トップページ', () => {
test('初期表示', async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('domcontentloaded')
await expect(page).toHaveScreenshot()
})
})
test.afterEach(async ({ page }) => {
await page.close()
})
下記のようなスナップショットファイルが保存されます。
pages/__screenshots__/{chrome|edge|firefox|safari}/トップページ-初期表示-1.png
つまり、snapshotPathTemplate
は下記の構成になります。
- testFileDir
- テスト実行時のディレクトリパス
- projectName
- ブラウザ種別
- arg
- 要はテストケースをつなげたもの
- ext
- 画像の拡張子
そのほかにもいろいろパラメータがあるので、気になる方は見ていただければと思います。
スクリプトの追加
上記設定までできたら package.json にスクリプトを追加します。
"scripts": {
"test:vrt": "playwright test -c playwright-vrt.config.ts",
"test:vrt:us": "npm run test:vrt -- --update-snapshots",
"test:vrt:ui": "npm run test:vrt -- --ui"
}
MSW の追加
最新の記事情報を表示する本アプリケーションでは、画面のスクリーンショットを取得する際、データはモックデータでないと、毎回違いスクリーンショットになってしまうため、モックデータを使用する必要がありました。
インストール
こちらは公式に従ってインストールを実施します。
npm install msw@latest --save-dev
次に、ワーカースクリプトの作成を行います。
こちらも公式にある通り、実施を行います。
npx msw init ./public --save
こうすることで、public ディレクトリにワーカースクリプトが配置され、package.json に msw に対して、ワーカースクリプトの配置場所を指定するソースが自動挿入されます。
playwright-msw のインストール
playwright で msw を使用できるようにするためのパッケージとなります。
あんまりメンテナンスされていなさそうなパッケージでもあったので導入するか迷いましたが、「上記パッケージ以外でいいものがない」+「自作するにはあまりにも時間がかかりそう」と言った点で、使用することにしました。
導入自体は非常に簡単で、まずパッケージをインストールする
npm install playwright-msw --save-dev
導入後、playwright の拡張を実施する
import { test as base, expect } from '@playwright/test'
import type { MockServiceWorker } from 'playwright-msw'
import { createWorkerFixture } from 'playwright-msw'
/** playwright に msw の機能を拡張 */
const test = base.extend<{
worker: MockServiceWorker;
}>({
worker: createWorkerFixture()
})
export { test, expect }
ここまでできたら、実際にモックしたい API に対しモックデータ・ハンドラーを作成して、テスト実施時にそちらを読み込ませればOKとなります。(実際のモックデータやハンドラーの作成方法は msw の公式などを確認していただければです)
import { handleGetArticles } from '../../../infrastructures/rest/qiita.com/api/v2/items/__mock__/msw'
import { test, expect } from '../../../tests/playwright'
test.describe('Qiita記事一覧', () => {
test('初期表示', async ({ page, worker }) => {
// 使用するモックハンドラーを追加
await worker.use(handleGetArticles())
await page.goto('/articles/qiita')
// ... 省略
Nuxt の SSR で問題が・・・
上記の設定で問題なく動くだろうと思っていたのですが、Nuxt の SSR では上記の設定で正常に機能しません。
というのも playwright-msw は playwright のヘッドレスブラウザ上で msw のコンテキストを使用できるようなモジュールとなっており、Nuxt のサーバサイド実行では playwright のヘッドレスブラウザとは関係ないので、うまくモック化できません。
苦肉の策、、、 Nuxt の SSR モードを off にする
Nuxt 側でサーバサイド実行しないようにし、ブラウザでのみ Nuxt アプリケーションを動かすようにすることで playwright-msw を読み込ませるという策を実行しました。
案の定、うまく動作したので良かったのですが、SSR モードでの VRT 検証はできないので、そちらがなんとも言えない・・・
(何かいい方法があれば教えてほしいです)
他の参考サイトなどには、Nuxt の plugins に msw を読み込ませるサンプルなどがあったのですが、テストケースごとでモックデータを変えたいということもあり、そちらの案はぼつにしました。
SSR モードを off にするに関しては、下記のような構成で実装しました。
- ビルド時に環境変数に
VRT=true
を渡す
VRT=true npm run build
- nuxt.config.ts に上記環境変数を使った ssr の設定を行う
export default defineNuxtConfig({
ssr: !process.env.VRT,
// ... 省略
})
スクリーンショットの取得
ここまでできたら、テストを作成し、スクリーンショットを取得します。
実際のテスト内容はそれぞれの画面に応じて異なると思うので、自サイトにあったテストの作成をしていただければと思います。
ただ、スクリーンショットの取得では toHaveScreenshot
関数が必要になるので、そちらは必須で定義しないといけません。
スクリーンショットの取得は Docker で行う
こちらは結構ハマりました・・・
ローカルの実行に対しては、何も問題なく実行できて、正常なスクリーンショットの取得ができたのですが、Github Actions と組み合わせるためには、ローカル環境での Docker で画像を取得する必要があったのです。
なぜかに関しては、Playwright で取得されるスクリーンショットは OS プラットフォームに依存するためとなります。
Github Actios では Linux で実行され、ローカルは、それとは異なります。
そうなると、生成されるスクリーンショットでフォントの差分等があり、VRT が絶対にこけてしまうためです。
そのため、対策としては下記のような対策を実施しました。
- ローカルで画像生成する際は Playwright のイメージを使用した Docker 上でスクリーンショットの取得を行う
- Github Actions でも同じ Playwright のイメージを使用して VRT を実行する
ローカルで画像生成する際は Playwright のイメージを使用した Docker 上でスクリーンショットの取得を行う
こちらは公式にあるコマンドをちょっと変更し、Docker run を走らせるようにしました。
# イメージの取得
docker pull mcr.microsoft.com/playwright:v1.41.1-jammy
# Docker Run
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash
上記コマンドを実行すると Playwright のイメージを使用したコンテナ内に入れます。
そこで下記コマンドを実行し、スクリーンショットの取得を行いました。
# node_modules が生成されていない場合、下記を実行
npm ci
# VRT で使用するブラウザのインストール
npx playwright install --with-deps chrome msedge
# VRT モードでのビルドを実行
VRT=true npm run build
# VRT におけるスクリーンショットの取得
npm run test:vrt:us
上記コマンドでうまくいくと信じていましたが、そう甘くはありません。
npm ci
が成功しない
最初の npm ci
における node_modules の生成自体は成功しますが、Nuxt3 から node_modules の更新を行うと下記コマンドも自動で動くようになっています。
nuxt prepare
上記の nuxt コマンドが失敗します。
こちらはいろいろ調査しました。そこで出会ったのが下記のサイトです。
上記のサイトを見ていただくとおり、なんと Docker Desktop のバージョンを 4.18 にダウングレードして、その Docker 側の設定を変更しないと nuxt コマンドや vite コマンド等は動かないとのことでした。(これは辛い)
そのため Docker Desktop のバージョンを 4.18 にダウングレードし、上記サイト通りに設定を変更したら、nuxt の cli や playwright の cli が動くようになりました。
以上で、VRT のスクリーンショットを取得することができました。
(この時点で、自分は現場のプロジェクトに Playwright の VRT を導入することをほぼ諦めてます笑)
Github Actions でも同じ Playwright のイメージを使用して VRT を実行する
Github Actions の workflow に、VRT の実行を記載するのですが、その実行に関して、Docker でも使用した Playworight のイメージを使用するように修正します。
# ... 省略
vrt-with-playwright:
container:
image: mcr.microsoft.com/playwright:v1.41.1-jammy
# ... 省略
上記設定で CI を動かすと、ローカルで作成したスクリーンショットとの差分もなしに CI が成功できました。
最終的なディレクトリ構成
ここまでで VRT の導入が完了しました。
この時点でのディレクトリ構成を下記に記載します。(関連のあるもののみ記載しています)
.
├── infrastructures
│ └── api
│ └── __mock__
│ ├── fixture.ts
│ └── msw.ts
├── pages
│ ├── __screenshots__
│ │ ├── chrome
│ │ ├── edge
│ │ ├── firefox
│ │ └── safari
│ ├── index.vrt.ts
│ └── index.vue
├── tests
│ └── playwright.ts
├── playwright-vrt.config.ts
├── playwright.config.ts
└── nuxt.config.ts
上記のようにすることで、画面単位の VRT のディレクトリ構成を作成することができ、いい感じになったなぁと思っています。
まとめ
ここまで来るのに、業務時間を除いて約1週間かかりました笑
何かを追加するたびに闇にぶち当たったので、何度も諦めかけましたが、なんとかやり切れて良かったです笑
現場への導入はやめようかな〜と思っていたりするので、まだ完璧に実装完了できていませんが、ここまでの内容とさせていただきます。
そのほかのやり残しは下記となります。
- テストファイルに対し
@
などを使用した相対パスでの import の実現
もし、どうしても Playwright で VRT を導入したいなどがあれば、本記事を参考にしていただければと思います。
Playwright 自体は、実行速度等はとても早いので、導入はお勧めします。
以上、最後まで読んでいただきありがとうございました!
おまけ
Nuxt3 で VRT を導入するというのは割と難しいです。
Playwright 以外でもし導入する場合、jest-image-snapshot などがあったりします。
そのほかには Storybook +reg-suit でコンポーネント単位での VRT もあったりしますが、現状 Storybook は Nuxt3 自体を正式にサポートしていなかったりするので、そちらの導入も厳しいと思われます。
そのため、自分の現場では Autify の導入を進めています。
有料サービスになりますが、とてもいい機能が満載で、VRT もサポートしているので、もし気になる方は、Autify を確認してみるのもありだと思います。(もちろん、予算次第にはなりますが)
Discussion