📕

reg-suitをS3もGCPも使わずにGitHub ActionsでCIする

2022/08/08に公開約11,900字

GitHub Actionsのキャッシュを頼りにVisual Regression Testを行いました

さっくり

  • mainブランチの.reg/をキャッシュした
  • コンポーネントの少ないリポジトリで試したら、2分ほどの実行時間で回った
  • ちゃんとPRにコメントも来る
  • ブラウザでの結果の確認にちょっと難あり

使っているもの

React
Styled Components
TypeScript
Storybook
storycap
reg-suit

やり方

環境構築

こちらのエントリを参考にしました。

https://zenn.dev/toshiokun/articles/3d7087b84ba1d9

最終的な設定ファイルたちはこんな感じです。

package.json

package.json
{
  "packageManager": "yarn@3.2.2",
  "scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "ci:screenshot": "storycap -C puppeteer --serverCmd \"start-storybook -p 6006\" http://localhost:6006 --serverTimeout 60000 --delay 1000",
    "ci:reg-suit": "reg-suit run"
  },
  "peerDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^5.3.5"
  },
  "devDependencies": {
    "@babel/core": "^7.18.10",
    "@mdx-js/react": "^1.6.22",
    "@storybook/addon-actions": "^6.5.10",
    "@storybook/addon-docs": "^6.5.10",
    "@storybook/addon-essentials": "^6.5.10",
    "@storybook/addon-links": "^6.5.10",
    "@storybook/addon-postcss": "^2.0.0",
    "@storybook/builder-webpack4": "^6.5.10",
    "@storybook/manager-webpack4": "^6.5.10",
    "@storybook/react": "^6.5.10",
    "@storybook/testing-library": "^0.0.13",
    "@types/react": "^18.0.15",
    "@types/styled-components": "^5.1.25",
    "babel-loader": "^8.2.5",
    "puppeteer": "^16.1.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "reg-keygen-git-hash-plugin": "^0.12.1",
    "reg-notify-github-plugin": "^0.12.1",
    "reg-suit": "^0.12.1",
    "storycap": "^3.1.9",
    "styled-components": "^5.3.5",
    "tslib": "^2.4.0",
    "typescript": "^4.7.4"
  }
}

regconfig.json

regconfig.json
{
  "core": {
    "workingDir": ".reg",
    "actualDir": "__screenshots__",
    "thresholdRate": 0,
    "addIgnore": true,
    "ximgdiff": {
      "invocationType": "client"
    }
  },
  "plugins": {
    "reg-keygen-git-hash-plugin": true,
    "reg-notify-github-plugin": {
      "prComment": true,
      "prCommentBehavior": "default",
      "clientId": "client-id-hogehoge-fugafuga"
    }
  }
}

workflowをつくる

mainブランチでのスクリーンショットと撮影とキャッシュ

jobs:
  prepare:
    name: Prepare main branch screenshots
    runs-on: ubuntu-latest
    steps:
      - name: setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: checkout main branch
        uses: actions/checkout@v3
        with:
          ref: main
          fetch-depth: 0

      - name: check commit hash
        id: commithash
        run: echo "::set-output name=hash::$(git rev-parse HEAD)"

      - name: restore yarn cache
        uses: actions/cache@v2
        with:
          path: .yarn/cache/
          key: reg-yarn-${{ hashFiles('**/yarn.lock') }}-v1

      - name: restore reg-suit screenshots
        uses: actions/cache@v2
        with:
          path: .reg/
          key: reg-${{ steps.commithash.outputs.hash }}-v7

      - name: Check file existence
        id: check_files
        uses: andstor/file-existence-action@v1
        with:
          files: ".reg/"

      - name: yanr install main branch
        if: steps.check_files.outputs.files_exists == 'false'
        run: yarn install --immutable

      - name: screenshots main branch
        if: steps.check_files.outputs.files_exists == 'false'
        run: yarn ci:screenshot

      - name: run reg-suit main branch
        if: steps.check_files.outputs.files_exists == 'false'
        run: yarn ci:reg-suit

      - name: set reg-suit expected
        if: steps.check_files.outputs.files_exists == 'false'
        run: |
          rm -rf .reg/expected/
          mv -f .reg/actual/ .reg/expected/
          mkdir .reg/actual/
          ls -l .reg/
      - name: upload .reg/
        uses: actions/upload-artifact@v2
        with:
          name: reg-expected
          path: .reg/

説明

