Visual Regression Test 導入その1

今日はVisual Regression Testの導入の話をします。

:::note warn
いろいろと端折っているのでこのままコピペしても動きません
:::

## はじめに
今回はコンポーネント単位でのVRTと、アプリケーションページ単位でのVRTの２種類を導入します。
StoryBookに載せるコンポーネントというのはいわゆる部品というもので、それらを組み合わせて出来上がったページ本体は載っていない想定ですが、アプリケーションのVRTを考えるとやはり出来上がったページ本体のレイアウトに妙な変更が起きていないかを確認したいものです。そこでStoryBookでのVRTとは別にアプリケーションのページを対象としたVRTを行う事で、この課題に対応しようと思いました。

## 技術選定

巷にはVRTの構成サンプルはStoryBookを利用して各Storyの差分を検出するものが多く流れています。プロジェクトでもStoryBookを利用してコンポーネントカタログを作成する予定のため、同じような構成でいくことにしました。

### StoryBook

https://storybook.js.org/

言わずと知れたUIカタログ作成ツール

### StoryCap

https://github.com/reg-viz/storycap

各Storyのスクリーンショットを撮影できるStoryBookのアドオン。内部的にpuppeteerを利用しており、Chromeしか対応していない（firefoxも対応したようだがStorycCapはまだっぽい）

クロスブラウザ対応という点でE2EテストツールであるPlaywrightも良さそうだが、今回は時間が限られているため、社内で前例があったStoryCapを採用

### reg-cli

https://github.com/reg-viz/reg-cli

画像を比較しHTMLレポートを生成するシンプルなツール

比較画像の取得、レポートの連携をいっぺんにやってくれる reg-suit というツールもあったが、比較画像の特定に*キー生成プラグインが必要で、ありものの*`reg-keygen-git-hash-plugin` では具合が悪かった[^1]ので色々と手動でできる `reg-cli` を採用

[^1]: ブランチを切った元のコミットではなくmainブランチの最新版と比較したい

## 構成図

CIはGitHub Actionsを使用しています。

![ci_vrt_qiita.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3889009/de281f49-cd18-f6b3-24c4-95714ea282b4.png)

## 作っていく

### 各種インストール

まずは選定した各種ツールをインストールします。

```bash
pnpm add -D storybook storycap puppeteer reg-cli
```

`puppeteer` を動かすにはいくつか依存パッケージが存在するのでそれも入れておきます。

https://pptr.dev/troubleshooting#running-puppeteer-on-wsl-windows-subsystem-for-linux

```bash
sudo apt-get update && sudo apt-get install -y libgtk-3.0 libgbm-dev libnss3 libatk-bridge2.0-0 libasound2
```

### 各種スクリプト

`package.json` にVRT関連のスクリプトを追加します

```json
"storybook.build": "storybook build -o storybook-static",
"storycap": "puppeteer browsers install chrome && storycap --outDir .reg/actual --serverCmd 'pnpm dlx http-server --port 7174 storybook-static' http://localhost:7174",
"vrt.components": "reg-cli ./.reg/actual ./.reg/expected ./.reg/diff -R ./.reg/report/index.html",
```

- `storybook.build` StoryBookを静的ビルドします。デフォルトで `storybook-static` に出力されますが他で指定するパスなので動かないよう明示します。
- `storycap` スクリーンショットを撮影します。StoryBookはどこかにホストしている訳ではないので、開発用サーバで自前起動します。ブラウザのインストールは `puppeteer` とは別途必要なためここで実行します。比較を `.reg/actual` と `.reg/expected` で行いたいので合うように出力します。
- `vrt.components` スクリーンショットを比較します。レポートを `.reg/report/index.html` に出力するよう明示します。

### GitHub Actions

まずは `pull request` 時のVRT本体を作っていきます。

