npm.io
0.5.0 • Published 8h agoCLI

@boost-jp/nagio-visual-diff-uploader

Licence
MIT
Version
0.5.0
Deps
0
Size
270 kB
Vulns
0
Weekly
0

@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/playwrightexpectNAGIO_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 / 設定ファイルの failOnDiff0.4.0 で廃止しました。指定するとエラー終了します。差分の合否は GitHub Check Run「画面差分」で判定してください。

動作フロー
  1. --dir を再帰走査して *.png を列挙する(0 件ならエラーで終了)
  2. 各ファイルの sha256・width/height(PNG ヘッダから直接パース)・byteSize を計算する
  3. VisualDiffService.CreateBuildV1 でビルドを作成する
  4. VisualDiffService.ReserveSnapshotsV1 で全スナップショットを予約する(uploadRequired: false の分は既にストレージに同一ハッシュの画像がある = アップロード不要)
  5. uploadRequired: true のエントリだけ、対応するファイルを POST {baseUrl}/visual-diff/upload に生バイナリで送る(--concurrency で並行数を制限。1 件失敗しても他は続行し、最後に失敗件数をまとめて報告。全件失敗した場合のみ exit code 1)
  6. VisualDiffService.CompleteBuildV1 を呼ぶ。backend は差分計算(pixelmatch)を待たず、ビルドを comparing(計算中)にして即座に応答する(画像差分計算の非同期化。0.4.0)。CLI はこれを待たず終了する
  7. サマリを標準出力に出す
[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");
    });