rhanda | 元銀行員Web系エンジニアの日記

実務未経験からWeb系受託開発企業に転職したひよっこエンジニアが覚えたことや日々の感情を残すブログ

Playright + reg-suit + GitHub Actionsでビジュアルリグレッションテストを定期実行する

ビジュアルリグレッションテストとは

スクリーンショットの比較によって、見た目に差分が生じていないかを検証するテストのことです。
意図しない差分が検知されれば、コードの変更で予期せぬ影響が出ていることに気づけます。

なぜやりたかったのか

既存のRSpecによるE2Eテストによって、開発者がテストケースとして書いた箇所は期待通り表示されていることが担保できていましたが、それ以外の箇所でも差分が発生していたら気づけるようにしたいとなったのがきっかけでした。

また今回は

  • Playwrightを使ってブラウザを操作したい
  • できればNode.jsではなく、Rubyを使ってPlaywrightを動かしたい
  • CIツールを使って定期実行したい
  • 常に前回実行時に撮影したスクリーンショットを、次回の期待画像としてテストしたい

という要件がありました。

構成

以下の構成でビジュアルリグレッションテストを実行するようにしました。

実装

Playwrightを使ったスクリーンショットの撮影

実画像となるスクリーンショットの撮影を行うコードです。Page#gotoの引数に指定したURLのページを撮影し、Page#screenshotの引数に指定したパスに指定したファイル名で撮影した画像を保存します。

require "playwright"

Playwright.create(playwright_cli_executable_path: "./node_modules/.bin/playwright") do |playwright|
  playwright.chromium.launch(headless: true) do |browser|
    page = browser.new_page
    page.goto("https://www.baystars.co.jp/")

    page.screenshot(path: "./actual_screenshots/screenshot.png", fullPage: true)
  end
end

reg-suitの設定

次のような設定にしました。補足の説明を設定の下に書いています。

// regconfig.json

{
  "core": {
    "workingDir": ".reg", 
    "actualDir": "actual_screenshots", 
    "thresholdRate": 0.01, 
    "addIgnore": true,
    "ximgdiff": {
      "invocationType": "client"
    }
  },
  "plugins": {
    "reg-simple-keygen-plugin": {
      "expectedKey": "${EXPECTED_KEY}",
      "actualKey": "${ACTUAL_KEY}"
    },
    "reg-publish-gcs-plugin": {
      "bucketName": "vrt-screenshot"
    },
    "reg-notify-slack-plugin": {
      "webhookUrl": "${SLACK_WEBHOOK_URL}"
    }
  }
}

actualDir

thresholdRate

reg-simple-keygen-plugin

  • Google Cloud Storageから画像をダウンロード・アップロードするときのキーとなる文字列の設定。ここで指定した文字列のディレクトリがGoogle Cloud Storageで作られて、撮影したスクリーンショットや結果レポートファイルなどが格納される。
  • expectedKeyが期待画像(つまり前回実行時に撮影した画像)を取得するキーで、actualKeyが今回撮影した画像をアップロードするときのキー
  • 今回は定期実行実現のため、実行した日付がキーになるように実装をしたいので、このあとに記載しているGitHub Actionsのワークフローで環境変数に設定した値を使うようにしています。

GitHub Actionsの設定

name: Visual Regression Testing
on:
  schedule:
    # NOTE: 前回実行時に撮影したスクリーンショットを期待画像としてテストするため、
    #       実行時の年月日時間(分は含まない)をキーとして画像をアップロード・ダウンロードするようになっている。
    #       00分を指定すると実行時間が前後した時に時間がずれる可能性があるので回避する。
    - cron: "30 1 * * mon"
env:
  TZ: "Asia/Tokyo"

