Cypress + reg-suitを使ったビジュアルリグレッションテスト

こんにちは。ATOM事業部フロントエンドテックリード兼デザイナーの河原です。
ATOMでは現在大規模なリファクタリングを実施してます。
今回はその一環で ビジュアルリグレッションテスト を導入した事例を紹介します。

はじめに

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

名前の通り見た目をテストするための手法です。
実際にブラウザにレンダリングした際のスクリーンショットを改修前後で比較し、ピクセル単位で差分を検出します。
一般的なE2Eテストコードでは検出困難な意図しないデザイン変更を検出することができます。

f:id:sk3kyu:20210830103639p:plain
VRTの例

VRT導入で実現したかったこと

  • バグの削減と早期発見
  • 回帰テストの工数削減
    • これにより積極的なリファクタリングを可能にしたい
  • 上記をなるべく楽して実現したい
    • テストは費用対効果が大事ですよね。。

ATOMとは

www.atom.tools

ATOMは運用型広告統合管理プラットフォームです。
ログインが必要なシステムで、画面に表示する内容が日々変化します。
日々のデータ変化に影響を受けないVRTの仕組みづくりが今回のポイントの1つです。

VRTの仕組み

CICDに下記のフローを導入しました。

E2Ecypress-VisualReg

テストシナリオはCypressで作成

www.cypress.io

Cypressを用いて一般的なE2Eのテストシナリオを作成します。
シナリオの要所でVRT用のスナップショットを取得します。

例:ログイン画面:ログイン成功のテストケース
context('login', () => {
  ...
  it('Success', () => {
    cy.saveScreenshot('Visit')      // VRT用のスナップショット(ログイン画面訪問時)
    cy.get('[aria-label="メールアドレス"]').type(userData.email)
    cy.get('[aria-label="パスワード"]').type(userData.password)
    cy.get('button[type="submit"]').click()
      ... 
    cy.saveScreenshot('Success')    // VRT用のスナップショット(ログイン成功時)
  })

※ 余談ですが、ATOMでは「アクセシビリティ情報を使った壊れにくいE2Eテスト」を取り入れてます。。

バックエンドとの通信はMock

ATOMではバックエンドから取得するデータをMockにすることで日々のデータ変化に影響を受けないVRTを実現しています。
Cypressの通信をMockにするライブラリはいくつかありますが、いずれも今回の用途に適さなかったため自作しました。

自作したライブラリ:useMock

useMockの利用例
context('hoge-screen', () => {
  useMock() //各テストケースではこれを追加するだけ

  it('Hoge Test', () => {
    ...
  })
})

テスト実行時に--env mockMode=recordを指定することで記録モード、 --env mockMode=replayを指定することでリプレイモードで実行します。

  • 記録モード
    • 実際のバックエンドと通信を行い、その内容を記憶する
    • テストケース作成と合わせて、このモードにてMockデータを作成する
  • リプレイモード
    • 実際のバックエンドと通信は行わず、 記録モードで保存した内容を返却する
    • VRTテストはこのモードで実行

この仕組みによって、Mockのデータを手書きせずに容易に作成できるようになりました。

VRTはreg-suitで実現

reg-viz.github.io

スナップショット画像の差分検出にはreg-suitを使います。
reg-suitとそのプラグインを使って画像差分検出、レポート作成、Github連携を実現します。

Gitの履歴をもとに比較対象のブランチを特定

reg-suitはトピックブランチのソースである親のコミットを自動的に検出します。
そして、検出されたコミットのスナップショット結果を回帰テストの期待される結果として使用します。
これを実現するためにはreg-keygen-git-hash-pluginを導入します。

image

画像差分検出とレポート作成

reg-suitは比較対象の画像との差分検出を行い、HTMLレポート作成します。

f:id:sk3kyu:20210830105229p:plain
レポート例

画像とレポートはS3に保存

reg-publish-s3-pluginを使って、画像とレポートをAWS S3に保存します。
S3バケットのパブリックアクセスの設定を適切にする必要があります。

VRTの結果をGithubのプルリクエストに連携

reg-notify-github-pluginを使って、VRTの結果をプルリクエストに通知します。
レビューワは差分の有無及び、差分がある場合はその詳細を参照することができます。

差分がある場合

スクリーンショット 2021-08-30 7 53 38

差分がない場合

スクリーンショット 2021-08-30 7 52 54

ハマったポイント

画像の安定性の問題

同一条件のはずなのに画像に微妙な差異が出る場合があります。
差異が出る要因は以下が考えられます。

  • 画像取得タイミング
    • アニメーションの実行状態
    • アセットの読み込み前後
    • レンダリングエンジンの描画完了前後
  • 環境差異(OSフォントなど)
  • その他、画像の微小な変化
    • アンチエイリアスなどが関係しているのかも

画像取得タイミング はテストケース側で工夫します(処理の完了を待つ、一定時間waitするなど)。

環境差異 に関しては、同一環境で実行するようにします。
(Cypressが提供しているDocker Imageを使うなど)

その他、画像の微小な変化 が発生する場合は reg-suitの下記の設定を見直します(条件を緩くする)

  • thresholdRate
  • thresholdPixel

※ ATOMでは、thresholdPixel=0, thresholdPixel=2を採用してます。

Cypressのスナップショットの画像サイズ

大きなページを1枚の画像でスナップショット取得する際に、caputure:viewportを指定してスナップショットの保存を行ってますが、windowsSizeを超えたサイズを指定しても反映されませんでした。
テスト実行時にwindowSizeを十分に大きくすることで回避しました。

on('before:browser:launch', (browser, launchOptions) => {
  if (browser.name === 'chrome' && browser.isHeadless) {
    launchOptions.args.push('--window-size=1200, 3000')
    return launchOptions
  }
})

おわりに

今回はCypress + reg-suitを使ったVRTの導入について紹介しました。
VRTを導入したことで、バグの早期発見はもちろんのこと回帰テストの工数削減が実現できました。
これにより、リファクタリングを安心・気軽に行えるようになりプロダクトの質の向上につながってます。

ぜひVRTを導入して快適な開発体験を!