backstopjsの設定方法を解説!【リファクタリング作業効率化】

こんにちは!のせっち@nosecchi01です。

サイト運営をしていたり、他の方からコードを引き継いだ時、「このコードはいるのかな?」と思うときがあると思います。しかし、

もしかしたら、ページが崩れるかもしれない…

と考えて放置した(している)という経験は誰にでもあるはずです。
特にCSSはグローバルに効いてしまうので、思わぬところで表示崩れが起きてしまう可能性があります。

変更前と変更後をスクリーンショット比較で確認できる『ビジュアルリグレッションテスト』というものがあります。

対象ページを全て比較して、差分があれば、色を付けて教えてくれるので非常に便利です。

backstopjsとは

ビジュアルリグレッションテストを行うツールの一つです。

backstopjsの画面例。差分が赤(ピンク)色で表示されている

上記の通り、REFERENCE – TEST – DIFFと並んでいて、差分がある場合には、色を付けて表示してくれます。
(背景色を黒からグレーに変わっているのでDIFFと判定されています。)

視覚的に差分がわかるので、意図している変更なのか、そうではないのか、一目瞭然です!

それではbackstopjsのセットアップ方法を解説します。

backstopjsのgithubページ

backstopjsのインストール

「ビジュアルリグレッションテストを行いたい」ということは既にプロジェクトがあるはずですので、そちらに移動してください。

cd PROJECT_NAME

プロジェクトに移動したら、早速パッケージをインストールしていきましょう!

npm i -D backstopjs

npm関連のファイル群がない人は先にnpm init -yしておいてください。

必要なパッケージはこれだけですが、設定しないと何も動きません。

backstopjsの設定

backstopjsの設定をしていきます。

backstop.config.jsを作成してください。
下記を追加します。

// --production を指定すると本番環境を見る
let baseUrl = "http://localhost:8080/"; // ローカルサーバーのURL
if (process.argv.includes("--production")) {
  baseUrl = "https://www.yoursite.com/"; // 本番サイトのURLを入れる
}
const allPaths = [""];

const targets = [];
process.argv.slice(4).forEach((arg) => {
  switch (arg) {
    case "--production":
      baseUrl = "https://www.yoursite.com/"; // 本番サイトのURLを入れる
      break;
    default:
      if (!allPaths.includes(arg)) {
        throw new Error(`Unknown option or path: ${arg}`);
      }
      targets.push(arg);
  }
});

module.exports = {
  id: "backstop_default",
  viewports: [
    {
      label: "sp",
      width: 375,
      height: 667,
    },
    {
      label: "pc",
      width: 1280,
      height: 720,
    },
  ],
  scenarios: (targets.length ? targets : allPaths).map((path) => ({
    label: path || "Top",
    url: `${baseUrl}/${path}`,
  })),
  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",
  },
  report: ["browser"],
  engine: "puppeteer",
  asyncCaptureLimit: 5, // 画面取り込みの並列処理を制限
  asyncCompareLimit: 50, // 画面比較の並列処理を制限
  debug: false, // デバック結果をターミナルに出力
  debugWindow: false, // 実行中の処理をChromeに表示
  fileNameTemplate: "{scenarioLabel}_{viewportLabel}", // ファイル名を指定
  onReadyScript: "puppet/onReady.js",
};

ローカルサーバーが必要です。Gulp、Nuxtなどなどを用意してください。

下記の部分をご自身のローカルサーバーのURLに変更します。

let baseUrl = "http://localhost:8080/"; // ローカルサーバーのURL
if (process.argv.includes("--production")) {
  baseUrl = "https://www.yoursite.com";
}
// 省略

次に、backstop_dataを作成、その中にengine_scripts > puppet > onReady.jsを作成します。

こんな感じのディレクトリになります。
(混乱しないように関係ないファイルは隠しました)

onReady.jsを編集します。

module.exports = async (page, scenario) => {
  console.log("SCENARIO > " + scenario.label);

  await page.evaluate(() => {
    // loading attributeを無効に
    document.querySelectorAll('[loading="lazy"]').forEach((el) => {
      el.loading = "eager";
    });

    // スクロールアニメーションを無効に
    document.querySelectorAll(".fade-in").forEach((el) => {
      el.style.opacity = 1;
    });

    if (window.jQuery) {
      window.jQuery.fx.off = true; // jQueryが提供する全ての動作からアニメーション処理を外す。
    }
  });

  await page.waitForTimeout(1000);
};

これで設定ができました。

最後にnpm scriptsで実行できるようにします。
package.jsonを編集します。

{
"scripts": {
    "visual-reference": "backstop reference --config=backstop.config.js",
    "visual-test": "backstop test --config=backstop.config.js"
  },
}

これで下記のように実行できます。

## REFERENCEを作成する
npm run visual-reference

## TESTを実行する
npm run visual-test

これでテストが実行されます。

実行結果はbackstop_data > html_reportの中に生成されます。

npm run visual-referenceREFERENCEを作成後、わざと変更を加えてnpm run visual-testを実行すれば、差分を表示してくれます。