jobs:
  reg-suit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Set reg-suit snapshot keys
        run: |
          echo "EXPECTED_KEY=$(date -d "1 week ago" +'%Y-%m-%d')" >> $GITHUB_ENV
          echo "ACTUAL_KEY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
      - name: Set slack webhook url
        run: echo "SLACK_WEBHOOK_URL=${{ secrets.SLACK_WEBHOOK_URL }}" >> $GITHUB_ENV
      - name: Build docker container
        run: docker compose build
      - name: Set Credentials
        # NOTE: github actionsでsecretsを扱うとき秘匿情報がマスクされるようになっているが、JSONを扱う場合 {} のみがマスクされるようになっていることから、
        #       正しくJSONとして扱えないなどの問題がある。(cf. https://github.com/actions/runner/issues/1488 )
        #       これを回避するためsecretsにはbase64エンコードした値を入れておいて、それをワークフロー上でデコードして使う。
        env:
          ENCODED_JSON: ${{ secrets.CREDENTIAL_BASE64 }}
        run: echo $ENCODED_JSON | base64 --decode > ./credential.json
      - name: Run screenshot-script
        run: docker compose run app ruby take_screenshot.rb
      - name: Run reg-suit run
        run: docker compose run app npx reg-suit run

DockerfileとComposeファイル

FROM ruby:3.2.2

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends locales-all fonts-ipafont fonts-ipafont-gothic fonts-ipafont-mincho

RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get install -y --no-install-recommends nodejs

# Playwrightの依存関係をインストールする
RUN apt-get update && apt-get install -y \
    libnspr4 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libcups2 \
    libdbus-1-3 \
    libdrm2 \
    libxkbcommon0 \
    libatspi2.0-0 \
    libxcomposite1 \
    libxdamage1 \
    libxfixes3 \
    libxrandr2 \
    libgbm1 \
    libasound2 \
    libnss3

WORKDIR /var/app

COPY package.json ./
COPY package-lock.json ./

RUN npm install

# v1.38からPlaywrightのパッケージインストールだけではブラウザがダウンロードされなくなった。
# ドキュメントの推奨に従い、コマンドを使って明示的にブラウザをダウンロードする。
# cf. https://github.com/microsoft/playwright/releases/tag/v1.38.0
RUN npx playwright install

COPY Gemfile ./
COPY Gemfile.lock ./

RUN bundle install

COPY . .
docker-compose.yml

version: '3'
services:
  app:
    build: ./
    volumes:
      - ${PWD}:/var/app
      - /var/app/node_modules
    environment:
      GOOGLE_APPLICATION_CREDENTIALS: "./credential.json"
      # GitHub Actionsで作られた日付の環境変数を、Compose内の環境変数に格納する
      EXPECTED_KEY: ${EXPECTED_KEY}
      ACTUAL_KEY: ${ACTUAL_KEY}
      SLACK_WEBHOOK_URL: ${SLACK_WEBHOOK_URL}

実行した結果

1週間ごとに自動で実行されて、2度目の実行時には1週間前に撮影した画像と比較して無事にテストが成功することが確認できました。

詰まったところ

Google Cloud Storgeとやりとりするために、サービスアカウントの認証情報jsonを使ってADCという認証を通過する必要がありました。

また認証情報の渡し方が「認証情報が入っているjsonファイルのパスを指定する」であるため、

  • GitHubリポジトリのsecretsに認証情報jsonを格納
  • そこから取得した認証情報jsonを、所定のファイルに書き込む
  • reg-suitはそのファイルを使って認証を通過する

という実装にしたのですが、なかなかうまくいかないということがありました。

調べたところ原因は「GitHub Actionsでsecretsを扱うとき、秘匿情報がマスクされるようになっている」ことで、

JSONを扱う時 {} のみがマスクされるようになっているために

という問題がありました。

GitHub ActionsでJSON などの構造化データを扱いたい時は、base64エンコードしたものをワークフロー上でデコードして使うのが良いようで、なかなかこの問題に気づけず時間を要しました。

まとめ

今回はRailsアプリケーションにビジュアルリグレッションテストを導入し、Playright + reg-suit + GitHub Actionsで定期実行を行ったときの設定を書きました。

reg-suitは、差分派生時には送信されるHTMLレポートからわかりやすく確認ができるだけでなく、Google Cloud Storageとの連携やSlack通知の設定も簡単に行えて、非常に導入しやすく便利なツールなのではないかと感じました。

Dockerfileの設定などは、手探りでやったためにもう少し改善の余地があると思うので頑張りたい気持ちです。