Communitygithub.com

ビジュアルディフ

Playwright のスクリーンショットを複数のブレークポイントで取得し、ピクセルレベルの画像差分を実行して、スタイルと HTML の修正をガイドする結果をレポートすることで、ベースライン URL と開発環境または Storybook の URL を比較します。既存のページやコンポーネントを複製する際、視覚的な正確性を検証する際、またはレスポンシブな忠実度を確認する際に使用します。

ビジュアルディフ とは?

Visual Diff スキルは、ベースライン URL(例:本番サイト)と現在の URL(例:ローカル開発サーバーや Storybook のストーリー)を視覚的に比較し、ピクセルレベルの違いを検出して修正をガイドする AI エージェントスキルです。主に Cursor および Codex をサポートし、Playwright MCP と pixelmatch/pngjs ライブラリを利用して動作します。エージェントは指定された URL にアクセスし、モバイル、タブレット、デスクトップの各ブレークポイントでスクリーンショットを取得し、専用の diff スクリプトで差分を計算します。結果は JSON 形式の定量的データ(差分ピクセル数、割合、寸法不一致の有無)と、赤色で差分を強調表示した視覚的な diff 画像の両方で提供され、エージェントはこれらを解釈して具体的な修正箇所を特定できます。コンポーネント単位の比較ではバウンディングボックスにクロップして不要なページ要素の影響を排除し、Storybook の iframe URL を利用することでクリーンなスクリーンショットを取得するなど、実用的なテクニックも多数含まれています。このスキルは、既存ページの複製、コンポーネントの視覚的精度検証、レスポンシブデザインの確認、CSS/HTML の反復調整などに有用で、差分率に基づく解釈ガイドラインと段階的な修正・再差分のワークフローを提供します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/bitovi/ai-enablement-prompts/tree/HEAD/plugins/playwright/skills/visual-diff

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

スキル: Visual Diff(ビジュアルディフ)

2 つの URL(ベースライン(例:本番サイト)とカレント(例:ローカル開発サーバーまたは Storybook ストーリー))を視覚的に比較し、ピクセルレベルの違いを検出して修正をガイドします。


使用するタイミング

  • Storybook または Astro サイトで既存のページやセクションを複製する場合
  • コンポーネントの視覚的な正確性をリファレンスデザインと照合する場合
  • スタイル変更後に複数のブレークポイントでレスポンシブな忠実度を確認する場合
  • CSS/HTML を反復調整してコンポーネントがリファレンスと一致するまで確認する場合

使用しないタイミング

  • テキストのみの変更(レイアウトが変わらないコピー編集など)
  • Figma からのデザイン比較 — 代わりに Figma MCP スキルを使用してください
  • パフォーマンスまたはアクセシビリティ監査 — 代わりに Lighthouse または a11y アドオンを使用してください

前提条件

開始前に以下を確認してください:

  1. Playwright MCP が .vscode/mcp.json で --caps=vision 付きで設定されていること
  2. pixelmatch と pngjs がインストールされていること:package.json の devDependencies を確認
  3. ベースライン URL にアクセス可能であること(外部サイト、または実行中のサーバー)
  4. カレント URL にアクセス可能であること(:4321 の開発サーバー、:6006 の Storybook など)
  5. ワークスペースルートに temp/ ディレクトリが存在すること(gitignore されています)

開発サーバーまたは Storybook が実行されていない場合は、VS Code タスクを使用して起動します:

  • 開発サーバー:Dev Server タスクを使用
  • Storybook:Storybook タスクを使用

ワークフロー

ステップ 1: 入力を受け取る

エージェントは以下を必要とします:

入力必須例
ベースライン URLはいhttps://www.bitovi.com/
カレント URLはいhttp://localhost:4321/ または http://localhost:6006/?path=/story/card--default
CSS セレクタいいえ#hero-section または .card-grid(要素スコープの比較用)
ブレークポイントいいえデフォルトはモバイル (375×667)、タブレット (768×1024)、デスクトップ (1280×720)

ステップ 2: ベースラインのスクリーンショットを撮る

コンポーネントスコープの比較(単一コンポーネントの比較時に推奨)

特定のコンポーネントを比較する場合 — 特に Storybook ストーリーがそのコンポーネントだけをレンダリングする一方で、ベースラインページがその周りにフルページを持つ場合 — コンポーネントのバウンディングボックスにクロップして、ナビゲーションバー、固定オーバーレイ、その他のモジュールなど、無関係なページセクションによる差分の汚染を防ぎます。

ベースラインページでクロップする方法:

  1. ベースライン URL に移動する
  2. ビューポートを { width: 1280, height: 720 } にリサイズする
  3. ターゲット要素を表示領域内にスクロールする:
    () => document.querySelector('<selector>').scrollIntoView({ behavior: 'instant', block: 'start' })
    
  4. コンポーネントのレンダリング高さを測定する:
    () => document.querySelector('<selector>').getBoundingClientRect().height
    
  5. ビューポートの高さをその正確な高さにリサイズします(幅は一定に保ちます):
    mcp_playwright_browser_resize → { width: 1280, height: <測定した高さ> }
    
  6. スクリーンショットを撮ります — これでコンポーネント領域のみがキャプチャされます。