```yaml
name: ビジュアルリグレッションテスト

on: pull_requst

# 実行内容
jobs:
    # jobID
    visual_regression_test:
        name: ビジュアルリグレッションテスト
        runs-on: ubuntu-latest
        permissions:
            id-token: write # AWS認証のため
            contents: read
            packages: read
            pull-requests: write

        # 実行コマンドたち
        steps:
            # node installとか略

            - name: 文字化け対策
              run: sudo apt install fonts-ipafont fonts-ipaexfont

            - name: Storybookのビルド
              run: pnpm storybook.build

            - name: snapshotを撮影
              run: pnpm storycap
              
            - name: AWS認証
              uses: aws-actions/configure-aws-credentials@v4
              with:
                  aws-region: ap-northeast-1
                  role-to-assume: ${{ secrets.aws-role-arn }}

            - name: mainブランチの画像をダウンロード
              run: aws s3 sync s3://${{ vars.snapshot-s3-bucket }}/ ./.reg/expected/

            - name: ビジュアルリグレッションテストの実行
              id: visual-regression-test
              run: pnpm vrt.components
              continue-on-error: true # 差分があっても失敗とはマークしない

            - name: テスト結果を保存
              id: save-report
              # VRTが実行された場合のみ保存
              if: always() && (steps.visual-regression-test.outcome == 'success' || steps.visual-regression-test.outcome == 'failure')
              run: aws s3 sync ./.reg/ s3://${{ vars.report-s3-bucket }}/

			# 結果のPR連携 略
```

`StoryCap` でスクリーンショットを撮影する際日本語が文字化けを起こすのでフォントをインストールします

```yaml
            - name: 文字化け対策
              run: sudo apt install fonts-ipafont fonts-ipaexfont
```

先ほど追加したスクリプトを利用してStoryBookの静的ビルドを生成します

```yaml
            - name: Storybookのビルド
              run: pnpm storybook.build
```

先ほど追加したスクリプトを利用してスクリーンショットを撮影します

```yaml

            - name: snapshotを撮影
              run: pnpm storycap
```

S3から画像をDLするのに必要なAWSの認証を済ませてから比較用の画像をDLします

```yaml
            - name: AWS認証
              uses: aws-actions/configure-aws-credentials@v4
              with:
                  aws-region: ap-northeast-1
                  role-to-assume: ${{ secrets.aws-role-arn }}

            - name: mainブランチの画像をダウンロード
              run: aws s3 sync s3://${{ vars.snapshot-s3-bucket }}/ ./.reg/expected/

```

先ほど追加したスクリプトを利用して画像比較＆レポートを出力します。ここでは省略していますがこの結果を以ってプルリクエストへコメントする内容を変更するため、差分が出ても失敗とはせずCIを続行させます。

```yaml
            - name: ビジュアルリグレッションテストの実行
              id: visual-regression-test
              run: pnpm vrt.components
              continue-on-error: true # 差分があっても失敗とはマークしない
```

最後に結果レポートをS3へ保存します

```yaml
            - name: テスト結果を保存
              id: save-report
              # VRTが実行された場合のみ保存
              if: always() && (steps.visual-regression-test.outcome == 'success' || steps.visual-regression-test.outcome == 'failure')
              run: aws s3 sync ./.reg/ s3://${{ vars.report-s3-bucket }}/
```

次に `merge` 時のイメージの更新を作ります。

```yaml
name: イメージ更新

on: pull_requst

# 実行内容
jobs:
    # jobID
    update_images:
        name: 正解イメージ更新
        runs-on: ubuntu-latest
        permissions:
            id-token: write # AWS認証のため
            contents: read
            packages: read
            pull-requests: write

        # 実行コマンドたち
        steps:
            # node installとか略

            - name: 文字化け対策
              run: sudo apt install fonts-ipafont fonts-ipaexfont

            - name: Storybookのビルド
              run: pnpm storybook.build

            - name: snapshotを撮影
              run: pnpm storycap
              
            - name: AWS認証
              uses: aws-actions/configure-aws-credentials@v4
              with:
                  aws-region: ap-northeast-1
                  role-to-assume: ${{ secrets.aws-role-arn }}

            - name: スナップショットのアップロード
              run: aws s3 sync ./.reg/actual/ s3://${{ vars.snapshot-s3-bucket }}/ --delete

```

最後のスナップショットのアップロード以外はVRT本体と変わりません

## Visual Regression Test 導入その2
https://qiita.com/y_mukasa/items/f4b20c33db9944c963f4