【Storybook】 Storycap と reg-suit を使って VRT(Visual Regression Testing)をやってみた

プロジェクトが大きくなってくると、コンポーネントの修正が他のコンポーネントに影響を及ぼすことがあります。 予期せぬ UI 崩れを起こさない為に、Storycap と reg-suit を使った VRT(Visual Regression Testing)を試してみました!
2024.05.21

React + Vite + Storybook の環境構築

まずは、React + Vite + Storybook の環境を構築します。

npm create vite@latest . -- --template react-ts
npm install
npx storybook@latest init --builder vite

今回は、Storybook が作成してくれた Story(src/stories/*)を元に、VRT を行います。

Storycap の導入

Storycap は、Storybook をクロールし、スクリーンショットを撮影するライブラリです。

まずは必要なパッケージをインストールします。

npm install -D storycap

次に、Storybook の設定に Storycap を追加します。

.storybook/main.ts

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-onboarding",
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@chromatic-com/storybook",
    "@storybook/addon-interactions",
    "storycap"
  ],
  // ...

parameters.screenshot.viewports には、desktop, tablet, mobileの 3 つのビューポートを指定しています。
これにより、各デバイス幅での UI 崩れも検知することができます。
その他の設定はこちらを参照してください。

.storybook/preview.ts

import type { Preview } from "@storybook/react";
import { withScreenshot } from "storycap";

export const decorators = [
  withScreenshot,
];

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    screenshot: {
      fullPage: false,
      delay: 0,
      viewports: {
        desktop: { width: 1920, height: 1080 },
        tablet: { width: 768, height: 1024 },
        mobile: { width: 360, height: 800, isMobile: true, hasTouch: true },
      },
    },
  },
};

export default preview;

最後に .gitignore に__screenshots__を追加し、Storycap の設定は完了です。

.gitignore

# storycap
__screenshots__

動作確認

下記コマンドを実行して、プロジェクト直下に__screenshots__ディレクトリが作成されていることを確認してください。

npx storycap http://localhost:6006 --serverCmd 'storybook dev -p 6006'

__screenshots__ディレクトリには、各 Story のスクリーンショットが保存されています。
また、viewports を 3 種類指定しているので、1 つの Story ごとに 3 つのスクリーンショットが撮影されているはずです。

reg-suit の導入

次に reg-suit を導入します。
reg-suit は、画像の差分を検知し、差分を画像として保存することができるライブラリです。

まずは画像の保存先として、S3 バケットを作成しておきます。

保存先の S3 バケットの作成

実際にプロジェクトで運用する場合は、CloudFront などを利用して関係者のみがアクセスできるようにする方が良いですが、今回は VRT のお試しなので、設定の簡略化のために、誰でもアクセスできるようにしておきます。
CloudFrontなどを利用して関係者のみがアクセスできるようにするに関しては、また別途記事にまとめたいと思います。

AWS コンソールから S3 バケットを作成します。
バケット名はユニークである必要があるので、適当な名前をつけてください。
オブジェクト所有者はACL有効にしておきます。
ブロックパブリックアクセス設定は、全てのチェックを外しておいてください。

また S3 にアクセスするための IAM ユーザーを作成し、アクセスキーとシークレットアクセスキーを取得しておいてください。

reg-suit のインストール

事前準備が完了したので、下記コマンドを実行してインストールします。

npm install -D reg-suit

インストールが完了したら、初期設定を行います。

npx reg-suit init

質問がいくつか表示されるので、以下のように回答してください。

? Plugin(s) to install (bold: recommended) (Press <space> to select, <a> to toggle all, <i> to invert selection, and
<enter> to proceed)
❯◉  reg-keygen-git-hash-plugin : Detect the snapshot key to be compare with using Git hash.
 ◉  reg-notify-github-plugin : Notify reg-suit result to GitHub repository
 ◉  reg-publish-s3-plugin : Fetch and publish snapshot images to AWS S3.
 ◯  reg-notify-chatwork-plugin : Notify reg-suit result to Chatwork channel.
 ◯  reg-notify-github-with-api-plugin : Notify reg-suit result to GHE repository using API
 ◯  reg-notify-gitlab-plugin : Notify reg-suit result to GitLab repository
 ◯  reg-notify-slack-plugin : Notify reg-suit result to Slack channel.

? Working directory of reg-suit. .reg
? Append ".reg" entry to your .gitignore file. Yes
? Directory contains actual images. __screenshots__
? Threshold, ranges from 0 to 1. Smaller value makes the comparison more sensitive. 0
? notify-github plugin requires a client ID of reg-suit GitHub app. Open installation window in your browser Yes # ブラウザが立ち上がるので、対象のリポジトリを選択してインストールして、ClientIDを取得してください
? This repositoriy's client ID of reg-suit GitHub app ○○○○ # ○○○○には、取得したClientIDを入力してください

? Bucket name ○○○○ # ○○○○には、事前に作成したS3バケット名を入力してください
? Update configuration file Yes
? Copy sample images to working dir No

次に、package.json に以下のスクリプトを追加します。

package.json

"scripts": {
  "ci:screenshot":"npx storycap http://localhost:6007 --serverCmd 'npx http-server storybook-static --ci -p 6007'",
  "ci:vrt":"npx reg-suit run"
},

GitHub Actions での自動化

最後に、GitHub Actions で VRT を自動化します。
GitHub のリポジトリのシークレットに、事前に取得しておいた アクセスキー(AWS_ACCESS_KEY_ID)とシークレットキー(AWS_SECRET_ACCESS_KEY)を登録しておいてください。

ワークフローを作成します。
プロジェクト直下に.github/workflows/vrt.ymlを作成し、以下の内容を記述してください。

.github/workflows/vrt.yml

name: visual regression testing

on: [push]

jobs:
  vrt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          git fetch --prune --unshallow
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1
      - name: Use Node.js v20
        uses: actions/setup-node@v4
        with:
          node-version: "20"
      - name: npm install, build, and test
        run: npm install
      - name: workaround for detached HEAD
        run: |
          git checkout ${GITHUB_REF#refs/heads/} || git checkout -b ${GITHUB_REF#refs/heads/} && git pull
      - name: storybook build
        run: npm run build-storybook
      - name: screenshot and visual regression test
        run: |
          npm run ci:screenshot
          npm run ci:vrt

おつかれさまでした!これで実装は完了です。

動作確認

動作確認を行いましょう!
現在の変更をmainブランチにコミットします。

まずは、UI に変更がないパターンを見てみましょう。
新しいブランチを作成して、src/components/Page.tsxにid を追加して、main ブランチへのプルリクエストを作成します。

src/components/Page.tsx

22  <section className="storybook-page">
23    <h2 id='title'>Pages in Storybook</h2>

プルリクエストをすると、VRT が実行され、結果を reg-suit が以下のようにコメントで教えてくれます!

次に、UI に変更があるパターンを見てみましょう。
また別のブランチを作成して、src/components/Page.tsxからStorybookのテキストを削除して、main ブランチへのプルリクエストを作成します。

src/components/Page.tsx

22  <section className="storybook-page">
23    <h2>Pages in</h2>

すると以下のようにコメントされます!

コメントの中の赤丸は、差分があった画像の数を表しています。
青丸は差分がなかった画像の数を表しています。
その他に、白丸は削除された画像の数、ただの丸は追加された画像の数を表しています。

this reportをクリックすると、レポートを確認することができます!
レポートには、差分画像が表示されているので、どこが変更されたのかを確認することができます。 レポートで変更内容に問題がない事を確認してから、マージを行います。

これで、VRT の自動化が完了しました!

詰まった点

Github Actions での自動化の際に、比較元の画像を取得できずに、毎回新規追加された画像として扱われてしまう問題が発生しました。
出力としては、warn Failed to detect the previous snapshot keyと毎回表示されていました。
この問題に関して、以下のサイトが参考になりました。

git fetch --prune --unshallowを追加することで、解決しました。

.github/workflows/vrt.yml

- uses: actions/checkout@v4
- run: |
    git fetch --prune --unshallow

さいごに

VRTを導入すると、予期せぬ UI 崩れを検知することができるので、安心して開発を進めることができます。
ぜひ、導入してみてください!