Backstop.js でクリスマスを少し幸せにするリファクタリング

「見た目に変わりがないこと」を保証するのはつらい

  • 複数人で書いたコードのルールを統一したい
  • ページはできたものの時間の都合でコードがぐちゃぐちゃ
  • 開発環境を移行したい etc…

見た目をまったく変化させずに、スタイリングや開発環境などを変更したい状況は度々発生します。 当然ですが、目視でのチェックのみだと高確率で見落としが起こり、事故に繋がりかねません。

目視より安全な方法として、たとえばデザインカンプとの比較ならZeplinやGoogleChrome拡張のPerfectPixelなどを使用すれば、単純な目視チェックよりも正確に確認をできます。 ただ、デザイン修正などによって比較したいキャプチャの内容が何度も変わってしまうと、その都度キャプチャをとるのはかなりの手間で、とてもつらいです。

本記事ではBackstop.jsというライブラリを使用して、視覚的な変化を「確実かつ少ない労力で避ける」方法についてまとめておきます。

BackstopJS

BackstopJS automates visual regression testing of your responsive web UI by comparing DOM screenshots over time.

具体的には

  • ローカル環境の画面キャプチャを取り
  • コードを修正し
  • 変更後、キャプチャと比較し差分が出ていないかをテスト

ということをします。

細かいことはいいのでとりあえず動かしたい!」という方はまとめからどうぞ。 コマンドや設定などがシンプルなので、少し知識があれば問題なく使用できると思います。

Backstop.jsでできること

簡単なコマンドで視覚的な変更の有無を確認できます。 実行サンプルは以下で、REFERENCE(キャプチャ)とTEST(開発中の画面)を比較しています。

まずは見た目に差分が出ず、テストを通過するパターン。

_Users_un-t_ieda_work_develop_191218_11ty_sample_backstop_data_html_report_index.html (2).png

試しに画像のmargin-rightを 10px → 20px に変更してみます。 差分は以下のように確認できます。

_Users_un-t_ieda_work_develop_191218_11ty_sample_backstop_data_html_report_index.html (1).png

画像と文字の改行位置が変わり、 DIFF(差分)が出ていることが確認できます。 Backstop.jsはこのように視覚的な差分を数コマンドで確認することができるツールです。

開発環境

  • node-version 10.15.3
  • Backstop.js 4.4.2

導入編

インストール

公式ではグローバルインストールが推奨されていますが、ローカルでも問題なく使用できます。 自分はプロジェクトルートで

npm install backstopjs -D

を実行し、開発環境依存のパッケージとしてモジュールを追加しています。

初期化

インストールが完了したら、

backstop init

を実行します。

これでキャプチャの参照先などの設定が書かれたbackstop.jsonと、キャプチャのデータなどをまとめてあるbackstop_dataディレクトリが自動生成されます。 デフォルトではbackstop.jsonの中身は以下のようになっています。

{
  "id": "backstop_default",
  "viewports": [
    {
      "label": "phone",
      "width": 320,
      "height": 480
    },
    {
      "label": "tablet",
      "width": 1024,
      "height": 768
    }
  ],
  "onBeforeScript": "puppet/onBefore.js",
  "onReadyScript": "puppet/onReady.js",
  "scenarios": [
    {
      "label": "BackstopJS Homepage",
      "cookiePath": "backstop_data/engine_scripts/cookies.json",
      "url": "https://garris.github.io/BackstopJS/",
      "referenceUrl": "",
      "readyEvent": "",
      "readySelector": "",
      "delay": 0,
      "hideSelectors": [],
      "removeSelectors": [],
      "hoverSelector": "",
      "clickSelector": "",
      "postInteractionWait": 0,
      "selectors": [],
      "selectorExpansion": true,
      "expect": 0,
      "misMatchThreshold" : 0.1,
      "requireSameDimensions": true
    }
  ],
  "paths": {
    "bitmaps_reference": "backstop_data/bitmaps_reference",
    "bitmaps_test": "backstop_data/bitmaps_test",
    "engine_scripts": "backstop_data/engine_scripts",
    "html_report": "backstop_data/html_report",
    "ci_report": "backstop_data/ci_report"
  },
  "report": ["browser"],
  "engine": "puppeteer",
  "engineOptions": {
    "args": ["--no-sandbox"]
  },
  "asyncCaptureLimit": 5,
  "asyncCompareLimit": 50,
  "debug": false,
  "debugWindow": false
}