背景の色のにじみに関する警告: コンポーネントの背後に見えるものは、Storybook(空白)とベースラインページ(他のモジュール)で異なります。よくあるケース:固定/ローカルナビゲーションバーが上部に重なる、モーダル/オーバーレイによって背後に他のページコンテンツが見える、部分的に透明なコンポーネント。これらの残留差分はコンポーネント自身のコンテンツの外側に集まり、対応の必要はありません — 無視してください。

Storybook ストーリーでクロップする方法:

Storybook の iframe.html は、コンポーネントのみを周囲のクロームなしでレンダリングします — リサイズは不要です。移動してスクリーンショットを撮るだけです:

mcp_playwright_browser_navigate → { url: "http://localhost:6006/iframe.html?id=<story-id>&viewMode=story" }
mcp_playwright_browser_resize → { width: 1280, height: <ベースラインクロップと同じ高さ> }
mcp_playwright_browser_take_screenshot → { filename: "temp/vdiff-current-desktop.png" }

フルページビューポートスクリーンショット(フルページを比較する場合)

1. mcp_playwright_browser_navigate → { url: "<baseline-url>" }
2. mcp_playwright_browser_resize → { width: 1280, height: 720 }
3. mcp_playwright_browser_wait_for → { time: 2 }       # ページの完全レンダリングを待つ
4. mcp_playwright_browser_take_screenshot → {
     type: "png",
     filename: "temp/vdiff-baseline-desktop.png"
   }

ブレークポイントごとに繰り返し:

ブレークポイント幅高さファイル名サフィックス
モバイル375667-mobile
タブレット7681024-tablet
デスクトップ1280720-desktop

ステップ 3: カレントのスクリーンショットを撮る

カレント URL に対してもまったく同じプロセスを繰り返し、temp/vdiff-current-{breakpoint}.png ファイル名を使用します。

重要: 両側で同じビューポート寸法と同じクロップ戦略(コンポーネントスコープまたはフルページ)を使用し、比較が公平になるようにします。

ステップ 4: diff スクリプトを実行する

ブレークポイントごとに、ターミナルで visual-diff スクリプトを実行します:

node scripts/visual-diff.mjs \
  --baseline temp/vdiff-baseline-desktop.png \
  --current temp/vdiff-current-desktop.png \
  --output temp/vdiff-diff-desktop.png

スクリプトは JSON を標準出力に出力します:

{
  "totalPixels": 921600,
  "diffPixels": 4521,
  "diffPercent": 0.49,
  "width": 1280,
  "height": 720,
  "baselineDimensions": { "width": 1280, "height": 720 },
  "currentDimensions": { "width": 1280, "height": 720 },
  "dimensionsMismatch": false,
  "diffImagePath": "/absolute/path/to/temp/vdiff-diff-desktop.png"
}

dimensionsMismatch が true の場合、画像のサイズが異なります。スクリプトは小さい方の画像を透明ピクセルでパディングしてから比較します。これは多くの場合、調査すべきレイアウトの問題を示しています。

ステップ 5: diff 画像を表示する

一時的な HTTP サーバーを起動して temp/ ディレクトリを提供し、Playwright で diff 画像に移動します:

# サーバーをバックグラウンドで起動(セッションごとに1回実行)
npx -y http-server temp/ -p 8787 --cors -c-1 &

次に Playwright で diff 画像に移動します:

mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-desktop.png" }
mcp_playwright_browser_take_screenshot → {}   # vision をトリガー — エージェントが diff を確認できるようになる

Playwright MCP の --caps=vision フラグにより、エージェントはスクリーンショット出力で画像を直接見ることができます。

注: file:// URL は Playwright MCP でブロックされるため、HTTP サーバーが必要です。セッション中は一度起動し、すべての diff 画像に対して再利用します。

diff 画像の読み方:

  • 赤いピクセル = ベースラインとカレントの違い
  • 黄色いピクセル = アンチエイリアスの違い(通常は無視可能)
  • 暗く表示されたオリジナル = 一致している領域(コンテキスト用に 30% の不透明度で表示)
  • 大きな赤いクラスタ = 構造上の問題(要素の欠落、レイアウトのずれ、誤ったサイズ設定)
  • 散在する赤い点 = サブピクセルレンダリング、フォントスムージング、またはアンチエイリアス(通常は許容可能)
  • 端の赤い帯 = 寸法の不一致またはパディング/マージンの違い

ステップ 6: 解釈して行動する

定量的な JSON データと視覚的な diff 検査を組み合わせます:

