【デジカルチーム ブログリレー3日目】
こんにちは、エンジニアリンググループ デジカルチームの穴繁です。
今回はクラウド型電子カルテであるエムスリーデジカルのフロントエンド開発にて、Visual Regression Test (VRT) を導入した話をします。
課題
現在、デジカルでは段階的なデザインシステムの導入を進めています。その過程で、「ボタンの横幅を変えたい」「Typographyを調整したい」といったデザイン変更の機会が増えました。各コンポーネントに対するデザイン変更は画面全体のデザインに影響が及びがちです。
そのため、コード上の修正は比較的簡単に済んだとしても、
- レイアウト崩れが発生していないか
- 意図しない箇所に変更が及んでいないか
といった確認のためのリグレッションテストが必要となります。
結果的に、安全にリリースを実施するまでにはそれなりのコストがかかってしまうという課題感がありました。
そこで「VRT」の出番です!
VRTでは、変更前と変更後のコードで対象画面のスクリーンショットを撮影し比較することで、発生したデザイン変更を検知し差分を確認することができます。VRTを導入することで、今まで手動で実施していたリグレッションテストの手間を削減し、安全かつ高速にデザイン変更をリリースできることを期待しました。
利用ツール
Playwright
PlaywrightはWebテスト・自動化のためのフレームワークです。Chromium, WebKit, Firefoxといった多くのレンダリングエンジンをサポートしており、クロスブラウザに対応しています。また、toHaveScreenshot と呼ばれるAPIで簡単にVRT用のスクリーンショットを撮影する機能も備えています。
さらに、以下の特徴にメリットを感じています。
- Reportersが豊富
- PlaywrightはJUnit, HTML, JSONといった様々なレポーターに対応しています。これらをGitLabのアーティファクトに保存することで、GitLab上でVRTの実行結果やスクリーンショットの差分の一覧化が見込めます。
- Dockerイメージが提供されている
- ローカル環境とCIでのスクリーンショットの差分 (ex. macとlinuxにおけるフォントの差分) をなくすため、同一環境でスクリーンショットの生成、差分チェックを実施する必要があります。Playwrightは公式がDockerイメージを提供してくれているので、ローカル環境でのスクリーンショット更新の仕組みやCIの設定を簡単に作ることができます。
- Storybookに依存しない
- デジカルではStorybookを利用しておらず、Viteで簡易的なUIコンポーネントのカタログを起動しています。
Mock Service Worker (MSW)
静的なスクリーンショットを撮影するため、モックが必要です。
そこでネットワークレベルでリクエストをインターセプトし、レスポンスとしてモックデータを返すことができるMSWを利用しています。
※デジカルはデータフェッチのライブラリとしてSWRを利用しているため、SWRを経由してデータの取得を行なっているコンポーネントが多いです。そのため、ネットワークレベルでのモックが必要になるという背景もあります。
ブラウザで動作するため、Playwrightとの相性もバッチリです。
工夫点
UIコンポーネントのカタログを新規作成
VRTを実装するにあたってデジカル特有の懸念点がいくつかありました。
- デジカルは大きく4ページしかない業務アプリケーションであり、1つのページに収まっている機能が多い。ページ単位でスクリーンショットを撮影しても差分検出がしづらい。
- モーダルが多く、一部出現させる条件が複雑なモーダルもある。Playwrightで記述するユーザーシナリオベースのテストが複雑化してしまう。
- APIサーバーのレスポンスによってデザインが大きく変わるケースがある。同一コンポーネントでも、モックデータが違った状態でいくつかのパターンのスクリーンショットを撮りたい。
これらを解決するため、各UIコンポーネントをアプリケーションから切り離し、各々が独立した状態で一覧化されたカタログページを新規作成しました。
カタログページによって、各コンポーネントに対してPlaywrightでスクリーンショットを撮影していくことが可能になり、
- 1つのスクリーンショットに収まる画面の範囲を調整できる
- モーダルはワンクリックで開くことができるため、Playwrightで記述するテストは簡潔になる
- カタログページのパス毎にMSWのhandlerが返すモックデータを上書きし、別パターンのデザインのスクリーンショットも撮影できる
といったメリットがあります。
デジカルではViteを利用しており、環境変数を切り替えるとローカルでカタログページを立ち上げることができます。
import { createRoot } from "react-dom/client"; const App = import.meta.env.MODE === "catalog" ? CatalogPage : ApplicationPage; const root = createRoot(document.getElementById("root")); root.render(<App />);
※Playwrightにはコンポーネントテストを実現する機能が提供されていますが、検討時にはまだExperimentalな機能であったこと、上記アプローチでも十分コンポーネントテストを実現できそうであったことから導入は見送りました。
CIでVRTを実行する
VRTを導入するにあたってCIで毎回実行できるようにしたいというモチベーションがありました。
- 上述の通り、PlaywrightはJUnitやHTMLといったレポーターを生成してくれます。各種レポーターをGitLabのアーティファクトに保存し、実行結果や画像差分を確認できるのは便利です。
- スクリーンショットが更新されず長期間放置されてしまうのは避けたい。
- 意図しないデザイン変更があるときに気づきやすい。特にデジカルには一部、古くからあるSCSSと新規で導入したCSS in JSが混在している画面があり、CSSのリファクタリングを実施するときの安心感が違う。
一方でVRTは、差分の閾値やタイムアウトといったパラメータのチューニングが必要であったりと、安定的にCIで実行できるのか懸念がありました。それらの問題にチームメンバー全員が真正面から向き合うのは労力が必要になります。そこで、CIで実行しつつジョブの失敗を許容するライトな運用で走り出しました。
導入初期は案の定、
- 日付に依存したコンポーネントに意図しない差分が出る
- スクリーンショットの更新時にタイムアウトが発生
- ローディング中の画面を撮影してしまい意図しない差分が出る
といった問題が発生しました。
現在は、
- MockDate を導入
- maxDiffPixelRatioやtimeoutといったパラメータのチューニング
- ローディングを待機するようにテストコードを修正
を実施し、比較的安定してCIで実行できています。
結果
チームメンバーとモックやカタログページの作成を協力しつつ、無事に丸々1ページに対するVRTの導入を達成できました。
結果的に、影響範囲の広いデザイン変更を素早く安全にリリースできるようになり、非常に効果を実感しております!
また、VRT導入当初は予期していなかった副次的な嬉しさもいくつかありました。
- UIコンポーネントのカタログが作られた
- 各コンポーネントで想定されるデザインパターンが網羅されており、ドキュメントとしての側面でも便利です。
- APIサーバーを立ち上げることなくフロントエンド単体での開発が可能になった
- MSWで作成したモックサーバーの利用幅は大きいです。デジカルはデジスマといった外部サービスとの連携によって成立している機能がいくつかあり、ローカルでの開発環境を整えるのに時間がかかることがしばしばあります。
まとめ
今回、影響範囲の広いデザイン変更を高速に安全にリリースすることを期待し、Playwright, MSW, Viteを用いてVRTを実装しました。
また実装にあたって、
- コンポーネント単位でテストを作る
- CIでVRTの実行結果が一覧化された状態にしつつ、失敗を許容したライトな運用で走り出す
といった工夫を行いました。
結果的に導入初期から積極的に活用されており、VRTの導入をやり切って良かったなと感じています。
ローカル環境やGitLabといった普段チームメンバーが利用しているツールの範囲内で、スクリーンショットを更新、差分確認をする仕組みを作れたのも利便性という点で良かったです。
今後は他ページへも積極的に導入していきたいと考えています。
是非同じような課題を抱えている方はVRTの導入を検討してみてください!
We are Hiring!
デジカルチームでは、プロダクトの抱える課題を技術で解決したい! というエンジニアを大募集中です。
今回のVRTの導入といったチャレンジも積極的に後押ししてくれる環境で伸び伸びと開発できています。
興味を持って頂けた方、カジュアル面談や採用のご応募をお待ちしています。
また、デジカルの開発チームに関する詳細は以下をご覧ください。