22
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ファンタラクティブAdvent Calendar 2023

Day 2

GitHubだけでVRTする仕組みを作ってみた

Last updated at Posted at 2023-12-01

この記事はファンタアドベントカレンダー2023の2日目の記事です!

社内で今までVRT(Visual Regression Test)の採用経験がなかったため、VRT環境を一度試してみよう!という話になりました。この際、以下の2点を踏まえてGitHubだけで完結するVRT環境を構築しました。

  • S3などのストレージが必要ない手軽なインフラ環境で試したい
  • 既存の財産・知見であるStorybookを活用したい

本記事では、その環境構築手順と気づきを共有します。
なお本記事で掲載した一部コードを含む全体をレポジトリで公開しています。

Cybozu Frontend Advent Calendar 2023の12日目にkorosuke613さんも近い内容の記事を公開しています!
GitHub Actions のキャッシュを使った VRT のすゝめ

スナップショットをどこに置くかの選択肢およびメリデメ等、本記事でカバーしていない内容も記載されています。
ぜひ参考にしてください!

想定読者

  • VRTに興味はあるが、試したことがない方
  • 普段からStorybookを利用して開発をしている方

技術スタック

本記事で使用する主な技術スタックは以下の通りです。

  • Storybook v7
  • storycap v4.2.0: Storybookのスナップショットの生成
  • reg-suit v0.12.2: 画像比較および差分レポートの生成
  • GitHub Actions
  • GitHub Pages

VRTのフロー

VRTはGitHub Actionsを利用して実行されます。実行フローを下図に示します。

image.png

1. 比較用画像群の生成

storycapを利用して参照スナップショット(develop/mainブランチ)とテスト対象スナップショット(featureブランチ)の画像群を生成します。
生成した画像群はArtifactsに保存され、VRT実行時に利用します。

image.png

2. VRT実行および結果レポート生成

reg-suitを利用してVRTを実行し、その結果をGitHub Pagesに公開します。また、PRコメントに結果サマリーとレポートのリンクを作成します。

image.png

3. レポートの閲覧

PRコメントに記載されたリンクからレポートを確認できます。

image.png

ワークフローの実装

以下では前項で紹介したフローの実装例を抜粋して解説します。
全体はサンプルレポジトリで公開していますので、よければ参考にしてください。

1. 比較用画像群の生成

参照スナップショットとテスト対象スナップショットの生成処理は独立しているため、並列で実行しています。
これらスナップショットは画像生成処理を実行するブランチが異なるだけで処理自体は同一となるため、以下では参照スナップショットを説明します。

image.png

画像生成処理は対象が増えるに従い実行時間が長くなるため、並列実行できるようにしています。そのため画像生成処理に利用するStorybookのbuild処理を別ジョブに切り出し、事前実行させています。

Storybookの事前buildジョブ

PR修正の度にスナップショットを生成するのはもったいないので、生成成功時にcacheさせています。
cacheが存在しない場合、Storybookのbuildを行います。

  check_expected_screenshots:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: volta-cli/action@v4
      - uses: actions/checkout@v3
        with:
          ref: main
          fetch-depth: 0

      - name: restore cached expected screenshots
        id: screenshots_cache
        uses: ./.github/actions/cache-screenshots
        with:
          lookup-only: 'true'

      - name: restore cached node_modules
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        uses: ./.github/actions/cache-node-modules

      - name: cache storybook build result
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        id: storybook_result_cache
        uses: ./.github/actions/cache-storybook-build-result

      - name: storybook build
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' && steps.storybook_result_cache.outputs.cache-hit != 'true' }}
        run: yarn storybook:build
        env:
          NEXT_IS_TEST: 'true'

スナップショット生成ジョブ

前述したように並列で実行します。各子ジョブで生成した画像群をArtifactsにアップロードして画像群を集約させます。

  take_expected_screenshots:
    needs: check_expected_screenshots
    runs-on: ubuntu-latest
    timeout-minutes: 30
    strategy:
      matrix:
        shard: [1/4, 2/4, 3/4, 4/4] # 任意の並列数

    steps:
      - uses: volta-cli/action@v4
      - uses: actions/checkout@v3
        with:
          ref: main
          fetch-depth: 0

      - name: restore cached expected screenshots
        id: screenshots_cache
        uses: ./.github/actions/cache-screenshots
        with:
          lookup-only: 'false'

      - name: restore cached node_modules
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        uses: ./.github/actions/cache-node-modules

      - name: cache storybook build result
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        id: storybook_result_cache
        uses: ./.github/actions/cache-storybook-build-result

      - name: prepare for screenshots
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        uses: ./.github/actions/prepare-screenshots

      - name: take screenshots
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        run: yarn storybook:screenshot --shard=${{ matrix.shard }}

      - name: upload screenshots
        uses: actions/upload-artifact@v3
        with:
          name: expected-screenshots
          path: __screenshots__
          retention-days: 1

