🎉

reg-suit+CircleCIでVisual Regression Test環境の構築

2021/05/05に公開

Visual Regression Testとは

Visual Regression Test(VRT)とはその名の通り、視覚的にリグレッションテストを行うことです。

フロントエンドの開発者、そうでなくてもHTMLやCSSの修正作業をすることのあるエンジニアの方は下記のような問題に遭遇することがあるのではないでしょうか。

  • あるコンポーネントのCSSを変更しただけのつもりだったのに、なぜか他のページのレイアウトが崩れてしまった
  • デザインのレイアウトルールが変わったので、共通で使用しているCSSを修正する必要があるが、影響のあるコンポーネントが正常に表示されるか全てを確認することが難しい、結果的に「えいや」でリリースせざるを得ない

通常のロジックのリグレッションテストであれば、自動テストなどで担保できますが、HTMLやCSSなどのレイアウトなどの視覚面の実装に関しては、人の目で差分が想定通りか確認する必要があります。
この面倒な作業をできる限り自動化してくれる解決策が、Visual Regression Testです。

VRT環境を構築するとこんな事ができる

後述するreg-suitを使用してVRT環境を構築すると下記のように、画像ベースで変更点を検知することができます。

変更を検知したコンポーネントファイルの一覧

どのように変更されたか色んな方法で確認できる




CSSの修正が入るたびに全てのコンポーネントを目視で確認するより、よっぽど効率よく想定していないレイアウト崩れを検知できそうです。

VRTのためのreg-suit

「VRTのための」と書いていますが、正確に言うとreg-suitは画像の差分比較ができるツールになります。
https://github.com/reg-viz/reg-suit

reg-suitの仕様は下記のとおりです。

  1. 指定のフォルダに配置されている画像ファイルをS3(またはGoogle Cloud Storage)に格納する
  2. PullRequestを作成すると、merge先のブランチの画像とmerge元のブランチの画像の差分を比較することができる

reg-suitは1の仕様を利用することで、コンポーネントのスクリーンショットを撮影するツール(後述のstorycapなど)と組み合わせてVRTを実現することができます。

reg-suit+storycap+CircleCIによるVRT環境構築手順

VRT環境構築手順は下記のとおりです。

  1. アップロード先のS3の準備をする
  2. コンポーネントのスクリーンショットを取る
  3. reg-suitの初期設定をする
  4. CircleCI上で、スクリーンショットを取りreg-suitによるS3アップロード&画像比較を実行できるようにする
  5. PRの作成

アップロード先のS3の準備をする

スクリーンショットの画像をアップロードするS3バケットを準備します。
S3バケットの作成の仕方は省きますが、一点パブリックアクセスの設定についてハマった部分があるので、記載しておきます。

S3バケットのパブリックアクセスの設定

S3バケットのパブリックアクセスの設定はデフォルトでは「パブリックアクセスをすべてブロック」になっていますが、この設定のままだと「画像のアップロード」および「差分を確認できるHTMLファイルの閲覧」ができなくなってしまいます。※1

上記の画像のように、一部パブリックアクセスをONに設定しておきます。

  • 「新しいアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする」をONにする
    • 「画像のアップロード」ができるようになる
  • 「任意のアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする」をONにする
    • 「差分を確認できるHTMLファイルの閲覧」ができるようになる

該当のS3バケットにアクセスできるユーザーを作成する

CircleCI上でS3に画像をアップロードするために、IAMから該当のS3バケットにアクセスできるユーザーを作成します。
今回、ポリシーは画像のようにAmazonS3FullAccessを付与しましたが、きちんと運用するのであれば該当のS3にのみResourceを制限するようにします。

作成時に取得したユーザーに紐づくACCESS_KEY_IDSECRET_ACCESS_KEYは後ほど使用します。

コンポーネントのスクリーンショットを取る

reg-suitは画像比較をするツールなので、スクリーンショット生成のライブラリは何を使用しても問題ありません。
今回はStorybookで定義したコンポーネントを撮影するstorycapというツールを使用します。
(※reg-suitと同じ開発者によるライブラリです)
https://github.com/reg-viz/storycap

# puppeteerをインストールするのは、puppeteerを使用してスクリーンショットを撮影するため
yarn add storycap puppeteer

package.jsonにscriptsの定義をします。

package.json
  "scripts": {
    "storybook": "start-storybook -p 6006 -c .storybook",
    "screenshot": "storycap -C puppeteer --serverCmd \"start-storybook -p 6006\" http://localhost:6006 --serverTimeout 60000",
  },

yarn screenshotと叩いて、__screenshots__配下にstorybookで定義したコンポーネントの画像が配置されていることを確認します。

Storybookの設定

Storybookの設定については簡単に解説します。
※React+TypeScriptを使用する前提です。

# config.jsの作成
mkdir .storybook
touch .storybook/config.js
.storybook/config.js
import { configure } from '@storybook/react';

// .stories.tsxというファイルをstorybookで認識するように設定
const req = require.context('../src', true, /\.stories\.tsx$/);

