ライブラリアップデート時のスタイルの変化をVisual Regression Testで検知しようとしたけどできなかった話
はじめに
趣味で開発しているプロダクトのライブラリアップデートの自動化にRenovateを使っています。マイナー/パッチアップデートの場合はアプリケーションの動作確認をユニットテストに任せていましたが、メジャーアップデートの場合は動作確認を手動で行なっていました。
あるときgithub-markdown-cssを4.0.0から5.1.0に変更して動作確認していると「エラーではないけど見た目が変わってしまっている」というアップデートの影響を見つけました。
before
after(なぜか背景が黒くなる)
ユニットテストやNext.jsのビルドでエラーにならないので目視で確認しないと気付きませんでした。こういった影響をライブラリをアップデートするたびに手動で確認するのは手間だなと思ったので、ある程度自動化できないかと思い方法を調べました。今回は以下の記事を参考にVisual Regression Testを使って視覚的な変化を検知する方法を試しました。
Visual Regression Test (VRT) とは
こちらの記事が詳しいです。
Visual Regression Test(VRT)とはその名の通り、視覚的にリグレッションテストを行うことです。
フロントエンドの開発者、そうでなくてもHTMLやCSSの修正作業をすることのあるエンジニアの方は下記のような問題に遭遇することがあるのではないでしょうか。
- あるコンポーネントのCSSを変更しただけのつもりだったのに、なぜか他のページのレイアウトが崩れてしまった
- デザインのレイアウトルールが変わったので、共通で使用しているCSSを修正する必要があるが、影響のあるコンポーネントが正常に表示されるか全てを確認することが難しい、結果的に「えいや」でリリースせざるを得ない
通常のロジックのリグレッションテストであれば、自動テストなどで担保できますが、HTMLやCSSなどのレイアウトなどの視覚面の実装に関しては、人の目で差分が想定通りか確認する必要があります。
この面倒な作業をできる限り自動化してくれる解決策が、Visual Regression Testです。
やること
- Next.jsアプリケーションへのStorybook, Storycap, reg-suitの追加
- Storycapのスナップショットを保存するS3バケットの作成
- Visual Regression TestをCI(GitHub Actions)で実行する環境構築
実行環境
- yarn@1.22.19
- npm packages
- next@12.1.6
- @storybook/react@6.5.9
- storycap@3.1.9
- puppeteer@13.4.0
- reg-suit@0.12.1
サンプルコード
実際に実装した内容は以下のリポジトリで確認できます。
ライブラリのセットアップ
Storybookの追加
参考記事の手順でほとんど対応できたので省略します。
"storybook": "start-storybook -p 9001",
背景色が変化しているあたりのコンポーネントをストーリー(Content With Code Block
)として作成しました。
Storycapの追加
yarn add storycap
"storycap": "storycap --serverCmd 'yarn storybook' http://localhost:9001 --serverTimeout 600000 --delay 1000",
リソースの取得やページのレンダリングの完了を待機するため--delay
オプションを指定しました。デフォルトだと__screenshots__
ディレクトリにスナップショットが出力されます。
reg-suitの追加
yarn add reg-suit
yarn reg-suit init
いくつか質問に答えて設定ファイルを作成します。
{
"core": {
"workingDir": ".reg",
"actualDir": "__screenshots__",
"thresholdRate": 0.001,
"addIgnore": true,
"ximgdiff": {
"invocationType": "client"
}
},
"plugins": {
"reg-keygen-git-hash-plugin": true,
"reg-notify-github-plugin": {
"prComment": true,
"prCommentBehavior": "default",
"clientId": "<クライアントID>",
"setCommitStatus": false
},
"reg-publish-s3-plugin": {
"bucketName": "<S3バケット名>"
}
}
}
reg-suitの実行結果をPRのステータスに反映させないようsetCommitStatus
を無効にしました。これがtrueだと、意図した変更なのにステータスが赤いPRをマージすることになり正常な感覚を失う恐れがあるためです。(N予備校のエンジニアの方の受け売り)
"regression": "reg-suit run"
S3バケットの作成
reg-suitを実行したときにスナップショットをアップロードするS3バケットを作成します。
- オブジェクト所有者の設定
- ACL有効、オブジェクト所有者は
希望するバケット所有者
とする。
- ACL有効、オブジェクト所有者は
- パブリックアクセスの設定
- 以下の二つにチェック
新しいパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする
- 以下の二つにチェック
S3バケットに書き込みできるIAMユーザーの作成
上記で作成したバケットしか操作できないインラインポリシーをアタッチしました。このユーザーのクレデンシャルを使ってreg-suitを実行します。
CIの設定
アプリケーションを修正するたびにVRTを手動で実行するのは手間なのでCIで実行するようにします。想定しているフローは以下です。
- RenovateがライブラリアップデートのブランチとPRを作成
- CIが動いてライブラリがアップデートされた状態のアプリケーションのスナップショットを作成
- 基点ブランチのスナップショットとRenovateが作成したブランチのスナップショットを比較してPRに通知
CIでは作成したIAMユーザーのクレデンシャルを環境変数に設定する必要があります。(AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
の部分)
secretの読み込み
クライアントIDとS3バケット名は環境変数を経由して読み込むようにしました。
env:
REG_SUIT_CLIENT_ID: ${{ secrets.REG_SUIT_CLIENT_ID }}
REG_SUIT_S3_BUCKET_NAME: ${{ secrets.REG_SUIT_S3_BUCKET_NAME }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
"plugins": {
"reg-keygen-git-hash-plugin": true,
"reg-notify-github-plugin": {
"prComment": true,
"prCommentBehavior": "default",
"clientId": "$REG_SUIT_CLIENT_ID", # 環境変数 REG_SUIT_CLIENT_ID の値を参照する
"setCommitStatus": false
},
"reg-publish-s3-plugin": {
"bucketName": "$REG_SUIT_S3_BUCKET_NAME"
}
}
切り離されたHEADの回避策
READMEのCIセクションに記述されている設定を追加します。
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: workaround for detached HEAD
run: |
git checkout ${GITHUB_REF#refs/heads/} || git checkout -b ${GITHUB_REF#refs/heads/} && git pull
この設定がないとreg-suitの実行時に前回のスナップショットを検出できず比較がスキップされてしまいます。
日本語フォントの追加
Storycapで取得するスナップショットの日本語が文字化けするので日本語フォントを追加します。
- name: Install Japanese fonts
run: |
sudo apt install fonts-ipafont fonts-ipaexfont
VRTの実行
CIの発火条件は以下を設定しました。何かしらコミットをプッシュすればCIが発火します。
on:
push:
VRTのジョブは以下を設定しました。
- name: Visual Regression Test
run: |
yarn storycap
yarn regression
- Storycapを実行してストーリーのスナップショットを作成します。
- reg-suitを実行して前回と今回のスナップショットの比較を行います。
- 前回のスナップショットをS3バケットから取得します。
- 今回のスナップショットをS3バケットにアップロードします。
実際にVRTのジョブを実行しました。
ログに以下の文言が出力されています。Content With Code Block
の変化が検知されているのではないでしょうか!?
[reg-suit] debug fail: Blog/Content/Content With Code Block.png
:
[reg-suit] info Changed items: 1
PRへの通知
ジョブが完了すると比較結果がPRにコメントされます。Changed itemsは赤い丸として表示されます。コメントのthis report
をクリックすると比較結果のレポートを閲覧できます。
比較結果のレポート
CHANGED ITEMSにContent With Code Block
が表示されています。ライブラリをアップデートして背景が黒くなったことが検知されているに違いありません。
実際にどういうスナップショットが比較されたのか見てみましょう。CHANGED ITEMSのサムネイルをクリックします。
左:基点ブランチ 右:Renovateブランチ
左が基点ブランチのスナップショット、右がRenovateが作成したブランチのスナップショットです。………ライブラリをアップデートした後のスナップショットなのに背景が黒くないですね。
ちなみにローカル環境でRenovateブランチをcheckoutしてnext
コマンドを実行してみると背景は黒くなりました。ローカル環境とCI環境の間に何らかの違いがあり、その違いが原因で背景が黒くなるかどうかが変化するようです🤔
CI環境で背景が黒くならない原因
ローカル環境で背景を黒くしているCSSを確認するとbackground-colorに変数--color-canvas-default
が設定されていました。その値は#0d1117
でした。
github-markdown-cssのソースを確認するとメディアクエリのprefers-color-schemeがdark
のときに上記の背景色が設定されるようです。
@media (prefers-color-scheme: dark) {
.markdown-body {
:
--color-canvas-default: #0d1117;
つまりブラウザのテーマがダークモードなら背景が黒くなり、ライトモードなら背景は黒くならないということです。実際、ブラウザのテーマをライトモードに変更してローカル環境でnext
コマンドを実行してみると背景は白くなりました。
CI環境で背景を黒くする方法
Storycapは内部でPuppeteerを使用しているようです。
以下のようにemulateMediaFeatures
を実行すればダークモードでスナップショットを作成できそうな気がします。
await page.emulateMediaFeatures([
{ name: "prefers-color-scheme", value: "dark" },
]);
ところがStorycapのCLIのオプションの中にemulateMediaFeatures
を実行できそうなものはありませんでした。
--puppeteerLaunchConfig
は指定できますがその中でprefers-color-schemeを指定できそうなものはありませんでした。
結論
CI環境でStorycapの実行時にダークモードでスナップショットを作成できず、ローカル環境で発生する背景が黒くなる現象を再現できませんでした。なので「ライブラリをアップデートしたら背景が黒くなった」という今回のケースをVRTで検知することは難しそうという結論に至りました…。もしダークモードでスナップショットを作成する方法をご存じの方はぜひ教えていただきたいです。
感想
今回のようなケースではVRTの真価を発揮できませんでしたが、冒頭で引用したようにVRTを導入する価値があるケースはいくつかあります。
- あるコンポーネントのCSSを変更したら意図せず別のページのレイアウトが崩れたことを検知したい
- 共通で使っているCSSを修正したとき影響のあるコンポーネントの変化を検知したい
またVRTをCIで実行することで修正による影響の確認フローを自動化できます。PRを作ると基点ブランチとfeatureブランチのスナップショットを比較して結果をPRにコメントするような仕組みを構築できます。
修正確認を手動で行うことに厳しさを感じている方はVRTを試してみてはいかがでしょうか。
Discussion