📖

PlaywrightとStoryBookで始めるVRT

はじめに

株式会社 Rehab for JAPAN のまっちゃんです。現在フロントエンドエンジニアとして、機能追加や運用改善に携わっています。この記事では、Playwright と StoryBook を活用した VRT の導入方法を具体的に解説します。ライブラリ更新時の安心感を得たい方におすすめの内容です。

背景

私が所属しているデイリーサービスは初回リリースから約 2 年が経過し、現在も機能追加を継続的に行っています。ただし、フロントエンド開発は 2 人体制であり、ライブラリのアップデートに手が回らない状況でした。
特に、UI コンポーネントのライブラリを更新する際に、デザインや機能の崩れを確認するリグレッションテストが手動で行われており、時間的コストが大きな課題となっていました。
この課題を解決するために、UI に特化した Visual Regression Test を導入し、自動化を検討しました。

VRT とは

VRT(Visual Regression Testing)は、画面のスクリーンショットを比較して、UI 変更による不具合を検出するテスト手法です。これにより、手動確認の手間を削減し、ライブラリ更新時の安心感を得られます。

Playwright の VRT を活用することで、以下のような比較画面を作成できます。
期待値と実際値を視覚的に比較でき、Diffモードでは変更部分をハイライト表示、Sliderモードでは変更前後をスライドで切り替えながら確認できます。

VRT のステップ

VRT を実施は大きく3つのステップがあります。

  • スクリーンショットの取得: 各 UI コンポーネントの表示状態をキャプチャ。
  • スクリーンショットの比較: 更新前後の画像差分を自動で検出。
  • レポート生成: 差分の有無を視覚的に確認できるレポートを作成。

各フェーズでの検討事項

VRT導入にあたり検討するべきことはたくさん

スクリーンショットの取得に関する課題

  • 取得タイミング:
    • テスト実行時?
    • マージリクエスト時?
  • 保存先:
    • ローカル?
    • GitHub Artifact?

比較基準の設定

  • 比較ツールの選定
  • しきい値の設定

レポート生成と活用

  • レポートの出力先
  • レポートの内容
  • 比較元スクリーンショットの更新フロー

などなど...

検討することが多く、腰が重くなっており導入をずっと躊躇していました。
今回はライブラリ更新時にデグレしないことをとりあえずの目標とし、ライトに導入できる方向で進めることにしました。導入するライブラリやサービスは最小に、インフラも使用しない方向で、コストもかからないようにしたいと考えました。

選定したツール

選定の背景

  • ライトに「今」から始めたい
    フロントエンドエンジニアが2人しかおらず、プロダクト開発と平行ですすめるため、導入コストを最小限に抑えることが重要でした。

  • 導入コストを最小化
    StoryBookを既存の環境で利用することで、VRT 環境の構築に新たなツールの導入を避け、導入コストを最小化しました。

  • 学習コストを抑えたい
    Playwrightを採用することで、複数のライブラリを併用する必要がなくなり、学習コストや運用コストを削減できました。

スクリーンショット取得の対象

  • StoryBook
    既にStoryBookが存在していたため、これを利用して VRT(Visual Regression Testing)を行うことにしました。
    新たなツールを導入する必要がなく、導入コストを抑えられると判断しました。

スクリーンショットの取得・比較・レポートの作成

  • Playwright
    以下のすべての処理をPlaywrightのみで実現可能であるため採用しました
    • スクリーンショットの保存
    • 差分の比較
    • スクリーンショットの比較
    • レポートの作成

スクリーンショットの保存先

  • Git 内にコミット
    現状、StoryBookで管理しているコンポーネント数が少なく、スクリーンショットの容量も問題ないため、Gitリポジトリに保存することにしました。

  • ローカルに保存
    開発者が2名で、かつOSも同一のため、環境依存による差分が発生しないと判断しました。

Playwright の導入

playwright のインストールは以下のコマンドを実行すると、サンプルのテストが作成されます。

yarn create playwright

Playwright のスタートガイド

StoryBook を VRT の流れ

  1. StoryBook を StaticSite としてローカルで起動
  2. Static サイトの StoryBook に対して Playwright でスクリーンショットを取得
    1. StoryBook Build 時に生成されるメタデータを利用し、StaticSite の URL を取得
    2. 取得した URL にアクセスし、スクリーンショットを取得
    3. スクリーンショットを比較する

StoryBook を StaticSite としてローカルで起動

StoryBook を StaticSite としてローカルで起動するためには、以下のコマンドで実行