色々オプションがありますが最低限の設定として、開発中のローカルサーバーからキャプチャを取りたいので、 "url": "https://garris.github.io/BackstopJS/"をローカルサーバーのURLに変更しておきます。

自分の環境では "url": "http://localhost:8080"にしていますが、ここは各々の環境に応じてキャプチャを取りたいページのURLを設定しておけば大丈夫です。

キャプチャ

ローカルサーバーを立ち上げた状態で

backstop reference

を実行すると backstop_data/bitmaps_referenceディレクトリ以下にpng形式で画面のキャプチャが保存されます。 デフォルトでは以下の名前で二枚キャプチャが保存されているかと思います。

  • backstop_default_BackstopJS_Homepage_0_document_0_Tablet_view.png
  • backstop_default_BackstopJS_Homepage_0_document_1_PC_view.png

画像名やキャプチャの画面幅など、オプションの設定については後述します。 キャプチャを保存したら、コードの修正を行なっていきます。

テスト

コード修正後、

backstop test

を実行することでブラウザが立ち上がり、先ほど保存したキャプチャと修正後のローカル環境が比較されます。

承認

テストで差分が出なかった場合、 または差分が出たが想定される通りの挙動であった場合、

backstop approve

を実行することでキャプチャが更新されます。

更新前のキャプチャとテストの結果は backstop_data/bitmaps_testディレクトリ以下にアーカイブされていきます。

実践編

最初にキャプチャを取る時のみ $ backstop referenceを使用します。

最初のキャプチャを取った後は、基本的には

  1. コード修正
  2. 比較 $ backstop test
  3. 承認 $ backstop approve

の手順を繰り返すことで、安全にリファクタリングを行うことができます。 簡単でとても嬉しいですね!

カスタマイズ編

ここまでライトにBackstop.jsを使用する方法について書いてきましたが、キャプチャ前の挙動などをもっと細かくオプションで設定できます。 backstop.jsonの中身を見ればある程度察しがつくかと思うのですが、簡単な設定方法をいくつか書いておきます。

フォームの入力やキャプチャしたいセレクターの選択など、ここに書いていないオプションも設定することができるので、詳細は公式のリファレンスをご確認ください。

複数のビューポートで見比べたいんですが…

"viewports": [
  {
    "label": "phone",
    "width": 320,
    "height": 480
  },
  {
    "label": "tablet",
    "width": 1024,
    "height": 768
  }
]

の部分を変更します。 デフォルトではPhone(320px)とTablet(768px)の2サイズでキャプチャが保存されるようになっています。 自分は基本的にSP(375px), Tablet(768px), PC(1024px)の3サイズで比較を行うように設定しています。 labelはキャプチャの画像名に反映されるので、サイズの追加などに合わせて適宜変更します。

比較したいページが数ページあるんですが…

"scenarios": [
  {
    "label": "BackstopJS Homepage",
    "cookiePath": "backstop_data/engine_scripts/cookies.json",
    "url": "https://garris.github.io/BackstopJS/",
    "referenceUrl": "",
    "readyEvent": "",
    "readySelector": "",
    "delay": 0,
    "hideSelectors": [],
    "removeSelectors": [],
    "hoverSelector": "",
    "clickSelector": "",
    "postInteractionWait": 0,
    "selectors": [],
    "selectorExpansion": true,
    "expect": 0,
    "misMatchThreshold" : 0.1,
    "requireSameDimensions": true
  }
]

