スキル: Visual Diff(ビジュアルディフ)
2 つの URL(ベースライン(例:本番サイト)とカレント(例:ローカル開発サーバーまたは Storybook ストーリー))を視覚的に比較し、ピクセルレベルの違いを検出して修正をガイドします。
使用するタイミング
- Storybook または Astro サイトで既存のページやセクションを複製する場合
- コンポーネントの視覚的な正確性をリファレンスデザインと照合する場合
- スタイル変更後に複数のブレークポイントでレスポンシブな忠実度を確認する場合
- CSS/HTML を反復調整してコンポーネントがリファレンスと一致するまで確認する場合
使用しないタイミング
- テキストのみの変更(レイアウトが変わらないコピー編集など)
- Figma からのデザイン比較 — 代わりに Figma MCP スキルを使用してください
- パフォーマンスまたはアクセシビリティ監査 — 代わりに Lighthouse または a11y アドオンを使用してください
前提条件
開始前に以下を確認してください:
- Playwright MCP が
.vscode/mcp.jsonで--caps=vision付きで設定されていること - pixelmatch と pngjs がインストールされていること:
package.jsonの devDependencies を確認 - ベースライン URL にアクセス可能であること(外部サイト、または実行中のサーバー)
- カレント URL にアクセス可能であること(
:4321の開発サーバー、:6006の Storybook など) - ワークスペースルートに
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 ストーリーがそのコンポーネントだけをレンダリングする一方で、ベースラインページがその周りにフルページを持つ場合 — コンポーネントのバウンディングボックスにクロップして、ナビゲーションバー、固定オーバーレイ、その他のモジュールなど、無関係なページセクションによる差分の汚染を防ぎます。
ベースラインページでクロップする方法:
- ベースライン URL に移動する
- ビューポートを
{ width: 1280, height: 720 }にリサイズする - ターゲット要素を表示領域内にスクロールする:
() => document.querySelector('<selector>').scrollIntoView({ behavior: 'instant', block: 'start' }) - コンポーネントのレンダリング高さを測定する:
() => document.querySelector('<selector>').getBoundingClientRect().height - ビューポートの高さをその正確な高さにリサイズします(幅は一定に保ちます):
mcp_playwright_browser_resize → { width: 1280, height: <測定した高さ> } - スクリーンショットを撮ります — これでコンポーネント領域のみがキャプチャされます。
背景の色のにじみに関する警告: コンポーネントの背後に見えるものは、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"
}
ブレークポイントごとに繰り返し:
| ブレークポイント | 幅 | 高さ | ファイル名サフィックス |
|---|---|---|---|
| モバイル | 375 | 667 | -mobile |
| タブレット | 768 | 1024 | -tablet |
| デスクトップ | 1280 | 720 | -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: 修正して再差分する
- diff 分析に基づいて CSS/HTML を修正する
- ステップ 3–6 を繰り返す(カレント URL のみ再スクリーンショット)
diffPercentが許容レベルに達するまで続ける
一般的な目標:
- 完全な複製:< 2%
- 合理的な一致:< 5%
- 構造的な一致(異なるコンテンツ):< 15%
フルページスクリーンショット
デフォルトでは、スクリーンショットは表示可能なビューポートのみをキャプチャします。長いページの場合は、fullPage オプションを使用します:
mcp_playwright_browser_take_screenshot → {
type: "png",
fullPage: true,
filename: "temp/vdiff-baseline-desktop-full.png"
}
警告: フルページスクリーンショットは大きな画像を生成し、差分計算が遅くなります。ページレイアウト全体を比較する場合にのみ使用してください。
Storybook 固有のヒント
Storybook ストーリーのスクリーンショットを撮る場合:
-
iframe URL を使用してクリーンなスクリーンショットを取得(Storybook のクロームなし):
http://localhost:6006/iframe.html?id=components-card--default&viewMode=story -
レンダリングを待つ — Storybook ストーリーはハイドレーションに時間がかかる場合があります:
mcp_playwright_browser_wait_for → { time: 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