astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

BackstopJSではじめるビジュアルリグレッションテスト

f:id:astamuse:20170829184049p:plain こんにちは。デザイン部でフロントエンドエンジニアをしているkitoです。
今回は、BackstopJSを使ったビジュアルリグレッションテストについて書きたいと思います。
ビジュアルリグレッションテストとは視覚的な回帰テストのことで、具体的にはスクリーンショットを撮影して差分抽出して行うテストです。
近年のWebフロントエンド開発では、SassやWebpackのような開発環境が整うに従ってスタイルシートをモジュール化することが増えています。 それはスタイルの汎用性を高めることに大きく貢献していますが、一方で、あるパーツのスタイル修正が想定外の場所で悪影響を及ぼしてしまう可能性をもつようになりました。 この問題に対処するために、Enduring CSSのような新しいタイプの設計手法も考えられてはいますが、既存のサービスに導入するにはかなり敷居が高いでしょう。
そこで注目したいのが、ビジュアルリグレッションテストです。自動テストでデザインの異常を検知し、モジュール化して見通しが悪くなったスタイルを管理可能にします。

BackstopJSの導入

それではビジュアルリグレッションテストツールであるBackstopJSを導入していきましょう。 手元の環境は以下です。

node -v
v8.1.3
python --version
Python 2.7.11

Pythonは3系だとbackstopjsのインストールに失敗する場合があります。

mkdir example_backstopjs
cd example_backstopjs
npm init
npm install --save-dev backstopjs

これでインストールできました。 次にbackstop.jsの設定ファイルを作成します。

backstop init

backstop.jsonとbackstop_dataというフォルダが作成されたと思います。 backstop.jsonはbackstop.jsの設定を記述します。こちらにテストサイトのurlなど様々なオプションを追加しましょう。
今回は以下のように設定します。

{
    "id": "test",
    "viewports": [{
            "label": "phone",
            "width": 320,
            "height": 480
        },
        {
            "label": "pc",
            "width": 1024,
            "height": 768
        }
    ],
    "scenarios": [{
        "label": "BackstopJS testing",
        "url": "http://localhost:5000/",
        "hideSelectors": ["iframe"],
        "removeSelectors": [],
        "selectorExpansion": true,
        "selectors": [],
        "readyEvent": null,
        "delay": 0,
        "misMatchThreshold": 0.1,
        "requireSameDimensions": true,
        "onBeforeScript": "onBefore.js",
        "onReadyScript": "onReady.js"
    }],
    "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"
    },
    "engineFlags": [],
    "engine": "phantomjs",
    "report": ["browser"],
    "debug": true,
    "debugWindow": true
}

viewportsを指定できるので、レスポンシブサイトなどはこちらでPCとスマホサイト両方の画面サイズを設定できます。ユーザーエージェントでPCとスマホサイトを出し分けている場合、別の設定が必要になります。(後で説明します。)  
scenariosでは、更に細かい設定ができます。labelはスクリーンショットの名前、urlはテストするサイトのurl、hideSelectorsとremoveSelectorは、それぞれ指定セレクターを非表示にするオプションですが、hideSelectorsはvisibility: hiddenになり、removeSelectorsはdisplay: noneになる違いがあります。
selectorsは、スクリーンショットを撮影するパーツを指定します。ここではbody以下すべてを撮影する設定になっています。もちろん、"#main"のように一部パーツのスクリーンショットを撮影することができます。
readyEventは指定の文字列がconsole.logに表示するまで処理を待ちます。SPAに使えそうです。
readySelectorは、指定セレクターの表示を待ってから処理に移るオプションで、遅延ロードされるコンテンツがあるサイトで使えるでしょう。delayはミリ秒単位で処理を遅延させます。misMatchThresholdは、テストがfailになる閾値を設定できます。 onBeforeScriptは、テストサイトにcookieのようなブラウザの設定を反映するJavaScriptを記述できます。上記のサンプルでは./backstop_data/engine_scripts/onBefore.jsにファイルを読み込みます。onReadyScriptは、スクリーンショットを撮影する前にUIの状態を変化させたり、ユーザーエージェントの設定するJavaScriptを記述できます。今回は、./backstop_data/engine_scripts/onReady.jsに、下記のようにユーザーエージェントを変更するコードを書いてください。

module.exports = function(casper, scenario, vp) {
    if (vp.label === 'phone') {
        casper.userAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1');
    } else {
        casper.userAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 ');
    }
    casper.thenOpen(scenario.url);
}

casperというのは、Casper.jsというE2Eテストライブラリーのことです。ここではCasper.jsを使って、ユーザーエージェントを設定しています。Casper.jsはBacksotp.jsが依存しているライブラリーなので、npm installした際に一緒にインストールされているはずです。

ビジュアルリグレッションテスト

では簡易的なローカルサーバーを立ててテストしてみましょう。 node.jsのserveモジュールをインストールします。さらにbootstrapも入れましょう。

npm install -g serve
npm install --save-dev bootstrap

index.htmlを下記のように作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Backsotp.js testing</title>
    <link href="./node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="jumbotron">
            <h1>example</h1>
            <p>
                <a class="btn btn-lg btn-primary" href="#" role="button">button</a>
            </p>
        </div>
    </div>
    <script src="./node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
</body>
</html>

ターミナルで別タブを開いて、example_backstopjsのディレクトリまで移動します。「serve」で簡易サーバーが立ち上がるのでhttp://localhost:5000/にアクセスしてみましょう。

serve

下のように表示されていると思います。 f:id:astamuse:20170829183825p:plain

準備が整ったので実際にテストしてみましょう。ターミナルで下記コマンドを入力してください。 すると./backstop_data/bitmaps_reference/以下に画像が2枚撮影されていると思います。 これがテストの元になるreference画像になります。

./node_modules/.bin/backstop reference

次に下記を実行してください。

./node_modules/.bin/backstop test

ブラウザーが立ち上がって下記のようなhtmlが表示されるます。これはbackstop.jsonのreportに"browser"を指定しているからです。 f:id:astamuse:20170829183835p:plain 全く何も変更していないので、テストは成功していると思います。 そして次にに、index.htmlのテキスト「button」を「button2」などに変更してわざと失敗させてみましょう。 failになり下記のようなスクリーンショットが表示されて、差分が着色されて表示されます。

f:id:astamuse:20170829183839p:plain

html上のshow statsのチェックを入れると、右側に下記のようなREPORTが表示されます。  misMatchPercentageの値をみると、0.39%ミスマッチであることがわかります。

Report: {
  "isSameDimensions": true,
  "dimensionDifference": {
    "width": 0,
    "height": 0
  },
  "misMatchPercentage": "0.39",
  "analysisTime": 49,
  "getDiffImage": null
}
Threshold: 1

engineをphantomjsではなくて、chromeに変更することも可能です。 実際のサービスサイトに使うにはmisMatchThresholdの値や除外セレクターの設定を細かく検討する必要があるでしょう。

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は遠慮なく採用サイトからご応募ください。お待ちしています。

Copyright © astamuse company, ltd. all rights reserved.