【BackstopJS】見た目の比較テストツールで、デザインデータとコーディング結果のビジュアル比較テストを作ってみた

  • URLをコピーしました!

※この記事ではBackstopJSそのものの使い方については解説しません。

実際に作ったBackstopJSのプロジェクト (2024年9月現在。内容は変更される場合があります)

目次

BackstopJSおよび見た目の比較テストツールとは

BackstopJSはビジュアルリグレッションテストツールのひとつです。

「ビジュアルリグレッションテスト」とは、サイトなどの一部に手を加えた結果、予期しない場所の見た目が変わってしまっていないかをテストすること。
たとえばWordPressをアップデートした結果、どこかのページの表示が崩れている……というようなことが起きていないかをテストすることです。

BackstopJSとは、このビジュアルリグレッションテストを自動化するためのツールのひとつ。
たとえば「WordPressをアップデートする前のスクリーンショット」と「アップデート後のスクリーンショット」を撮影し、二つの画像に差異がないかを自動で検証し、レポートを生成してくれます。

BackStopJSのGitHub

BackstopJSの機能を使って、デザインデータから書き出したPNGと、コーディング後のサイトを比較したい

BackstopJSでは、通常「ひとつのURLに対して、昔撮影したスクリーンショットを正解として、今のスクリーンショットをテスト」または「実稼働環境のスクリーンショットを正解として、ステージング環境のスクリーンショットをテスト」します。

でも、今回は「デザインデータから書き出したPNGを正解として、コーディング後のURLをテスト」したいと考えました。

これは「正解画像(reference)」のフォルダにデザインデータから書き出したPNGを入れておくことで実現できます。

正解画像(reference)のファイル名の制限

ここで問題になったのは正解画像のファイル名の制限です。

BackstopJSでは、プロジェクトとシナリオ(どのようなテストをするか)の設定によって、生成される画像のファイル名が
[プロジェクトのID]_[シナリオのラベル]_0_[どの部分のスクリーンショットか]_0_[ビューポートの名前].png
のように決まっています。

(0の部分は、どちらかの0がプロジェクト設定のviewports:[]の番号ですが詳細を理解していないため割愛します)

この命名の通りに正解画像を作成し保存しておかないと、BackstopJSは目当ての正解画像を見つけることができません。
見つけることができないとBackstopJSは以下のようなエラーを出します。

BACKSTOP ERROR: Reference file not found D:\Files\[プロジェクトのフォルダ]\reference\image_001_0_document_0_desktop.png

デザインデータから書き出しやすいファイル名から、BackstopJS用のファイル名に書き換える処理をはさむ

BackstopJS側で正解画像のファイル名のルールを変えるのは難しかったため、

  • 所定のフォルダには「001.png」「002.png」「003.png」と連番でファイルを保存
  • npm scriptsの実行時にファイル名を書き換える

という方法で解決することにしました。

ファイル名に書き換える処理

/reference/001.png を /reference/image_001_0_document_0_device.png というように変更するコードです。

※途中で「数字を3桁にパディング」するところは、今回の運用ルール上は必要がない気もしますが…

package.jsonのscriptsに "rename:image": "node rename-reference-images.js" のように書いて置きます。

const fs = require("fs");
const path = require("path");

const referenceDir = path.join(process.cwd(), "reference");

// 指定されたディレクトリ内のすべてのPNGファイルを取得
fs.readdir(referenceDir, (err, files) => {
  if (err) {
    console.error("ディレクトリの読み取りエラー:", err);
    return;
  }

  // 各ファイルを処理
  files.forEach((file) => {
    // 拡張子がPNGかどうかを確認
    if (path.extname(file).toLowerCase() === ".png") {
      // ファイル名が数字のみかどうかを確認
      const match = file.match(/^(\d+)\.png$/);
      if (match) {
        // 数字を3桁にパディング
        const number = match[1].padStart(3, "0");
        // 新しいファイル名を作成
        const newFileName = `image_${number}_0_document_0_device.png`;
        const oldPath = path.join(referenceDir, file);
        const newPath = path.join(referenceDir, newFileName);

        // ファイル名を変更
        fs.rename(oldPath, newPath, (err) => {
          if (err) {
            console.error(`${file} のリネームエラー:`, err);
          } else {
            console.log(`${file} を ${newFileName} にリネームしました。`);
          }
        });
      }
    }
  });
});