diffPercent解釈アクション
< 1%視覚的に同一軽微なサブピクセルの違いのみ。アクション不要。
1–5%ほぼ一致間隔、フォントウェイト、または境界線の違いについて diff 画像を検査。小さな CSS 調整が必要な可能性。
5–15%顕著な違いレイアウトのずれ、色の不一致、または要素の欠落。diff 内の赤いクラスタを確認し、どのセクションに作業が必要かを特定。
> 15%大きなギャップ主要な構造的またはスタイル上の違い。まず最大の赤い領域に焦点を当て — これらは最も大きなレイアウトの不一致を示しています。

diff 画像を解釈する際の注意点:

  • 赤いクラスタはページのどこにあるか?(上部、中央、下部、左、右)
  • それらは特定の要素に関連しているか?(ナビゲーション、ヒーロー、カード、フッター)
  • クラスタは間隔の問題、色の違い、またはコンテンツの欠落を示唆しているか?

ステップ 7: 修正して再差分する

  1. diff 分析に基づいて CSS/HTML を修正する
  2. ステップ 3–6 を繰り返す(カレント URL のみ再スクリーンショット)
  3. diffPercent が許容レベルに達するまで続ける

一般的な目標:

  • 完全な複製:< 2%
  • 合理的な一致:< 5%
  • 構造的な一致(異なるコンテンツ):< 15%

フルページスクリーンショット

デフォルトでは、スクリーンショットは表示可能なビューポートのみをキャプチャします。長いページの場合は、fullPage オプションを使用します:

mcp_playwright_browser_take_screenshot → {
  type: "png",
  fullPage: true,
  filename: "temp/vdiff-baseline-desktop-full.png"
}

警告: フルページスクリーンショットは大きな画像を生成し、差分計算が遅くなります。ページレイアウト全体を比較する場合にのみ使用してください。


Storybook 固有のヒント

Storybook ストーリーのスクリーンショットを撮る場合:

  1. iframe URL を使用してクリーンなスクリーンショットを取得(Storybook のクロームなし):

    http://localhost:6006/iframe.html?id=components-card--default&viewMode=story
    
  2. レンダリングを待つ — Storybook ストーリーはハイドレーションに時間がかかる場合があります:

    mcp_playwright_browser_wait_for → { time: 3 }
    
  3. 要素スクリーンショットは、ストーリーコンテンツを Storybook のパディングから分離するのに適しています。


閾値の調整

--threshold フラグ(0 から 1)は、pixelmatch の感度を制御します:

値感度使用例
0.05非常に厳格正確なピクセルマッチング、すべてを検出
0.1デフォルト良いバランス、ほとんどのアンチエイリアスを無視
0.2寛容システム間のフォントレンダリングの違いを許容
0.3非常に寛容主要な色/レイアウトの違いのみを検出
node scripts/visual-diff.mjs \
  --baseline temp/vdiff-baseline-desktop.png \
  --current temp/vdiff-current-desktop.png \
  --output temp/vdiff-diff-desktop.png \
  --threshold 0.2

例:ヒーローセクションの比較

# 1. ベースラインのスクリーンショット(本番サイトのヒーロー)
mcp_playwright_browser_navigate → { url: "https://www.bitovi.com/" }
mcp_playwright_browser_resize → { width: 1280, height: 720 }
mcp_playwright_browser_wait_for → { time: 2 }
mcp_playwright_browser_snapshot → {}
# ヒーローセクションの ref を見つける(例:ref="hero-[1]")
mcp_playwright_browser_take_screenshot → {
  type: "png",
  element: "hero section",
  ref: "hero-[1]",
  filename: "temp/vdiff-baseline-hero-desktop.png"
}

# 2. カレントのスクリーンショット(Storybook ストーリー)
mcp_playwright_browser_navigate → { url: "http://localhost:6006/iframe.html?id=components-hero--default" }
mcp_playwright_browser_resize → { width: 1280, height: 720 }
mcp_playwright_browser_wait_for → { time: 3 }
mcp_playwright_browser_take_screenshot → {
  type: "png",
  filename: "temp/vdiff-current-hero-desktop.png"
}

# 3. diff を実行(ターミナル)
node scripts/visual-diff.mjs \
  --baseline temp/vdiff-baseline-hero-desktop.png \
  --current temp/vdiff-current-hero-desktop.png \
  --output temp/vdiff-diff-hero-desktop.png

# 4. 一時サーバーを起動(セッションごとに1回、バックグラウンドターミナル)
npx -y http-server temp/ -p 8787 --cors -c-1 &

# 5. Playwright で diff 画像を表示
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-hero-desktop.png" }
mcp_playwright_browser_take_screenshot → {}   # vision をトリガー

# 6. 解釈:JSON 出力を読み、視覚的に diff を検査
# 7. スタイルを修正し、カレントを再スクリーンショット、再差分

クリーンアップ

すべての出力ファイルは temp/ に保存され、gitignore されています。セッション後にクリーンアップするには:

rm -f temp/vdiff-*.png

関連スキル