2. VRT実行および結果レポート生成

参照スナップショットとテスト対象スナップショットの生成ジョブが成功した場合、VRTを実行します。
実行後に予め設定したGitHub Pagesの公開元ブランチgh-pagesブランチにレポートをpushします。

GitHub Pagesの事前設定内容

予め公開元ブランチを用意した上、Settings > Pages にて以下のような設定をしてください。

image.png

また、Settings > Actions > General にてワークフローの権限をread/writeに変更してください。

image.png

  run_reg_suit:
    needs:
      - take_expected_screenshots
      - take_actual_screenshots
    runs-on: ubuntu-latest
    timeout-minutes: 30

    steps:
      - uses: volta-cli/action@v4
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

        # cacheから参照スナップショットを取得
      - name: restore cached expected screenshots
        id: screenshots_cache
        uses: ./.github/actions/cache-screenshots
        with:
          lookup-only: 'false'

        # cacheにない場合はArtifactsから参照スナップショットを取得
      - name: download expected screenshots
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        uses: actions/download-artifact@v3
        with:
          name: expected-screenshots
          path: __screenshots__

        # 取得した参照スナップショットを参照用画像群に移動
      - name: set reg-suit expected
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        run: |
          rm -rf .reg/expected/
          mkdir -p .reg/expected
          mv -f __screenshots__/* .reg/expected/
          ls -l .reg/

        # Artifactsからテスト対象スナップショットを取得
      - name: download actual screenshots
        uses: actions/download-artifact@v3
        with:
          name: actual-screenshots
          path: __screenshots__

      - name: restore cached node_modules
        if: ${{ steps.screenshots_cache.outputs.cache-hit != 'true' }}
        uses: ./.github/actions/cache-node-modules

      - name: workaround for detached HEAD
        run: |
          git checkout ${GITHUB_HEAD_REF#refs/heads/} || git checkout -b ${GITHUB_HEAD_REF#refs/heads/} && git pull

        # VRT実行
      - name: run reg-suit
        run: yarn test:vrt

        # GitHub Page公開元ブランチへレポートをpush
      - name: deploy report to github page
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: .reg/
          destination_dir: ${{ github.head_ref  }} # PR毎にディレクトリを分けることで上書きを防ぐ

      - name: find comment
        uses: peter-evans/find-comment@v2
        id: fc
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: 'github-actions[bot]'
          body-includes: reg-suit report

      - name: upsert comment
        uses: peter-evans/create-or-update-comment@v3
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            reg-suit report
            https://tacrew.github.io/vrt-with-github-sample/${{github.head_ref}}
          edit-mode: replace

おわりに

本記事ではGitHubのみを利用してVRTを実行し、その結果を閲覧できる環境の構築方法を紹介しました。
VRTをちょっと試してみたいけどS3等を用意するのが手間...と感じていた方への朗報となれば幸いです!

冒頭にも追記しましたが、Cybozu Frontend Advent Calendar 2023の12日目にkorosuke613さんも近い内容の記事を公開しています!
GitHub Actions のキャッシュを使った VRT のすゝめ

スナップショットをどこに置くかの選択肢およびメリデメ等、本記事でカバーしていない内容も記載されています。
ぜひ参考にしてください!

明日はOSS活動など社内外で活躍しているエンジニア、yoshi2no55さんがNext.jsのMiddlewareを利用したIP制限のかけ方について記事を書いてくれます!乞うご期待

おまけ

PRを閉じた後のVRT実行結果レポートをGitHub Pagesから削除するためのワークフローも紹介します。必要に応じて使用してください。

レポート削除ワークフロー
clean-vrt-report.yml
name: clean-vrt-report-branch
on:
  pull_request:
    types: [closed]

defaults:
  run:
    shell: bash

jobs:
  delete-dir:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: gh-pages
          fetch-depth: 0

      - name: delete closed pr branch directory
        run: |
          if [[ "${{ github.head_ref }}" != "" ]]; then
            rm -rf "./${{ github.head_ref }}"
          fi

      - name: check if there were changes
        id: git_diff
        run: |
          git diff --exit-code || echo "changed=true" >> $GITHUB_OUTPUT

      - name: Commit and push
        if: steps.git_diff.outputs.changed == 'true'
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add -A
          git commit -m "delete ${{ github.head_ref }} directory"
          git push

環境構築の際に参考にした記事

22
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?