リネームの処理のあとでBackstopJSの処理を走らせる

npm-run-all を使って、リネーム用のscriptsのあとにBackstopJSのscriptsを実行させます。

"image": "run-s rename:image test:image" こんな感じにしました。

test:image の方でBackstopJSのコマンドを走らせています。

実行するとどうなるか

デザインデータから書き出した連番の画像をフォルダに格納してコマンドを実行すると

  • ファイルのリネームの処理
  • BackstopJSのスクリーンショット撮影処理
  • 比較テストの実行
  • レポートの表示

が実施されます。

> backstop-project@1.0.0 image
> run-s rename:image test:image


> backstop-project@1.0.0 rename:image
> node rename-reference-images.js

001.png を image_001_0_document_0_device.png にリネームしました。
002.png を image_002_0_document_0_device.png にリネームしました。
003.png を image_003_0_document_0_device.png にリネームしました。
004.png を image_004_0_document_0_device.png にリネームしました。

> backstop-project@1.0.0 test:image
> backstop test --config=backstop.image.js

<略>
Disposing Browser
      COMMAND | Executing core for "report"
<なんかエラー>
ERROR: "test:image" exited with 1.

Process finished with exit code 1

レポートでは、ふたつの画像の違う部分だけをピンク色で強調して示すDiff画像も合わせて表示されるため、どこに問題があったかを確認することができます。

※今回の例では正解画像を真っ黒のPNG、チェックするURLを example.com にしたので、比較テストの様子が分かりづらいです

BackstopJS Reportのスクリーンショット

実際のコード

実際に作ったコードはGitHubで公開してあります

実際に作ったBackstopJSのプロジェクト (2024年9月現在。内容は変更される場合があります)

補足:なぜBackstopJSを選んだか

  1. もともとPlaywrightでスクリーンショット撮影→Resemble.jsでDiff画像作成を別々に実行するプログラムを使っていた
    • 保存されたDiff画像を1枚ずつ開いて確認するのが面倒
    • 正解画像とスクリーンショットを左右に並べるのも手動でやっていたので面倒
    • デザインデータから書き出した画像に対応できていなかった
  2. Playwrightのビジュアルリグレッションテスト用のコマンドを使ってレポートを出すように変更した
    • レポートページが出せるようになって便利になった
    • デザインデータから書き出した画像にも対応した
    • カルーセルスライダーなど動き続ける要素があると、タイムアウトしてテストができず困った
  3. BackstopJSなら比較をResembles.jsで行いつつ、レポートページも表示できると分かった
    • ※ BackstopJSでのDiffには @mirzazeyrek/node-resemble-js を使っているようです。
    • レポートページの便利さはPlaywrightのビジュアルリグレッションテストと近い
    • カルーセルスライダーなどの動き続ける要素があっても、タイムアウトしない
    • がんばればデザインデータから書き出した画像にも対応できた

以上が、BackstopJSを選定した理由となります。

補足:Figmaからファイルを連番で書き出す方法

デザインデータ上でフレームが必ずしもきれいに整列しているとは限らないので、まずは整列します。

フレームを選択して、Align Topにマウスを乗せている

※このとき、レイヤー(選択中以外)を全て閉じる:⌥(Alt) + L でフレームから飛び出た要素が無いかを確認し、フレームから飛び出た要素はフレームの中に収めるか、要らないものは消しておきます。

次にレイヤー一覧上のフレームの並び順を、見た目の順番と同じになるように並び変えます。
プラグインの「Sorter」で「Sort Position」を実行します。

プラグインのSorterで「Sort Position」を選択している

レイヤーを複数選択した状態で右クリックメニューの「Rename」を実行します。
Rename to という入力欄に「$nnn」を入力すると、一番上のレイヤーから「001」「002」というように連番にすることができます。

※「Number ↑」というボタンを押すと「$nn」と入力されますのでそこに「n」を一つ足すだけです。

複数のレイヤーを選択した状態で「Rename」を実行する
Rename 4 layersというウィンドウ。2つ目の入力欄に$nnn と入力されている。

フレームを選択した状態でExportの設定を1x PNG として画像をExportします。

Exportの設定を1x、PNGとして「Export 4 layers」というボタンが現れているところ
よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

コメントは日本語で入力してください。(スパム対策)

CAPTCHA

目次