StorybookでのVRT(Visual Regression Testing) を最小構成で試す

# はじめに
タイトルの通り、VRT(Visual Regression Testing) をStorybookを用いて最低限の構成で試してみました。
※ 最低限の構成で試したので、プロジェクトに導入する場合はもう少し考慮が必要そうです。。

# 対象者
- VRT(Visual Regression Testing) に興味がある
- Storybookを用いてVRTを導入しようと思っている
- VRTどんなものか試してみたい

# ゴール
↓のようにお試しでレポート出力が見れる状態
<img width="500" alt="image1.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/7bbbc15c-2302-7eb9-4534-8770b6acf449.gif">

# 環境構築や準備

各バージョンや環境

```
- storybook/react: 6.3.12
- storycap: 3.1.0
- node: v12.22.0
```

## 1. Docker周り
[こちら](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-on-alpine)を参考にDockerfileを作成していきます。後ほど使う `puppeteer` の為に色々入れてます。

```Dockerfile
FROM node:12.22.0-alpine

RUN apk update && \
    apk add --no-cache \
      git \
      vim \
      chromium \
      nss \
      freetype \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
```

`docker-compose.yml` は以下になります。

```yml
version: '3.3'
volumes:
  modules_data:
    driver: local
  next_data:
    driver: local

services:
  vrt:
    build: .
    image: xxxx/vrt
    container_name: 'vrt'
    environment:
      - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
      - PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
    volumes:
      - .:/usr/src
      - modules_data:/usr/src/node_modules
      - next_data:/usr/src/.next
    command: ash -c "yarn install && yarn dev"
    ports:
      - '3000:3000'
      - '6006:6006'
    working_dir: /usr/src
```

puppeteerの設定用の環境変数 `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` と
 `PUPPETEER_EXECUTABLE_PATH` を設定しています。

実際の手順の詳細は省きますが、
`npx create-next-app@latest --typescript` でnextjsのプロジェクトを作成し、起動できるまでにしときます。
(今回 Next.jsのプロジェクトの想定で実施してますが、storybookが使えれば何でも良さそうかと思います。)

## 2. Storybookに必要なパッケージをインストール

### 2-1.`npx sb init` でインストール

```sh
$ npx sb init
```

`package.json` に追加されたスクリプトやパッケージは以下になります。

```json
  "scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },
  "devDependencies": {
    "@babel/core": "^7.16.0",
    "@storybook/addon-actions": "^6.3.12",
    "@storybook/addon-essentials": "^6.3.12",
    "@storybook/addon-links": "^6.3.12",
    "@storybook/react": "^6.3.12",
    "babel-loader": "^8.2.3",
  }
```
また、`stories` ディレクトリ配下にサンプルが作成されています。まずはこの `stories` のサンプルでVRTを試してみたいと思います。
早速 `yarn storybook` でstorybookを起動してみます。
起動後 `http://localhost:6006/` にアクセスすると以下のような画面が表示されればOKです。

<img width="500" alt="image2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/982273f4-7f16-866e-1407-eac8bb4d5722.png">


