@boost-jp/nagio-visual-diff-uploader
VisualDiff(ビジュアルリグレッションテスト)のスナップショット(PNG)を nagio backend にアップロードする CLI です。
Playwright / Storybook / 任意の PNG 出力ディレクトリなど、スクリーンショットを吐くツールを問わず使えるようにするため、
@boost-jp/playwright(Playwright reporter)とは独立したパッケージにしています。
なぜ別パッケージなのか
- VisualDiff は画像アップロードのため sha256 計算・生バイナリ送信が必要で、
@boost-jp/playwrightの「依存ゼロ」方針を守れなくなる可能性がある - 失敗時に CI を落とすデフォルトの挙動を reporter とは変えたい(ネットワーク/認証エラー以外では CI を落とさない。差分の合否判定は GitHub Check Run「画面差分」で行う)
- Playwright 専用ではなく、Storybook や任意の PNG ディレクトリからも使いたい
並行数制限(src/concurrency-limit.ts)のような小さいユーティリティは、@boost-jp/playwright を import せず、このパッケージ内に自己完結で実装しています(パッケージ間の依存を増やさないため)。
設定ファイル
nagio-visual.config.json に書いておくと、CLI 実行のたびに同じ設定を使い回せます。
「CI では通るのに手元では設定が違う」という実行環境ごとのズレを防ぐ、
Chromatic の chromatic.config.json と同じ考え方です。
{
"app": "my-app",
"env": "production",
"dir": ".generated/visual-diff",
"viewports": {
"visual-chrome": "desktop-1920x1080",
"visual-iphone-15": "mobile-393x852"
}
}
- 置き場所は cwd から親へ遡って探す。
--config <path>で明示もできる (明示したのに見つからない場合はエラーにする。黙って無視されるのが最も困るため) - トークンは書かないこと。 このファイルはコミットされる。
--tokenか 環境変数NAGIO_VD_TOKENで渡す - 優先順位は フラグ > 環境変数 > 設定ファイル > 既定値。その場の指定が常に勝つ
- 未知のキーはエラーにする。綴り間違い(
viewPortsなど)を黙って無視すると 「設定したのに効かない」という最も気づきにくい壊れ方をするため
env(送信先の環境)
送信先を環境名で指定できる。URL を覚えたり設定ファイルに書き写したりしなくてよく、 ドメインが変わってもこのパッケージを上げれば追随する。
| 環境名 | 説明 |
|---|---|
production(既定) |
空。本番環境がまだ無いため |
dev01 |
共有 dev 環境(社外からは利用できません) |
local |
http://localhost:8181 |
既定の production は URL が空にしてある。環境の指定漏れで dev へ書き込む事故を
防ぐためで、何も指定しなければ「送信先が決まっていません」というエラーで止まる。
解決順は --base-url > NAGIO_VD_BASE_URL > 設定ファイルの baseUrl > 環境名。
URL の直接指定が常に勝つ(環境名は省略記法という位置づけ)。
viewports
「撮影ディレクトリ直下のサブディレクトリ名 → viewport 名」の対応。指定すると サブディレクトリごとに分けてアップロードする(1 回のアップロードには viewport を 1 つしか指定できないため)。
Playwright の project ごとに viewport が違い、同じファイル名で撮ることがあるので、
撮影側は project 名のサブディレクトリに書き出す(@boost-jp/playwright の
expect が NAGIO_VD_CAPTURE_DIR 配下にその形で出す)。
--viewport を明示した場合は分割せず、--dir 全体を 1 回で送る。
インストール
このパッケージは npmjs.com で公開しています。
npm install -D @boost-jp/nagio-visual-diff-uploader
使い方
nagio-visual-diff upload --app <visual_diff_app_key> --dir <dir> --base-url <url> --token <token> [options]
必須
| オプション | 説明 |
|---|---|
--app <key> |
VisualDiff アプリの key(例: web-visual)。--suite は非推奨のエイリアスとして使えるが --app への移行を推奨する(両方指定するとエラー) |
--dir <path> |
PNG を探すディレクトリ(再帰走査) |
--base-url <url> |
Nagio backend(connect-go)のベース URL(NAGIO_VD_BASE_URL でも指定可、フラグが優先)。既定値は無い(指定漏れに気づかず意図しない環境へ書き込む事故を防ぐため) |
--token <token> |
API トークン(NAGIO_VD_TOKEN でも指定可、フラグが優先)。未指定ならエラーで終了(exit code 1) |
任意
| オプション | デフォルト | 説明 |
|---|---|---|
--branch <branch> |
GITHUB_HEAD_REF || GITHUB_REF_NAME || "local" |
ブランチ名 |
--commit <sha> |
GITHUB_SHA || "unknown" |
コミット SHA |
--message <text> |
空文字 | コミットメッセージ |
--pr <number> |
0 |
PR 番号 |
--repository <owner/repo> |
GITHUB_REPOSITORY 環境変数(GitHub Actions が自動設定) |
リポジトリ識別子。現時点では backend には送信されません(GitHub 連携の受け側の設計が未確定のため、CLI 側で受け取れる状態にしてある予約中の値です。ログには出力されます) |
--viewport <name> |
"default" |
全スナップショット共通の viewport 名 |
--browser <name> |
"chromium" |
ブラウザ名 |
--concurrency <n> |
5 |
アップロード(およびファイルメタデータ計算)の並行数 |
--timeout-ms <n> |
15000 |
1 リクエストあたりのタイムアウト(ms)。CompleteBuildV1 を含む全リクエスト共通 |
--fail-on-diff / 設定ファイルの failOnDiff は 0.4.0 で廃止しました。指定するとエラー終了します。差分の合否は GitHub Check Run「画面差分」で判定してください。
動作フロー
--dirを再帰走査して*.pngを列挙する(0 件ならエラーで終了)- 各ファイルの sha256・width/height(PNG ヘッダから直接パース)・byteSize を計算する
VisualDiffService.CreateBuildV1でビルドを作成するVisualDiffService.ReserveSnapshotsV1で全スナップショットを予約する(uploadRequired: falseの分は既にストレージに同一ハッシュの画像がある = アップロード不要)uploadRequired: trueのエントリだけ、対応するファイルをPOST {baseUrl}/visual-diff/uploadに生バイナリで送る(--concurrencyで並行数を制限。1 件失敗しても他は続行し、最後に失敗件数をまとめて報告。全件失敗した場合のみ exit code 1)VisualDiffService.CompleteBuildV1を呼ぶ。backend は差分計算(pixelmatch)を待たず、ビルドをcomparing(計算中)にして即座に応答する(画像差分計算の非同期化。0.4.0)。CLI はこれを待たず終了する- サマリを標準出力に出す
[nagio-visual-diff] ビルド <visualDiffBuildId> を作成しました(app=web-visual, branch=..., commit=...)
[nagio-visual-diff] 12 件中 3 件はアップロード済みのため送信をスキップしました
[nagio-visual-diff] 9 件をアップロードしました(失敗 0 件)
[nagio-visual-diff] ビルドが完了しました: status=COMPARING, diffCount=0, unreviewedDiffCount=0
[nagio-visual-diff] 比較は非同期に行われます。結果は GitHub の Check Run「画面差分」か nagio のビルド詳細で確認してください。
エラーハンドリング
--token未指定、または backend が 401 相当(connect のunauthenticated)を返した場合は、はっきりしたエラーメッセージを stderr に出し exit code 1 で終了します(CI を落とします)CreateBuildV1が 404 相当(connect のnot_found)を返した場合、backend の応答自体は「対象が見つかりませんでした」としか言いません(越境アクセスと不存在を区別しない存在オラクル対策のため)。CLI 側でアプリ "<--app の値>" が存在しないか、アクセス権がありません。Web UI で作成してくださいという案内を追加します(アプリは CLI からは自動作成されません。事前に Web UI から Editor 権限で作成が必要です)- connect のエラーレスポンス(
{"code": "...", "message": "..."}の JSON、HTTP ステータスも 4xx/5xx)はパースしてメッセージに含めます - ネットワークエラー・タイムアウトも非ゼロ終了します
- ビルドが正常に完了しさえすれば(
comparingを含む。diff の有無に関わらず)exit code は 0 です。差分の合否は GitHub Check Run「画面差分」で判定してください
環境変数
| 変数 | 説明 |
|---|---|
NAGIO_VD_BASE_URL |
--base-url の代替。フラグが優先 |
NAGIO_VD_TOKEN |
--token の代替。フラグが優先 |
GITHUB_HEAD_REF / GITHUB_REF_NAME |
--branch 省略時の既定値解決に使用 |
GITHUB_SHA |
--commit 省略時の既定値解決に使用 |
GITHUB_REPOSITORY |
--repository 省略時の既定値解決に使用(GitHub Actions が自動設定する owner/repo)。現時点では backend には送信されません |
CI(GitHub Actions)連携例
- name: Upload VisualDiff snapshots
run: node dist/cli.js upload --app web-visual --dir ./test-results/screenshots
env:
NAGIO_VD_BASE_URL: ${{ vars.NAGIO_VD_BASE_URL }}
NAGIO_VD_TOKEN: ${{ secrets.NAGIO_VD_TOKEN }}
NAGIO_VD_TOKEN は GitHub Actions の secrets(vars ではなく secrets)で管理してください(API トークンは漏洩するとプロジェクトへの権限を持つ機密情報です)。
diff の有無で CI を落とすことはありません(人がレビューする前提のワークフローのため)。差分の合否は GitHub Check Run「画面差分」で確認してください。
冪等性について
CreateBuildV1 は呼び出すたびに新しいビルドを作成します(backend 側でビルドの再利用はしません)。そのため、同じ --dir に対して CLI を複数回実行すると、その都度新しいビルドが作られます。
「未送信分だけ再送する」冪等性は、あくまで「同一ビルドに対して ReserveSnapshotsV1 を再度呼び、既にアップロード済み(同一ハッシュ)のスナップショットは uploadRequired: false になり送信をスキップする」という粒度のものです。
同一ビルドへの再送(再実行時に新規ビルドを作らず、指定した既存ビルドに追記する)は Phase 1 のスコープ外です。 必要になった場合は --build-id のようなオプションを別途追加してください。
status サブコマンド
未実装です。 現状は upload サブコマンドのみサポートしています。
エクスポート
CLI 以外に、ライブラリとしても利用できます。
import {
VisualDiffClient,
VisualDiffApiError,
isUnauthenticated,
sha256File,
parsePngSize,
readPngSize,
scanPngFiles,
mapWithConcurrencyLimit,
waitForVisualStability,
} from "@boost-jp/nagio-visual-diff-uploader";
VisualDiffClient:VisualDiffService(connect-go)を叩く fetch ベースの薄いクライアント(createBuild/reserveSnapshots/completeBuild/uploadSnapshot)sha256File(path): ファイルを sha256 でハッシュ化(ストリーム読み込み)parsePngSize(buffer)/readPngSize(path): PNG の width/height を追加 npm 依存なしで取得(image-size等は未使用。PNG シグネチャ + IHDR チャンクを直接パース)scanPngFiles(dir): ディレクトリを再帰走査して*.pngを列挙し、--dirからの相対パス(拡張子なし・区切りは/)をtestNameとして決定するmapWithConcurrencyLimit(items, limit, fn): 簡易な並行数制限ユーティリティwaitForVisualStability(page, options?): 画面差分の撮影前にフォント読み込み・画像読み込みの完了を待ち、短い静止時間を置く簡易ヘルパ(docs/visual-diff-setup.md§2.5)。@playwright/testを直接依存に持たないよう、evaluate/waitForTimeoutだけを持つ最小限の構造型(StabilityPage)を受け取ります。Playwright のPageはそのまま渡せます。import { test, expect } from "@playwright/test"; import { waitForVisualStability } from "@boost-jp/nagio-visual-diff-uploader"; test("商品一覧が正しく表示される", async ({ page }) => { await page.goto("/products"); await waitForVisualStability(page); await expect(page).toHaveScreenshot("products-list.png"); });