N 予備校に Visual Regression Testing を導入した話 + tips

こんにちは。N 予備校 Web フロントエンド開発チームの berlysia です。

N 予備校の Web フロントエンド開発に、 Storycap + reg-suit による Visual Regression Testing を導入しました。設定の工夫から、設定中や運用してしばらくの間に実際に発生したハマりどころを挙げ、簡単に注意点や対処例を紹介します。

背景

N予備校について

N 予備校はドワンゴが提供するオンライン学習サービスです。大学受験対策、プログラミング、Webデザイン、機械学習など多様なコースがあります。オンラインでの利用に合わせた教材や、講師が生放送で行う授業、受講者同士でも質問し教えあえるフォーラムを備えています。

www.nnn.ed.nico

周辺状況

N 予備校の Web フロントエンド開発とそれを取り巻く状況には、次のような特徴があります。

  • React による SPA 実装である
  • styled-components による CSS in JS を採用している
  • AWS 文化圏にあり、CodeBuild を CI として利用している
  • E2E テストがない
    • Testing Trophy における E2E 以外のテストは充実している
  • UI カタログとして Storybook を運用し、それなりに網羅している
    • Pull Request 単位で Storybook がプレビュー可能になってもいる

現状の問題と目指す状態

大きく分けてふたつの問題がありました。

  1. コンポーネントの変更やリファクタリングで、意図しない外観変化を検知するすべがない
  2. 特に CSS in JS まわりのライブラリ更新時に外観が崩れることがあり、検知するすべがない

コードの変更前後で崩れたことに気付きたい

コンポーネントのリファクタリングで構造や CSS 実装を改めたとき、意図せず外観が変化してしまうことがあります。

こうした意図しない外観の変化は、利用箇所の周辺を目視するなどの努力と、多少壊れても目立たなければ次の更新で直すという諦めによって対処されてきました。

とはいえ、外観を意図して変更することも当然あります。変化がある箇所が列挙されたうえで、意図した変化はそのまま受け入れつつ、意図しないもののうち問題のある変化に気づける仕組みが求められます

ライブラリ更新で崩れたことに気付きたい

N 予備校の Web フロントエンドの外観実装には、 styled-components と Rebass を利用しています。 styled-components 自体の更新により、バージョンによって結果が変化してしまうことがありました。

CSS Modules 等他の CSS 実装手法と混在している場合にも、よく適用順序の変化によって見た目が意図通りにならなくなっていました。

もともとライブラリ更新には Renovate を活用していて、定期的に自動で更新のための PR が作成される状態になっています。ライブラリはこまめに更新し続けたいので、動作確認はある程度の信頼性を伴って自動化されていることが望ましいです。

VRT: Visual Regression Testing とは

f:id:berlysia:20210428233000p:plain
図1 Visual Regression Testingの概念図

Visual Regression Testing とは、画像を比較して差分を検出する回帰テストです。

WEB 開発においては、ブラウザにレンダリングされた UI の画像を比較して、過去との差分を比較し検出するものになります。

ツールの選定

周辺状況から、E2E テストに付随した手法は、立ち上げるコストを考慮して除外しました。解決したい問題は開発中に集中していますから、SaaS 系手法も開発環境に疎通させねばならず、最初から考慮外となりました。

主に素朴なツールが候補に残ったところから、Storybook の利用状況を踏まえて、 Storycap + reg-suit を選びました。

  • S3 等の外部ストレージが必要 → AWS 文化圏のため速やかに用意できた
  • :+1: リポジトリに画像が入らず、肥大化しない
  • :+1: ツールの公式なプラグインで簡単に Pull Request にレポート表示できる :sparkles:
  • :+1: ツールの動作対象が Storybook で、前提から Pull Request 単位で Storybook を確認可能なため、 画像差分があった箇所について直ちに動くものを触れる

導入した VRT のツールセットと構成

下記の 3 つの開発ツールを用いることで、フロントエンド開発に VRT を導入しました。

  • Storybook
  • Storycap
  • reg-suit

先程の VRT の概念図に、実際の開発ツールの言葉を当てはめると、図 2 のようになります。

f:id:berlysia:20210428233016p:plain
図2 今回採用したツールと概念図の対応関係

Storybook

Storybook とは、UI のコンポーネントと最終的なプロダクトのページを分離して開発するための開発ツールです。

