ビジュアルリグレッションテストとは
スクリーンショットの比較によって、見た目に差分が生じていないかを検証するテストのことです。
意図しない差分が検知されれば、コードの変更で予期せぬ影響が出ていることに気づけます。
なぜやりたかったのか
既存のRSpecによるE2Eテストによって、開発者がテストケースとして書いた箇所は期待通り表示されていることが担保できていましたが、それ以外の箇所でも差分が発生していたら気づけるようにしたいとなったのがきっかけでした。
また今回は
- Playwrightを使ってブラウザを操作したい
- できればNode.jsではなく、Rubyを使ってPlaywrightを動かしたい
- CIツールを使って定期実行したい
- 常に前回実行時に撮影したスクリーンショットを、次回の期待画像としてテストしたい
という要件がありました。
構成
以下の構成でビジュアルリグレッションテストを実行するようにしました。
- Playwright
- ブラウザを操作して、スクリーンショットを撮影する。
- playwright-ruby-client
- Google Cloud Storage
- reg-suit
- Playwrightとの組み合わせでビジュアルリグレッションテストを実装した事例が多く見つかったことから、導入しやすいのではないかと考えて選定しました。
- 利用したプラグイン
- reg-simple-keygen-plugin
- reg-publish-gcs-plugin
- reg-notify-slack-plugin
- テストの結果をSlackへ通知するためのプラグイン。
- GitHub Actions
- スケジュールイベントでワークフローをトリガして定期実行できるようにします。
実装
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
- どれだけ細かい差分を検知するかの閾値の設定。0だと意図しない差分まで検知されたので0.1にしています。(0〜1で指定)
- cf. https://github.com/reg-viz/reg-suit#core
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 Actionsでsecretsを扱うとき、秘匿情報がマスクされるようになっている」ことで、
JSONを扱う時 {}
のみがマスクされるようになっているために
- 本当に隠したいはずのkey:valueはログなどに露出されてしまう
- JSONとして扱えない
- cf. https://github.com/actions/runner/issues/1488
という問題がありました。
GitHub ActionsでJSON などの構造化データを扱いたい時は、base64エンコードしたものをワークフロー上でデコードして使うのが良いようで、なかなかこの問題に気づけず時間を要しました。
まとめ
今回はRailsアプリケーションにビジュアルリグレッションテストを導入し、Playright + reg-suit + GitHub Actionsで定期実行を行ったときの設定を書きました。
reg-suitは、差分派生時には送信されるHTMLレポートからわかりやすく確認ができるだけでなく、Google Cloud Storageとの連携やSlack通知の設定も簡単に行えて、非常に導入しやすく便利なツールなのではないかと感じました。
Dockerfileの設定などは、手探りでやったためにもう少し改善の余地があると思うので頑張りたい気持ちです。