の箇所に比較したいページを追加します。 公式によるとscenariosに必須なのはlabelとurlで、他のオプションは省略可能なようです。

scenarios – This is where you set up your actual tests. The important sub properties are… scenarios[n].label – Required. Also used for screenshot naming. scenarios[n].url – Required. Tells BackstopJS what endpoint/document you want to test. This can be an absolute URL or local to your current working directory. TIP: no other SCENARIO properties are required. Other properties can just be added as necessary

の3ページをチェックしたいなら、以下のように追加、変更します。 (以下はオプションをすべて省略したパターンです。)

"scenarios": [
  {
    "label": "page_index",
    "url": "http://localhost:8080/",
  },
  {
    "label": "page_2019",
    "url": "http://localhost:8080/we_wish_you_a_merry_christmas.html",
  },
  {
    "label": "page_2020",
    "url": "http://localhost:8080/happy_new_year.html",
  }
]

これで設定した3ページ分のキャプチャをとってくれるようになります。 ビューポートの設定もしていれば、ページ数×ビューポート数のキャプチャが保存されます。

hoverの確認もしたいんですが…

  "hoverSelector": "",

にhoverを確認したいセレクターを記述します。

  "hoverSelector": ".content_btn",

のような感じですね。 これで要素をhoverした状態でキャプチャを取ってくれます。

要素をクリックした後の画面を比較したいのですが

モーダルやアコーディオンなどですね。 hoverと同じような感じですが

  "clickSelector": "",

にclick時の挙動を確認したいセレクターを記述します。

  "clickSelector": ".content_btn",

のような感じですね。 これで設定したセレクターをクリック後にキャプチャを取ってくれるようになります。

毎回差分が出てしまう要素があるんですが…

動的に内容が変化するコンテンツなど、キャプチャのたびに見た目が変わってしまう要素は非表示を設定することで対応します。

  "hideSelectors": [],

の中にセレクターを記述することで、要素が非表示になった状態でキャプチャが取られます。

見た目が変わっているのにテストを通っているんですが…

_Users_un-t_ieda_work_develop_191218_11ty_sample_backstop_data_html_report_index.html (4).png

上のキャプチャのように「もっと読む」の部分に差分が出ているはずなのですが、テストを通ることがあります。 backstop.jsonにはどの程度の変化まで許容するかを設定するオプションがあり、差分が設定値より小さかった場合テストをパスします。

  "misMatchThreshold" : 0.1,

の値を変更することで、テストの厳密さを指定できます。 デフォルトでは0.1%の差分までは許容されており、上のキャプチャだとdiffの値が0.08%となっているためテストをパスしている訳ですね。

試しに"misMatchThreshold" : 0.05で設定してみると

_Users_un-t_ieda_work_develop_191218_11ty_sample_backstop_data_html_report_index.html (9).png

diffの値は変わらず0.08%ですが、テストはパスしなくなります。 (余談なのですが、テスト画面でキャプチャ名の上に要素をhoverすると、上記のdiff値などの少し細かい情報が見られます。)

令和の差分がちょっとつらいんですが…

来年はもっといい一年になるよう、サンタさんにお願いしておきましょう。

まとめ

導入の流れ

  1. npm install backstopjs -D でインストール
  2. backstop init → 設定ファイル(backstop.json)を編集して初期設定
  3. backstop reference で設定ファイルに記述されたURLの画面をキャプチャ

実践の流れ

  1. コード修正
  2. backstop test でキャプチャと比較
  3. backstop approve でキャプチャを更新
  4. 気がすむまで 4 ~ 6 を繰り返し

これでコードのリファクタリングも安心ですね。 自分で何度もキャプチャと開発環境を見比べる必要が無くなり、クリスマスも少し幸せに過ごせそうです。

残すところ10日程度ですが、皆様良い2019年をお過ごしください。 来年も良いことがありますよう。