このツールによって、コンポーネント単位での部分的な開発が可能になります。今回の VRT では、Storybook 上から UI の画像をキャプチャして使用しています。

Storycap

Storycap は、Storybook 上のページをクロールし、スクリーンショットの画像を取得するためのものです。Storybook 上のページに対応する画像を書き出してくれます。

reg-suit

reg-suit は、画像を比較しその差分の HTML レポートを生成するための開発ツールです。Storybook や Storycap と併用することで、Storybook 上の各コンポーネントの画像を比較し、差分のレポートを生成します。

さらに、reg-suit にはいくつかの便利なプラグインが存在します。私達のプロジェクトでは、下記の 3 つのプラグインを採用しました。

  • reg-publish-s3-plugin
    • スナップショットを S3 に保存し、差分を比較できるようにする
    • 生成された HTML レポートも、S3 上に保存してくれる
  • reg-keygen-git-hash-plugin
    • どの時点の画像と差分を比較するか、git の commit ハッシュを用いて指定することができるようになる
  • reg-notify-github-with-api-plugin
    • GitHub Enterprise を使用している場合は、このプラグインを組み込むことで PR 上に簡易的なレポートを投稿してくれるようになる

構成

上述した 3 つの開発ツールを用いた VRT の構成を図にしました。

N 予備校の Web フロントエンドの CI 環境は、 AWS CodeBuild を使用しているため、 reg-suit は CodeBuild 上で実行されます。

f:id:berlysia:20210428233029p:plain
図3 今回実現したVRTの構成と流れ
  1. git push
  2. ビルド実行
  3. ソースコード取得
    • CodeBuild 内からソースコードを git コマンドを使用して取得します。
  4. Storybook のページ生成
    • build-storybook を実行して static な Storybook の HTML ページを生成します。
  5. Storycap で画像を生成
    • Storycap のコマンドで 4. で生成した Storybook の HTML ページのディレクトリを指定し、スクリーンショットを生成します。
  6. reg-suit が親ブランチの画像を S3 からダウンロードして比較
      1. で生成したスクリーンショットの画像と、 S3 からダウンロードしてきた親ブランチのスクリーンショットを比較します。
  7. 差分の HTML レポートを S3 へアップロード
  8. スクリーンショットを S3 へアップロード
  9. 比較結果の投稿
    • PR 内にコメントが投稿されます。
  10. 対応の要否の判断
    • PR 内のコメントをもとに、実装者やレビュアーが結果を吟味し、対応の要否を判断します。

設定の工夫

手元での実行は想定しない

一般的な話題になりますが、フォントなどの環境由来で結果が乱れるため、常に CI 上で実行することを前提にしました

閾値

reg-suit では、誤差の許容範囲を割合で指定(thresholdRate)したり、ピクセルで指定(thresholdPixel)することができます。

閾値については、より厳密な比較をしていこうと思い、最初は thresholdPixel で調整を行いました。 しかし、厳密に差分比較をしようとすると、角丸ボタンや半透明なオブジェクト、シャドウなどをレンダリングしたときの微妙なピクセルのずれまで検知してしまいました

また、角丸ボタンが並んでいるような Storybook のページがある場合は、ピクセルで指定してしまうとどうしても差分のピクセル数が多くなってしまい、誤検知の原因となってしまいました。

そのため、最終的に thresholdRate を利用する設定にしたほうがよい、という結論になりました。意図的に差分を作って検知できるかを確認したり、誤検知が起きやすい事例を観察した結果、 thresholdRate の値を 0.001 としました*1

結果を Pull Request のステータスに反映しない

意図した変更や単純な誤検知が発生することを見越し、Pull Request のステータスに差分の有無を反映させないようにしました

誤検知であるとはいえ、ステータスが赤くなっている Pull Request にマージボタンを押すことに慣れてはいけません。せっかく信頼を勝ち取ってきたステータスを狼少年化させ、正常な感覚を失うことになります。reg-suit のレポートでは赤丸を表示してしっかりと警告してくれていますから、それで十分でした。

ハマったこととその対処

トークンを環境変数から渡したいが設定ファイルが JSON

reg-suit の設定ファイルである regconfig.json では、 reg-notify-github-with-api-plugin (GitHub Enterprise 用のプラグイン)の設定をする際に直接 privateToken を記述する必要がありました。

トークンを commit したくなかったため、環境変数から読み込むようにスクリプトを作成し、 CodeBuild 上で regconfig.json へ環境変数を埋め込むようにしました