stepsの2つめのこれ

- name: checkout main branch
  uses: actions/checkout@v3
  with:
    ref: main
    fetch-depth: 0 // ※ mainブランチをチェックアウトするために必要 ※

ここでmainブランチをチェックアウトします。
fetch-depsを0にすることですべてのコミットログを取得するようになり、origin/mainをチェックアウトできるようになります。


stepsの3つめでコミットハッシュを取得し、キャッシュのキーとして利用しています。
コミットハッシュを頼りにキャッシュを探し、ヒットしたらそれをそのまま使います。

- name: check commit hash
  id: commithash
  run: echo "::set-output name=hash::$(git rev-parse HEAD)"

- name: restore reg-suit screenshots
  uses: actions/cache@v2
  with:
    path: .reg/
    key: reg-${{ steps.commithash.outputs.hash }}-v1

コミットハッシュが異なる == キャッシュが見つからない == ディレクトリが復元されない なので
ディレクトリが存在するかを確認し、存在しなければスクリーンショットを撮影します。

- name: Check file existence
  id: check_files
  uses: andstor/file-existence-action@v1
  with:
    files: ".reg/"
    
- name: yanr install main branch
  if: steps.check_files.outputs.files_exists == 'false'
  run: yarn install --immutable

- name: screenshots main branch
  if: steps.check_files.outputs.files_exists == 'false'
  run: yarn ci:screenshot

スクリーンショットの準備ができたら、一度reg-suitを実行し、.reg/actual.reg/expected/へリネームします。
こうすることでmainブランチが比較対象となります。

- name: run reg-suit main branch
  if: steps.check_files.outputs.files_exists == 'false'
  run: yarn ci:reg-suit

- name: set reg-suit expected
  if: steps.check_files.outputs.files_exists == 'false'
  run: |
    rm -rf .reg/expected/
    mv -f .reg/actual/ .reg/expected/
    mkdir .reg/actual/
    ls -l .reg/

mainブランチが更新されず、同じコミットハッシュが得られれば.reg/をキャッシュしているので次回以降yarn installもreg-suitの実行も発生しないので非常に素早く準備できます。

reg-suitの実行

run:
  name: Run reg-suit
  needs: prepare
  runs-on: ubuntu-latest
  steps:
  - name: checkout
    uses: actions/checkout@v3
    with:
      fetch-depth: 0

  - name: workaround for detached HEAD
    if: ${{ github.event_name == 'pull_request' }}
    run: |
      git checkout ${GITHUB_HEAD_REF#refs/heads/} || git checkout -b ${GITHUB_HEAD_REF#refs/heads/} && git pull
      
  - name: restore yarn cache
    uses: actions/cache@v2
    with:
      path: .yarn/cache/
      key: yarn-${{ hashFiles('**/yarn.lock') }}-v1

  - name: yarn install
    run: yarn install --immutable

  - name: load reg-suit expecteds
    uses: actions/download-artifact@v2
    with:
      name: reg-expected
      path: .reg/

  - name: screenshots
    run: yarn ci:screenshot

  - name: run reg-suit
    run: yarn ci:reg-suit

  - name: upload reg-suit
    uses: actions/upload-artifact@v2
    with:
      name: visual regression test result
      path: .reg/

stepsの2つ目のこれ

- name: workaround for detached HEAD
  if: ${{ github.event_name == 'pull_request' }}
  run: |
  git checkout ${GITHUB_HEAD_REF#refs/heads/} || git checkout -b ${GITHUB_HEAD_REF#refs/heads/} && git pull

これはreg-viz/reg-suit: Visual Regression Testing tool のREADMEに記載があったもので、GitHub Actionsでreg-suitのブランチ名を調べるプラグインを動作させるために必要なコマンドのようです。
ただ、何が良くなったのか、fetch-depth: 0を指定しているからかこのstepは必要ありませんでした。

しかし、pull_requestからWorkflowをトリガーしている場合、GITHUB_REFには値がセットされておらず、正常に動作しませんでした。
その代わり、pull-requestイベントではGITHUB_HEAD_REFにマージ予定のブランチの名前が入ってくるとのことだったのでこれで代用しています

GitHub ActionsのWorkflow実行内でRef(Branch)名を取得する方法 | DevelopersIO

説明

stepsの5つ目で先ほどのjobで準備したスクリーンショットをとってきます。
pathを指定しないとカレントディレクトリに.reg/の中身のファイルそのものが展開されてしまうのでpathの指定が必要です。

- name: load reg-suit expecteds
  uses: actions/download-artifact@v2
  with:
    name: reg-expected
    path: .reg/

比較したいブランチのスクリーンショットを撮影して、reg-suitを実行

- name: run reg-suit
  run: yarn ci:reg-suit

- name: upload reg-suit
  uses: actions/upload-artifact@v2

最後に、artifactに.reg/をアップロードして終了!

- name: upload reg-suit
  uses: actions/upload-artifact@v2
  with:
    name: visual regression test result
    path: .reg/

結果が見たいときはartifactをダウンロードして、FireFoxか--allow-file-access-from-filesオプションの付いたChrome系でindex.htmlを開けば閲覧できます。

出来上がったWorkflow

最終的な .github/workflows/vrtest.yml
.github/workflows/vrtest.yml
name: Visual Regression Test
on:
  push:
    branches:
      - ci/**
  pull_request:
    branches-ignore:
      - ci/**
    types:
      - opened
      - synchronize
      - reopened
jobs:
  prepare:
    name: Prepare main branch screenshots
    runs-on: ubuntu-latest
    steps:
      - name: setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: checkout main branch
        uses: actions/checkout@v3
        with:
          ref: main
          fetch-depth: 0

      - name: check commit hash
        id: commithash
        run: echo "::set-output name=hash::$(git rev-parse HEAD)"

      - name: restore yarn cache
        uses: actions/cache@v2
        with:
          path: .yarn/cache/
          key: reg-yarn-${{ hashFiles('**/yarn.lock') }}-v1

      - name: restore reg-suit screenshots
        uses: actions/cache@v2
        with:
          path: .reg/
          key: reg-${{ steps.commithash.outputs.hash }}-v7

      - name: Check file existence
        id: check_files
        uses: andstor/file-existence-action@v1
        with:
          files: ".reg/"

      - name: yanr install main branch
        if: steps.check_files.outputs.files_exists == 'false'
        run: yarn install --immutable

      - name: screenshots main branch
        if: steps.check_files.outputs.files_exists == 'false'
        run: yarn ci:screenshot

      - name: run reg-suit main branch
        if: steps.check_files.outputs.files_exists == 'false'
        run: yarn ci:reg-suit

      - name: set reg-suit expected
        if: steps.check_files.outputs.files_exists == 'false'
        run: |
          rm -rf .reg/expected/
          mv -f .reg/actual/ .reg/expected/
          mkdir .reg/actual/
          ls -l .reg/

      - name: upload .reg/
        uses: actions/upload-artifact@v2
        with:
          name: reg-expected
          path: .reg/

  run:
    name: Run reg-suit
    needs: prepare
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: workaround for detached HEAD
        if: ${{ github.event_name == 'pull_request' }}
        run: |
          git checkout ${GITHUB_HEAD_REF#refs/heads/} || git checkout -b ${GITHUB_HEAD_REF#refs/heads/} && git pull

      - name: restore yarn cache
        uses: actions/cache@v2
        with:
          path: .yarn/cache/
          key: yarn-${{ hashFiles('**/yarn.lock') }}-v1

      - name: yarn install
        run: yarn install --immutable

      - name: load reg-suit expecteds
        uses: actions/download-artifact@v2
        with:
          name: reg-expected
          path: .reg/

      - name: screenshots
        run: yarn ci:screenshot

      - name: run reg-suit
        run: yarn ci:reg-suit

      - name: upload reg-suit
        uses: actions/upload-artifact@v2
        with:
          name: visual regression test result
          path: .reg/

PullRequestを出してみる

自分のリポジトリではPullRequestを作ったときのみreg-suitを実行するようにしているので、PullRequestを出してみる。

reg-suitのGitHub Appsをインストールしておくと、こんな感じでPRにコメントをつけてくれる!

pushしてみる

ちゃんと動いてます

結果の確認はArtifactをダウンロードしてFireFoxで開く運用になってしまっているので、ここをどうにかしたければGitHub Pagesにデプロイする等の対応が必要です。

2回目以降はキャッシュが効くので、素早く準備完了

コンテナの立ち上げの時間がほとんどで、コンテナ内での処理は10秒程度で終了しています

参考

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

https://zenn.dev/toshiokun/articles/3d7087b84ba1d9

GitHub ActionsのWorkflow実行内でRef(Branch)名を取得する方法 | DevelopersIO

Discussion

ログインするとコメントできます