## 3. storycap をインストール
[reg-viz/storycap: A Storybook Addon, Save the screenshot image of your stories via puppeteer.](https://github.com/reg-viz/storycap)
`v3.0.0` から `Puppeteer` を別途インストールしたものを使うようになったぽいので、
puppeteerも一緒にインストールしておきます。[参考](https://github.com/reg-viz/storycap#chromium-version)
 
```sh
$ yarn add -D storycap puppeteer
```

以下今回インストールされたパッケージのバージョンになります。

```json
    "puppeteer": "^11.0.0",
    "storycap": "^3.1.0"
```

`package.json` の `script` に

```json
"storycap": "storycap --serverCmd \"yarn storybook\" http://localhost:6006 --serverTimeout 600000"
```
を追加し、試しに実行してみます。

```sh
$ yarn storycap
```

成功すると、`__screenshots__` ディレクトリに画像が保存されます。
Storycap は `simple` と `managed` の2モードがあり、上記のままだと `simple` モードで
今回は細かく制御したい場合は `managed` モードを使うようです。

## 4. reg-suit をインストール
[reg-viz/reg-suit: Visual Regression Testing tool](https://github.com/reg-viz/reg-suit)

```sh
$ yarn add -D reg-suit
```

初期設定を行います。

```sh
$ yarn reg-suit init --use-yarn
```
<img width="700" alt="image3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/403745ac-b74c-2a1e-a30b-aa53ea5a94c3.png">

↑初回必要なPluginのインストールを選択するのですが、今回はお試しなので `reg-publish-s3-plugin` だけ選択しました。
後の質問は以下のように回答しました。

```sh
? Working directory of reg-suit. .reg # デフォルトの.reg
? Append ".reg" entry to your .gitignore file. Yes
? Directory contains actual images. __screenshots__ # storycapの出力先の__screenshots__を指定
? Threshold, ranges from 0 to 1. Smaller value makes the comparison more sensitive. 0
[reg-suit] info Set up reg-publish-s3-plugin:
? Create a new S3 bucket No # S3のBucketは別途用意するのでNoで名前も空にしました
? Existing bucket name 
``` 

ここまでで出来上がった `regconfig.json` は以下になります。

```json
{
  "core": {
    "workingDir": ".reg",
    "actualDir": "__screenshots__",
    "thresholdRate": 0,
    "addIgnore": true,
    "ximgdiff": {
      "invocationType": "client"
    }
  },
  "plugins": {
    "reg-publish-s3-plugin": {
      "bucketName": "$S3_BUCKET_NAME"
    }
  }
}
```

※後々作成したバケット名を外部から設定する為に `$S3_BUCKET_NAME` を指定しています。
また、`package.json` の `script` に `"regression": "reg-suit run"` を追加しました。

## 5. S3にreg-suit用のバケット作成
詳細は割愛しますが、今回  `reg-suit-sample` というバケットを作成して使います。
またreg-suit実行時にS3アクセスのアカウントを指定する為に、docker-compose.ymlにアクセスキー等を追加しました。

```yml
    environment:
      AWS_DEFAULT_REGION: ap-northeast-1
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
      S3_BUCKET_NAME: ${S3_BUCKET_NAME}
```

# VRT実施

## 1. S3が空の状態で実行

```sh
$ yarn storycap
$ yarn regression
```

実行が完了するとS3には以下がアップロードされていました。
※後々出てきますが、この時 `key generator plugin` を指定していない為、`snapshot_日時` で作成されています。

<img width="300" alt="image4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/b2dd9f58-2b99-9446-340a-cbd78ac888d3.png">


 yarn regressionで生成された出力にある `Report URL` にアクセスしてみると以下のレポートが出力されていました。
<img width="500" alt="image5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/852fb783-695e-6b52-bb43-1ace30c18c2b.png">

## 2. 差分が発生するように変更して再度実施

再度実行すると別途snapshotが作成れていました。
<img width="300" alt="image6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/4eb985ec-a627-702d-459f-428bba326b02.png">

ただし、レポートを見ても差分が検知されていません。。
よくよくログを見てみると、

```sh
[reg-suit] info Skipped to detect the previous snapshot key because key generator plugin is not set up.
[reg-suit] info Skipped to fetch the expected data because expected key is null.
```
`key generator plugin` がセットされていないので、比較元のデータを取ってきてくれてないようです。
改めて、[reg-simple-keygen-plugin](https://github.com/reg-viz/reg-suit/blob/master/packages/reg-simple-keygen-plugin/README.md)をインストールして再度実施してみたいと思います。

```sh
$ yarn add -D reg-simple-keygen-plugin
```

`regconfig.json` の `plugins` に↓のように追加します。

```json
    "reg-simple-keygen-plugin": {
      "expectedKey": "hoge",
      "actualKey": "fuga"
    },
``` 
今回はお試しなので、keyは一度切りで使う前提でセットしています。
プロジェクトでちゃんと使う場合、[reg-keygen-git-hash-plugin](https://github.com/reg-viz/reg-suit/tree/master/packages/reg-keygen-git-hash-plugin/README.md)を使うか、
CIで生成したhash値を使う事になるかと思います。-> [こちら](https://github.com/reg-viz/reg-suit/blob/master/packages/reg-simple-keygen-plugin/README.md#using-environment-values)

S3のデータも削除しリセットした状態で再度実行します。

初回は actual しか無いので、`actualKey` を `hoge` に変更して実施しときます。
<img width="286" alt="image7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/d6c7b6bd-a3ed-2bd4-ec94-89653f6a1784.png">

次は `actualKey` を `fuga`  に戻して差分が出るように実施します。
出力されたレポートには以下の様に差分が表示され、比較できるようになっています :sparkles: 

<img width="500" alt="image1.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/7bbbc15c-2302-7eb9-4534-8770b6acf449.gif">

めでたしめでたし :tada:  

。。。で終わりじゃなく `storycap` の `managed` モードを使って
もう少し色々試してみたいと思います。

## 3. コンポーネントを新規に作成して操作時のキャプチャでVRTを実施する
ただただコンポーネント表示してレイアウト崩れてないかテストする場合は、上記でも事足りそうですが、
ホバーしたり、クリックしたり、何かしら操作した際にコンポーネントが反応する場合のテストも試してみたいと思います。

### 3-1. ToggleButtonコンポーネントを作成
<img width="88" alt="image8.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/0feefa49-0a14-9b7f-0adc-59091045ae73.gif">

↑のようにhoverで色が変わって、クリックするとON / OFF が切り替わる誰得なコンポーネントを作成します。
ディレクトリ構成としては以下になります。

```
├── pages
├── components
│   └── ToggleButton
│       ├── index.css
│       ├── index.stories.tsx
│       └── index.tsx
```

```tsx
import React, { useState } from 'react';
import './index.css';

export type ToggleButtonProps = {};

const ToggleButton: React.VFC = () => {
  const [on, setOn] = useState(false);
  return (
    <div style={{ padding: '0.5rem' }}>
      <button className="toggle-button" onClick={() => setOn(!on)}>
        {on ? 'ON' : 'OFF'}
      </button>
    </div>
  );
};

export default ToggleButton;
```

```css
.toggle-button {
  background-color: aliceblue;
}

.toggle-button:hover {
  background-color: bisque;
}
```

### 3-2. Storybookの設定

`.storybook/main.js` と `.storybook/preview.js` をそれぞれ以下に修正します。

```js:main.js
module.exports = {
  "stories": [
    "../components/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "storycap"
  ]
}
```

```js:preview.js
import { withScreenshot } from 'storycap';

export const decorators = [
  withScreenshot,
];

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}
```

次に `components/ToggleButton/index.stories.tsx` は以下の様に設定しました。

```tsx
import { Meta, Story } from '@storybook/react';
import React from 'react';
import ToggleButton, { ToggleButtonProps } from '.';

export default {
  title: 'components/ToggleButton',
  component: ToggleButton,
} as Meta;

const Template: Story<ToggleButtonProps> = (args) => <ToggleButton {...args} />;

// Basic
export const Basic: Story<ToggleButtonProps> = Template.bind({});
Basic.parameters = {
  screenshot: {
    variants: {
      hovered: {
        hover: '.toggle-button',
      },
      clicked: {
        click: '.toggle-button',
      },
    },
  },
};
Basic.args = {};
```
screenshotのvariantsで複数状態を指定できるので今回は `hover` と `click` した状態もキャプチャするようにしました。
この状態で一度S3にアップしときます。
次にhoverした際の色を少し変更して、VRTを実行してみます。

```css
.toggle-button:hover {
  background-color: antiquewhite;
}
```
↓hover時の微妙な色の違いもちゃんと検知してくれています :sparkles: 
<img width="500" alt="image9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/d3a52c69-5bc7-21ea-65f8-7ba6d708194a.png">

ちなみに `thresholdRate` を変えて試してみた所、`0.002` 以上だと↑の違いは検知できず、`0 ~ 0.001` の間だと検知してくれました 。

# まとめ

個人的には導入してしまえば、継続的にチェックができるし、導入もすごい大変というわけでも無いので
費用対効果高くて良さそうだなと思いました :sparkles: 

今回試した分は以下のリポジトリにアップしてます。
[Slowhand0309/vrt-nextjs-storybook-sample: VRT(Visual Regression Testing) project with Next.js, Storybook](https://github.com/Slowhand0309/vrt-nextjs-storybook-sample)

# 参考リンク
- [Visual Regression Testing はじめました – 具体的な運用 Tips – PSYENCE:MEDIA](https://tech.recruit-mp.co.jp/front-end/visual-regression-testing/)
- [React/Next.jsにStorybookを導入する | tech-broccoli.life](https://tech-broccoli.life/articles/engineer/add-storybook-for-nextjs/)
- [storybook-chrome-screenshotとzisuiとStorycapと | by Yosuke Kurami | Medium](https://quramy.medium.com/storybook-chrome-screenshot%E3%81%A8zisui%E3%81%A8storycap%E3%81%A8-b878f8ed8361)
- [reg-suit+CircleCIでVisual Regression Test環境の構築](https://zenn.dev/toshiokun/articles/3d7087b84ba1d9)