S3 の使用容量が単調増加

reg-publish-s3-plugin を利用すると、生成されたスクリーンショット一式は S3 に保存されます。

S3 は保存されているファイルの容量によって従量課金されるため、S3 の バケットライフサイクルを設定して 1 ヶ月以上経ったスクリーンショットを削除するようにしました。*2

ファイル数が多いままだと、インシデント対処((たとえば ACL の設定をし忘れて初期値の public-read のままアップロードしてしまった、など))のような操作にも時間がかかることから、十分古くなったスクリーンショットと HTML レポートは削除することに決めました

全ての差分が新規作成扱いされることがある

導入後しばらくの間、すべての差分が新規作成扱いされることがありました。調査の結果、「ベースコミットに対応するスクリーンショットが S3 上に存在しなかった」ことが原因とわかりました。

reg-keygen-git-hash-pluginは、分岐する前のコミットを推測し、そのコミットと現在のコミットのスクリーンショットの差分を比較してくれます。

次のようなシチュエーションで、ベースコミットに対応するスクリーンショットが得られず、すべてのページが新規作成アイテムとして検知されてしまいます。

  • 派生元ブランチの push 時に reg-suit を動作させていないとき
    • PR のマージや push によるブランチの更新時に reg-suit が動作していないと、そこから派生した PR 上では、ベースコミットに対応するスクリーンショットが撮影されていません。
    • reg-suit 導入前の状態から派生しているブランチの場合も、同じことが起こります。
  • 長寿命なブランチに対してマージしたとき
    • 今回は S3 にアップロードしたものの後始末をするようにしているので、 S3 のライフサイクルルールによって削除されたあとになることがあります。

どちらも、何らかの方法でベースコミットに対する reg-suit を再実行し、ベースコミットに対応するスクリーンショットが S3 にある状態にすることで、期待通りに差分のみが検知できるようになります。

得られた結果と今後の課題

導入から 3 ヶ月ほど経過していますが、解決を期待した問題に対して既に有効なことが実感できています。

  • 原因不明の差分があって更新を止めていた styled-components を、ある程度安心して更新できました
  • コンポーネントのリファクタリング時に、対象コンポーネント以外も含めた外観の変化がないことに自信を持って開発・レビューができました
  • Storybook まわりのコードを改めたときに、 Storybook の実装が壊れてしまっていることを検知できました。Storybook の信頼性にも寄与してくれています。

導入した仕組みによる VRT は十分に役立ってくれていますが、より信頼できてかつチームに馴染んだものにするために、次の 3 点が課題と考えています。

  • コンポーネントカタログだけでなく VRT の対象と考えると、Storybook にはより高い網羅性が求められそうでした。初期状態で多様な状態が確認できるようにしておく必要があります。
  • VRT に差分が発生した場合は、 PR 作成者がまずその妥当性にコメントし、対応の要否を率先して考えてもらいたいです。日の浅い・ブランクのあるメンバーでも、自然に対応できるような仕組みが求められます
  • 今回の内容とは別の関心になりますが、N 予備校の Web フロントエンドには iOS/Android アプリケーションの WebView から表示する画面もあり、環境間差異にも苦しめられています。別のアプローチで改善を目指します。

おわりに

N 予備校の Web フロントエンド開発に、 Storycap + reg-suit による Visual Regression Testing を導入しました。

現時点で Storybook を運用していて、 E2E テストをもたない私たちにとって、Storycap と reg-suit による VRT は、手軽に導入できた上で大きな効果をもたらしてくれました

これから VRT の導入をする方々や、既に導入している方々にも役立つものになっていたら幸いです。

We are hiring!

株式会社ドワンゴの教育事業では、一緒に未来の当たり前の教育を作るメンバーを募集しています。

blog.nnn.dev

Web フロントエンド開発チームだけでなく、他チームの取り組み、教育事業の今後については、他の記事や採用資料をご覧ください。

speakerdeck.com

参考文献

補遺

VRT 導入にあたっての具体的な調査・検証・設定は、チームメンバーに大きく貢献いただきました。いつもありがとうございます。

*1:他社での VRT 導入の先行事例でも同じ値が採用されており、別の角度から良い評価をされていることはサポート要因となりました。

*2:おかげでいい感じの diff が出ているレポートが全て消えており、例示するものがありませんでした :cry: