Misocaにおける

ビジュアルテストへの

取り組み

株式会社Misoca

森 俊介

$ whoami

  • 森 俊介 / 黒曜

  • @kokuyouwind

  • 株式会社Misoca

  • 一応Railsエンジニア

  • 最近はTerraformとかDockerをよく触っている

突然ですが

自動テスト
してるよって人🙋

E2Eの自動テスト

してるよって人🙋

デザインの自動テスト
してるよって人🙋

CSSの問題検知は難しい

🤔

marginすこし弄ったけど、

使ってるところは確認したし

CI Greenだから大丈夫だよね……

結果……

真ん中に出るはずのモーダルが

下にずれた

この下にあるボタンが

押せなくなってる😢

_人人人人人人_
> 突然の死 <
 ̄Y^Y^Y^Y^Y^Y^ ̄

なぜ気づけないか?

  • feature specではDOM構造を検査する

    • ​デザイン崩れはほぼ検知できない

  • 影響範囲がグローバル

    • ​classの命名規約が適切でないと、
      ​どこに影響するかがわからない

    • grepで見つけられるかというと……

      • ​div.container li > a とか……

そこでビジュアルテストですよ!

ビジュアルテストって?

  • ビジュアルリグレッションテスト
    (Visual Regression Testing)が正確

  • 「見た目」に対するテスト

    • キャプチャ画像などのdiffを検査する

ビジュアルテストのレポート

Agenda

  • ビジュアルテストの概要

  • Misocaでの活用事例の紹介

  • 運用での工夫や注意点

  • 今後の展望・活用アイディア

  • まとめ

Agenda

  • ビジュアルテストの概要

  • Misocaでの活用事例の紹介

  • 運用での工夫や注意点

  • 今後の展望・活用アイディア

  • まとめ

ビジュアルテストの流れ

1. 現在の外観をキャプチャ

2. 正答画像の取得

3. 画像の比較・レポート

各ステップごとに
いくつかのやり方がある

ビジュアルテストの流れ

1. 現在の外観をキャプチャ

2. 正答画像の取得

3. 画像の比較・レポート

手法分類ー外観キャプチャ

  • テストしたい単位で描画

    • ​コンポーネント単位など

  • 一時サーバでキャプチャ

    • ​Feature SpecでのRackサーバなど

  • 実環境でキャプチャ

テストしたい単位で描画

  • Pros.

    • コンポーネント単位で差分が出せる

    • データを自由に変えやすい

    • サーバアクセス不要なので早い(はず)

  • Cons.

    • 実際の画面単位の描画とは多少差がある

    • RailsのPartial Viewでは工夫が必要

一時サーバでキャプチャ

  • Pros.

    • 既存のE2Eテストに組み込める(かもしれない)

    • データを自由に変えやすい

    • 実際の画面単位でキャプチャできる

  • Cons.

    • 必要な単位での描画と比べると重い

    • ​ランダム要素があると、E2Eテストと同居できない

実環境でキャプチャ

  • Pros.

    • ​導入しやすい

    • 簡単にページを開いて確認できる

  • Cons.

    • 実アプリ上でデータの準備・保守が必要

    • 時間経過などで見た目が変わると使えない

    • 表示が恒常的に変わる操作はテストできない

ビジュアルテストの流れ

1. 現在の外観をキャプチャ

2. 正答画像の取得

3. 画像の比較・レポート

手法分類ー正答画像の取得

  • 正答画像を別途保存

    • ​人間が都度判断して更新する

  • 前回のキャプチャと比較

  • 分岐元のキャプチャと比較

    • gitのコミットごとに画像を貯めておく

    • git branchが分岐した元の画像と比較する

正答画像を別途保存

  • Pros.

    • 正答データと確実に比較できる

  • Cons.

    • ​正答データが変わると更新が必要

前回のキャプチャと比較

  • Pros.

    • 自動で正答データが最新になる

  • Cons.

    • 一度差分を見逃すと、次回以降検知できない

    • 検知した差分を修正したときも差分が出る

分岐元のキャプチャと比較

  • Pros.

    • PR単位でmasterからの差分を確認できる

    • 自動で正答データが更新される

  • Cons.

    • 運用環境でのテストでは使えない

    • Gitの分岐元判定が難しい

ビジュアルテストの流れ

1. 現在の外観をキャプチャ

2. 正答画像の取得

3. 画像の比較・レポート