backstopjsの実行例:差分が出ている

サーバーアップされている本番サイトとの差分比較をする場合

既にサイトアップされている本番サイトとの差分比較をしたい場合があるはずです。

その場合は、まずbackstop.cofig.jsで本番サイトのURLを指定します。

let baseUrl = "http://localhost:8080/";
if (process.argv.includes("--production")) {
  baseUrl = "https://www.yoursite.com/";
}
const allPaths = [""];

const targets = [];
process.argv.slice(4).forEach((arg) => {
  switch (arg) {
    case "--production":
      baseUrl = "https://www.yoursite.com/";
      break;
    default:
      if (!allPaths.includes(arg)) {
        throw new Error(`Unknown option or path: ${arg}`);
      }
      targets.push(arg);
  }
});


あとは、--productionを付けて実行すればOKです。

## 本番サイトでREFERENCEを作成
npm run visual-reference --production

設定内容の解説(抜粋)

backstop.cofig.jsおよび、onReady.jsに書いた内容で、特に重要そうなところを抜粋して解説します。

backstopjsのオプションはGithubに記載があるのでそちらを参照しながら見ていただくのがいいです。

backtopjsのgithubページ

backstop.config.jsの解説

debug: false, // デバック結果をターミナルに出力
debugWindow: false, // 実行中の処理をChromeに表示

この2つはfalseでいいと思います。特にdebugWindowはchromeを開いてしまうので。

クリックやホバーをしてから、スクリーンショットを撮ってもらうこともできるので、動作確認したい方はtureにしてください。

onReadyScript: "puppet/onReady.js",

スクリーンショットを撮る前に実行したいスクリプトを書きます。

onReady.jsの詳しい解説は次の章からです!↓↓

テストでの不都合を解消する

テストを実行していると、なにかと不都合が出てくるはずです。
例えば、

  • スクロールアニメーションの部分が真っ白
  • lazyloadした画像が読み込まれたり、読み込まれなかったり
  • 動的なコンテンツがある

などなどです。

backstopjsも完璧ではないので、ある程度の折り合いは必要と思うのですが、なるべくDIFFが出ないようにしたいです。

backstopjsのオプションに用意されていないものは自分でscriptを書きましょう!

    // loading attributeを無効に
    document.querySelectorAll('[loading="lazy"]').forEach((el) => {
      el.loading = "eager";
    });

これでimgタグなどのloading="lazy"を無効にします。

    // スクロールアニメーションを無効に
    document.querySelectorAll(".fade-in").forEach((el) => {
      el.style.opacity = 1;
    });

これはスクロールでふわっと出現するアニメーションがある想定です。

上記の例では.fade-inopacity: 0が書いてあるので、opacity: 1に上書きしています。

    if (window.jQuery) {
      window.jQuery.fx.off = true; // jQueryが提供する全ての動作からアニメーション処理を外す。
    }
  });

window.jQuer.fx.offを設定すると、jQueryが提供する全ての動作からアニメーション処理を外すことができるそうです。

jQuery.fx.off

jQuery日本語レファレンス:http://semooh.jp/jquery/api/effects/jQuery.fx.off/

動的なコンテンツが毎回DIFFになって面倒だ、という場合はhideSelectorsを使うといいです。
例えばslickスライダーが毎回DIFFになってストレスだ、という場合

scenarios: (targets.length ? targets : paths).map((path) => ({
    label: path,
    url: `${baseUrl}/${path}`,
    hideSelectors: [".slick-list"],
  })),

hideSelectorsを使うと、該当部分はvisibility: hiddenになってくれます。

これで領域は確保しつつ隠してくれるので、DIFFは出なくなります。
ただし該当箇所は真っ白になってしまうので、slick部分に変更があっても検知してくれません。使うかどうかはお任せします。

おまけ:ローカルサーバーにbrowser-syncを使うなら

ローカルサーバーにbrowser-syncを使っているなら、"BrowserSync Connected"のメッセージは消しておきたいですね。これが原因で全ページエラーになってしまうので…。

const browserSync = require("browser-sync");

const buildServer = (done) => {
  browserSync.init({
    port: 8080,
    files: ["**/*"],
    server: { baseDir: "./" },
    notify: false, // 追加
  });
  done();
};

notify: falseを追加すれば、"BrowserSync Connected"は出なくなります。

まとめ

  • ビジュアルリグレッションテスト・backstopjsを使えば変更前・後の差分が一目瞭然になる。
  • 設定を追加することである程度カスタマイズ可能
  • 完璧な動作は難しいことがあるので、設定追加で対応できないこともある。ある程度は折り合いをつける。

この記事を書いた人

のせっち

福岡県出身。早稲田大学卒。創業100年の鉄鋼商社でバンコク駐在最年少抜擢。毎日擦り切れるまで働かなくても幸せに生きている人々を見てフリーランスになりました。
- WordPressが得意!
- 初心者向けGulp教材は購入者100部以上!