package.json
"storybook:build": "storybook build",
"storybook:start": "http-server storybook-static",
yarn storybook:build
yarn storybook:start

これで、localhost:{ポート} に StoryBook が起動しアクセスできるようになります。

StoryBook Build 時に生成されるメタデータを利用し、StaticSite の URL を取得

StoryBook はビルド時に Json 形式のメタデータを生成します。
これには、StoryBook で表示されるコンポーネントの情報が含まれています。
これを利用して、URL を取得します。

const storiesJsonPath = resolve(
  process.cwd(),
  "storybook-static",
  "index.json"
);
const stories: StoriesJson = JSON.parse(readFileSync(storiesJsonPath, "utf-8"));

const storyInfos = Object.values(stories.entries)
  .filter(({ type }) => type === "story")
  .map((story) => ({
    url: `http://localhost:8080/iframe.html?id=${story.id}`,
    title: story.title,
    name: story.name,
  }));

取得した URL にアクセスし、スクリーンショットを取得と比較

storyInfos.map(({ url, title, name }) => {
  test(`snapshot test ${title}: ${name} `, async ({ page }) => {
    await page.goto(url, {
      waitUntil: "networkidle",
      timeout: 1000 * 10,
    });

    // スクリーンショットを取得し比較
    expect(await page.screenshot({ fullPage: true })).toMatchSnapshot();
    await page.close();
  });
});

実行(StoryBook のビルド & 起動 & テスト実行 & サーバー停止)

後々 CI にも組み込めるように StoryBook のビルド & 起動 & テスト実行 & サーバー停止をスクリプト化します。

package.json
"test:e2e": "playwright test && playwright show-report",
"storybook:build": "storybook build",
"storybook:start": "http-server storybook-static",
"storybook:vrt": "bash storybook-vrt.sh",
storybook-vrt.sh
#!/bin/bash

# エラーが発生したらスクリプトを停止
set -e

# Storybookをビルド
echo "Building Storybook..."
yarn storybook:build

# Storybookをバックグラウンドで起動
echo "Starting Storybook server..."
http-server storybook-static &
SERVER_PID=$! # サーバープロセスのPIDを取得

# サーバーが起動するまで待機
echo "Waiting for Storybook server to be ready..."
sleep 3

# Playwrightのテストを実行
echo "Running Playwright tests..."
yarn playwright test

# サーバーを停止
echo "Stopping Storybook server..."
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null

echo "Storybook VRT process completed."

yarn storybook:vrtをすることで、StoryBook をビルドし、サーバーを起動し、テストを実行し、サーバーを停止することができます。

まとめ

StoryBook を書くことで VRT が行える環境が用意できました。

この環境を導入することで、ライブラリ更新時の確認作業にかかる時間が短縮され、UI崩れを早期に発見できるようになりました。
実は、まだ環境を作って数日しか立っていないため、潜在的な課題は有ると思いますが、今後も引き続き検証を行い、問題があれば改善していきたいと考えています。

今後は以下の課題を改善していきたいと考えています。

  • スクリーンショット管理の効率化(保存先の変更など)
  • CI/CD環境への統合
  • VRT対象の拡大(ページ単位のテストなど)

VRT の導入を検討している方の参考になれば幸いです。

Rehab Tech Blog

Discussion

tommy34tommy34

使ったことはないのですがChromaticやstorycapを使うことでもっとシンプルにできたりしないでしょうか?

ねこぜのまっちゃんねこぜのまっちゃん

@tommy34 さん!
コメントありがとうございます!

ChromaticやStoryCapを使う方法もありますね!
特にChromaticはStorybookのホスティングやビジュアルリグレッションテストを自動化できる点で魅力的だと感じていました。導入も比較的簡単で、プロジェクトによっては非常に有効だと思います。

ただ、ChromaticはStorybookをデプロイしてそのURLをChromaticに登録する必要があるため、ローカル環境だけで完結するテストには向いていないと思っています。現プロジェクトでは、ローカル環境でしかStorybookを動かす環境がなかったため、この点が導入のハードルになりました。

StoryCapについては、ビジュアルリグレッションテストのキャプチャに特化している反面、他のE2Eテストツールとの組み合わせが必要でした。そのため、1つのツールでテストを完結でき、今後のE2Eテストへの拡張性も考慮できるPlaywrightを選択しました。

もちろん、ChromaticやStoryCapを使った方がシンプルに実現できるケースもあると思いますので、今後の選択肢として検討してみたいと思います!アドバイス、ありがとうございました。