BackstopJSで始めるリグレッションテスト

この記事は[コネヒト Advent Calendar 2017](https://qiita.com/advent-calendar/2017/connehito) 10日目の記事です。

今日はVisual Regression Testtoolである BackstopJS を使ってみたのでそのレポートをお送りします。

# BackstopJSとは？
- [garris/BackstopJS](https://github.com/garris/BackstopJS)
- 変更の前後でスクショを取って差分をチェックできるビジュアルリグレッションテストツール
- CSSが意図せず崩れていないかチェックできる
- 2016年9月の大幅なバージョンアップ(v2)でかなり使いやすくなった
  - gulpからの完全脱却
  - jsonの他にnode scriptによるconfigもサポート

## こんな風に差分が見れます
- diff
    - 見出しの文言が変わっている
    - 画像が「グリーン」から「ネイビー」に変わっている
<img width="705" alt="backstopjs" src="https://qiita-image-store.s3.amazonaws.com/0/6159/51ceed44-1561-e913-0de1-8b1c9784505d.png">

# 実際にリグレッションテストをやってみよう！
## テスト対象
- [fortkle/backstopjs-css-test](https://github.com/fortkle/backstopjs-css-test)
- Laravelで作ったただのWebページ
  - ログイン認証機能あり
  - 未ログイン、一般ユーザー、管理者ユーザーで見え方が少し異なる
  - 広告のようにランダムに表示される画像がでたり、モーダルのあるページを用意

![sample_site.gif](https://qiita-image-store.s3.amazonaws.com/0/6159/43949baf-1e6f-1b02-1f02-7974843908e5.gif)

## やりたいこと
- 各ユーザー（Admin, Normal, Non-login）毎にページが崩れていないか
- PCとモバイルでページが崩れていないか
- ランダムに出る広告や動的なモーダルなどもうまく対処したい

## BackstopJSの進め方
1. テストシナリオを書く
2. 変更前のスクリーンショット（ Reference ）を保存
〜機能追加など〜
3. 変更後のスクリーンショット（ Test ）を保存
4. 両者を比較し、差分として出たものが意図的であればReferenceに取り込む

## 実際にテストを動かす！
[今回のサンプルリポジトリ](https://github.com/fortkle/backstopjs-css-test)をcloneしてREADMEに記載の`Install`と`Setup`を実行してください。
これで準備完了です。早速テストを動かしてみましょう。動かし方はローカルサーバーを起動して、BackstopJSのコマンドを実行するだけです。

```sh
// ローカルサーバーを立ち上げ
php artisan serve
// テスト時の比較元として使う画像を取得
backstop reference --config=./backstop.config.js
// テストしたい環境の画像を取得
backstop test --config=./backstop.config.js
```

上記コマンドを実行すると以下のようにHTMLレポートが生成され、結果を確認することができます。

<img width="1270" alt="BackstopJS_Report.png" src="https://qiita-image-store.s3.amazonaws.com/0/6159/a7d740e0-16ee-1aed-65b8-8d346758f5d9.png">

## ポイント
### シナリオの分割管理
デフォルトではjson1ファイルで設定とシナリオを記述していく必要がありますが、サイトの規模が大きくなるとメンテナンスがしづらくなってきます。

backstopJSはv2からnode scriptによるconfigもサポートしたので設定とシナリオを分離し、さらにシナリオもコンテキストに合わせて分割しましょう。

サンプルサイトでは設定を `backstop.config.js`に、シナリオを `backstop_data/scenarios/` 配下に記述しています。


```js

const fs = require('fs')
let allScenarios = []

function loadScenarios (dirname) {
  const files = fs.readdirSync(dirname)
  files.forEach ((file) => {
    const content = JSON.parse(fs.readFileSync(dirname + file, 'utf-8'))
    if (Array.isArray(content)) {
      content.forEach((scenario) => {
        allScenarios.push(scenario)
      })
    } else {
      allScenarios.push(content)
    }
  })
}

loadScenarios('backstop_data/scenarios/')
```

<img width="251" alt="backstopjs-css-test_backstop_data_scenarios_at_master_·_fortkle_backstopjs-css-test.png" src="https://qiita-image-store.s3.amazonaws.com/0/6159/499325ff-b4ae-8247-8c24-775488bfba79.png">

### ログイン状態の実現やUAの変更
backstopJSではcasper.jsやchromyを使ってテスト対象のページにUserAgentを変更してアクセスしたり、ログイン処理を行ったりすることができます。

サンプルリポジトリでは `backstop_data/casper_scripts/onBefore.js` で事前にこれらの処理を行うことで各ユーザー（Admin, Normal, Non-login）毎のテストや、PCとモバイルでのテストの切り替えを実現しています。

```js
const User = require('./test-users');

module.exports = function (casper, scenario, vp) {

  casper.echo('Setting UA');
  casper.then(function(){
    if (vp.name === 'PC') {
      casper.userAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X)');
    } else if (vp.name === 'Mobile') {
      casper.userAgent('Mozilla /5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X)');
    }
  });

  casper.echo('Setting login user');
  casper.page.cookies = [];
  if (scenario.loginAs) {
    const baseUrl = 'http://localhost:8000';
    casper.thenOpen(baseUrl + '/login', function(){
      var loginFormName = 'form.form-horizontal';
      if (this.exists(loginFormName)) {
        var user = User[scenario.loginAs];
        this.fill(loginFormName, {
          'email': user.id,
          'password': user.pass
        }, true)
      }
      this.wait(1000);
    });
  }

  casper.thenOpen(scenario.url);
};
```

# まとめ
スクリーンショットを取れるツールや画像のdiffが取れるツールはありますがそれらを組みわせてリグレッションテストに活用するのは少し大変です。
backstopJSではそれらをほぼ意識すること無くリグレッションテストが書けるのが特徴です。
Headless Chromeのサポートなどまだまだ発展途上ですが、ぜひ活用してみてください！