画像の比較・レポート

  • ここは大差ない

    • 画像diffツールに何が使われるかくらい

    • レポートは大抵HTMLで出てくる

  • ​誤差許容パラメタのほうが重要

    • 差分をどの程度許容するか(px/%)

    • 色の違いをどの程度まで一致とみなすか

どれがいいの?

  • それぞれ向き・不向きがある

    • 運用環境でリリース毎や定期的に見るのか

    • PR単位でmasterからの差分を見るのか

  • ツールによって担当範囲や手法が違う

ビジュアルテストツール

  • reg-suit

    • 正答データ管理や通知をカスタマイズできる

    • 現在の外観画像は別途用意が必要

  • BackstopJS

    • 外観画像取得から比較まで全部やってくれる

    • 実環境へのWebアクセス+正答データ明示のみ

  • Storybook

    • コンポーネント単位でレンダリング

目的に応じて適切な方法を
組み合わせる必要がある

Agenda

  • ビジュアルテスト手法の分類

  • Misocaでの活用事例の紹介

  • 運用での工夫や注意点

  • 今後の展望・活用アイディア

  • まとめ

Misocaでのビジュアルテスト

  • 本番環境・ステージ環境のキャプチャ

    • 実環境で前回との差分チェック

  • ローカル環境のキャプチャ

    • 実環境で正答データとの差分チェック

  • ​帳票PDFのキャプチャ

    • PDFを描画し、正答データとの差分チェック

本番環境

  • 日次で、前日との差分を検知

    • ​キャプチャ: capybara-screenshot

    • 正答管理: reg-suit simple-keygen(Jenkinsビルド番号をキー)

    • 差分レポート: reg-suit

  • Slackの専用チャンネルに通知

    • 差分があったら、問題ないかを見てスレッドに書く

Slack通知

reg-suitのレポート画面(再掲)

ステージ環境

  • リリース毎に、前回との差分を検知

    • 本番環境と同じ仕組み

    • ​キャプチャ: capybara-screenshot

    • 正答管理: reg-suit simple-keygen(Jenkinsビルド番号をキー)

    • 差分レポート: reg-suit

  • リリース作業チャンネルに通知

    • 差分があったら、問題ないかを見てスレッドに書く

Slack通知

ローカル環境

  • 必要なときに手動で実行

    • キャプチャ: BackstopJS

    • 正答管理: BackstopJS(init, approve)

    • 差分レポート: BackstopJS

なぜ本番・ステージと違う?

  • ローカルで表示しづらい画面がある

    • 外部連携など

  • 環境によって描画に微妙な差が出る

    • 正答データが集約できない

    • Dockerを使えば解決しそう

  • CSSを書く人がJavaScriptに慣れていた

帳票PDF

  • 通常CIで、正答データとの差分を検知

    • ​キャプチャ: MiniMagickでpdf -> pngに変換

    • 正答管理: Gitコミット(approveモード実行で上書き)

    • 差分レポート: MiniMagick

  • 失敗した場合はPR Statusに反映

    • 他のテストが失敗したときと同じ

帳票PDFのdiff

なぜ帳票だけ?

  • Misocaのサービス上重要な部分

    • 個別に整備する価値がある

    • 現在手を入れている箇所なので、安心して触りたい

  • PDF生成処理が独立している

    • ​データを変えて網羅的にテストしやすい

環境に応じて色々やってる
ステージ環境が防衛ライン
最悪でも本番で日次検知

Agenda

  • ビジュアルテスト手法の分類

  • Misocaでの活用事例の紹介

  • 運用での工夫や注意点

  • 今後の展望・活用アイディア

  • まとめ

キャプチャ時の注意点

  • CSSでアニメーションやキャレットを切る

  • 非同期読み込みは少し長めに待つ

    • 画像も長めに待たないと出ない事がある

  • 画面や状態がわかる画像名にする

    • 「請求書編集画面-プルダウン-発行.png」など

    • ルールを作ってヘルパーメソッドを作ると良い

CSSでアニメーションを切る

    def disable_animations
      page.execute_script(ANIMATIONS_DESABLE_SCRIPT)
    end

    ANIMATIONS_DISABLE_STYLE = <<~CSS
      <!--
      *, *:before, *:after {
        /*CSS transitions*/
        transition-property: none !important;
        /*CSS transforms*/
        transform: none !important;
        /*CSS animations*/
        animation: none !important;
      }
      -->
    CSS

    ANIMATIONS_DESABLE_SCRIPT = <<~"SCRIPT"
      const style = document.createElement('style');
      style.innerHTML = `#{ANIMATIONS_DISABLE_STYLE}`;
      document.head.appendChild(style);
    SCRIPT

テストコード

module Pages
  module Invoices
    include Pages::Base

    register_tours do
      tour '請求書一覧画面', '/invoices' do
        screenshot full: true

        check 'order[invoices][]', match: :first
        screenshot 'フッター-単独', full: true
        check 'all-checkbox'
        screenshot 'フッター-複数', full: true
        uncheck 'all-checkbox'

# ...

テストコード

  • 基本はただのCapybaraコード

  • ヘルパーメソッドをいくつか作っている

    • tour(view_name, path)

      • ​pathにvisitする

    • ​screenshot(state_name, full: bool, sleep: float)

      • ​現在の画面を(view_name + state_name)で保存する

      • fullなら縦幅を要素に合わせる

      • 画面キャプチャ前にsleep秒待つ

キャプチャするメソッド

    def screenshot(name = nil, wait: 0.5, full: false)
      # テキストボックスのキャレットが表示されないようにする
      page.execute_script "document.activeElement.style.caretColor = 'transparent'"
      # ページの読み込み待ちと負荷軽減を兼ねて、少し待つ
      sleep(wait)
      # 全体をスクリーンショットに撮るために、ブラウザウィンドウをリサイズする
      resize_to_full if full

      Capybara::Screenshot.new_saver(Capybara, Capybara.page, false, name ? "#{pagename}_#{name}" : pagename).save

      # ブラウザウィンドをリサイズしていた場合、元に戻す
      resize_to_default if full
    end

どの程度網羅するか

  • コーナーケースを追求しすぎない

    • ​1ケースごとに実行がかなり遅くなる

    • 実環境に対しては分散実行も負荷・競合リスクになる

    • 重要 / 典型的なケースをテストする

  • 1画面で複数の状態をテストする

    • 一覧画面では複数項目でパターン網羅できる

一覧画面の例

誤差の許容

  • どの程度、画像の誤差を許容するか

    • 小さすぎると誤検知が増える(False Positive)

    • 大きすぎると差異を見逃す(False Negative)

  • False Positiveが多すぎるとつらい

    • ブラウザレンダリングで数pxのdiffが出たりする

微妙な誤差

誤差許容の設定

// regconfig.json

{
  "core": {
    "workingDir": ".reg",
    "actualDir": "screenshots",
    "thresholdPixel": 50,
    "addIgnore": true,
    "thresholdRate": 0,
    "ximgdiff": {
      "invocationType": "client"
    }
  },
  // ...
}

差分検知時の対応

  • 本番/ステージでは意図通りのdiffが出る

    • お知らせの更新など、本番のデータ変更

    • 実際のデザイン変更

  • ​Slackに通知してチェックする

    • スレッドがついてなければまだ誰も見ていない

  • 差分検知時に確認する体制 / フローが重要

Slack通知(再掲)

Agenda

  • ビジュアルテスト手法の分類

  • Misocaでの活用事例の紹介

  • 運用での工夫や注意点

  • 今後の展望・活用アイディア

  • まとめ

PRごとのビジュアルテスト

  • 表示に使うデータを揃える必要がある

    • created_atなども表示するなら揃える必要がある

    • 既存のfeature specとは別にしたほうが扱いやすそう

  • rrrspecで分散実行している

    • 画像を集約する必要がある

    • S3に吐いておけば良さそう

コンポーネント単位のテスト

  • 全部Vue.js化すればできる……

    • そう簡単にはできない

  • partial単位で画像を作れないか

    • 必要なCSSだけ読み込めるように分ける

    • コンポーネントカタログ的なページをRailsでレンダリング

    • ページ全体をrenderしてから必要な要素以外をdisplay: hiddenにする

Agenda

  • ビジュアルテスト手法の分類

  • Misocaでの活用事例の紹介

  • 運用での工夫や注意点

  • 今後の展望・活用アイディア

  • まとめ

結局導入してよかった?

  • よかった

    • リファクタで意図しないマージンにリリース前時点で気づけた

    • 「意図通りにデザインが変わる」ことを確認できるのも安心できる

  • 仕組みを作れば、保守コストは割と安い

    • DOM構造が変わってもほとんど直さずに動く

    • CIにかかる時間は増えるので、うまく妥協する

まとめ

  • ビジュアルテストにもいろいろある

    • ​運用環境での差分検査が導入しやすい

    • 重要な部分に絞って網羅的に検査するのもあり

  • 普通のテストと違う運用が必要

    • False PositiveとFalse Negativeのバランスを取る

    • 検知した差分が意図どおりか確認する体制・フロー