configure(() => {
  req.keys().forEach((filename) => req(filename));
}, module);
src/Label.tsx
import { FC, PropsWithChildren } from 'react'

interface Props {
  primary?: boolean
}

export const Label: FC<Props> = ({
  children,
  primary,
}: PropsWithChildren<Props>) => {
  return (
    <span
      style={{
        display: 'inline-block',
        padding: '0 3em',
        height: 55,
        borderStyle: 'solid',
        borderWidth: 1,
        borderColor: primary ? 'red' : '#adadad',
        background: primary ? 'red' : '#fff',
        color: primary ? '#fff' : '#363636',
        font: 'normal 14px/55px sans-serif',
      }}
    >
      {children}
    </span>
  )
}
src/Label.stories.tsx
import { storiesOf } from '@storybook/react';
import { Label } from './Label';

storiesOf('Label', module)
  .add('with default style', () => <Label>Default</Label>)
  .add('with primary style', () => <Label primary>Primary</Label>);

yarn screenshotを叩いた結果

上記のコンポーネントを定義した後に、yarn screenshotを叩くと、下記のような画像が出力されます。


reg-suitの初期設定をする

reg-suitの初期設定を行います。

> yarn add reg-suit

> yarn reg-suit init --use-yarn
# --use-yarnはpluginをインストールする際にyarnの使用を指定するオプション

? Plugin(s) to install (bold: recommended)
# デフォルトで選択している下記のプラグインをインストールする
# 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.
# →S3ではなくGoogle Cloud Storageに画像をあげるreg-publish-gcs-pluginというプラグインもあるので、GCSを利用したい場合には変更する

? Working directory of reg-suit. .reg
# 特にこだわりなければデフォルトの.regに設定

? Append ".reg" entry to your .gitignore file. Yes
# 特にこだわりなければ.gitignoreに追加

? Directory contains actual images. __screenshots__
# スクリーンショット作成ツールによるが、storycapは__screenshots__配下に画像を配置するので、__screenshots__を指定する

? Threshold, ranges from 0 to 1. Smaller value makes the comparison more sensitive. 0
# 差分検知の精度を変更できるが、取りあえず最大の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 <GithubのクライアントID>

[reg-suit] info Set up reg-publish-s3-plugin:
? Create a new S3 bucket No
# 既存のS3を準備していない場合にはYesを選択してもよい、aws-cliで設定しているcredential情報を使ってS3を作成する点に注意

? Existing bucket name
# 先ほど作成したS3の名前を入力する

? Update configuration file Yes

? Copy sample images to working dir No
# スクリーンショットをすでにあげているためサンプルの画像は不要

package.jsonにscripts"regression"の定義をします。

package.json
  "scripts": {
    "storybook": "start-storybook -p 6006 -c .storybook",
    "screenshot": "storycap -C puppeteer --serverCmd \"start-storybook -p 6006\" http://localhost:6006 --serverTimeout 60000",
    "regression": "reg-suit run",
  },

CircleCI上で、スクリーンショットを取りreg-suitによるS3アップロード&画像比較を実行できるようにする

jobの定義

.circleci/config.ymlにyarn screenshotyarn regressionを実行するjobを定義します。

.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: regviz/node-xcb

    working_directory: ~/repo

    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
            - v1-dependencies-

      - run: yarn

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}

      - run: yarn screenshot
      - run: yarn regression

環境変数設定

S3にアクセスできるようにするためにプロジェクトの環境変数AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYを設定します。

time out対策

CircleCI上でstorycapを実行しようとするとtime outになります。

そのためserverTimeoutのオプションに60000を設定しています。
(デフォルトは20000ミリ秒)

package.json
  "scripts": {
    "screenshot": "storycap -C puppeteer --serverCmd \"start-storybook -p 6006\" http://localhost:6006 --serverTimeout 600000","
  },

PRの作成

ここまで来れば、CSSの変更によるレイアウト差分を画像ベースで検知する仕組みが整っています。
早速先ほどのLabel.tsxのCSSを変更して、PRを出してみます。

CircleCIのジョブが完了すると下記のようなコメントが自動で投稿されます。

What do the circles mean?で記載されているように、このコメントで

  • 変更のあったコンポーネントの数
  • 新しいコンポーネントの数
  • 変更のなかったコンポーネントの数
  • 削除されたコンポーネントの数

を確認することができます。

this reportをクリックすると、最初に紹介した画像差分のHTMLに遷移するのでぜひ確認してみてください。

参考文献

Storybookとreg-suitで気軽にはじめるVisual Regression Testing
https://blog.wadackel.me/2018/storybook-chrome-screenshot-with-reg-viz/
GitLab CI/CD + Storybook + Storycap + reg-suit で画像回帰テストを行う
https://qiita.com/eyuta/items/c11469ec48649ac358fc#1-timed-out

注釈

※1
ちなみにこれはIAMのポリシー設定とは関係ありませんでした。
パブリックアクセスを許可せずに画像をアップロードできる方法をご存じの方がいれば、コメントいただけると幸いです。

Discussion