技能:視覺比對 (Visual Diff)
以視覺方式比較兩個 URL — 基準(例如正式環境)與當前(例如本地開發伺服器或 Storybook 故事)— 找出像素級別的差異並引導修正。
使用時機
- 在 Storybook 或 Astro 站台中複製現有頁面或區塊
- 驗證元件與參考設計的視覺準確性
- 在樣式變更後,檢查不同響應式斷點的忠實度
- 反覆調整 CSS/HTML 直到元件與參考相符
不應使用的時機
- 僅有內容變更(文字更新、文案修改),佈局未變
- 比較 Figma 設計稿 — 請改用 Figma MCP 技能
- 效能或無障礙稽核 — 請改用 Lighthouse 或 a11y addon
前置條件
開始前,請確認:
- Playwright MCP 已於
.vscode/mcp.json中設定,並帶有--caps=vision參數 - pixelmatch 和 pngjs 已安裝:檢查
package.json的 devDependencies - 基準 URL 可存取(外部網站或正在執行的伺服器)
- 當前 URL 可存取(開發伺服器在
:4321,Storybook 在:6006等) - 工作目錄根目錄下存在
temp/資料夾(已加入 .gitignore)
如果開發伺服器或 Storybook 尚未執行,請使用 VS Code 工作啟動:
- 開發伺服器:使用 Dev Server 工作
- Storybook:使用 Storybook 工作
工作流程
步驟 1:接收輸入
agent 需要以下資訊:
| 輸入 | 必要 | 範例 |
|---|---|---|
| 基準 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('<選擇器>').scrollIntoView({ behavior: 'instant', block: 'start' }) - 測量元件的渲染高度:
() => document.querySelector('<選擇器>').getBoundingClientRect().height - 將視窗高度調整為該精確高度(寬度保持不變):
mcp_playwright_browser_resize → { width: 1280, height: <測量高度> } - 進行截圖 — 現在只捕捉元件區域。
背景滲色警告: 元件背後顯示的內容在 Storybook(空白)與基準頁面(其他模組)之間會有所不同。常見情況:固定/區域導航列重疊於頂部、模態/覆蓋層背後可見的其他頁面內容,以及部分透明的元件。這些殘留的差異會聚集在元件自身內容之外,且不具可操作性 — 請忽略它們。
如何在 Storybook 故事上裁切:
Storybook 的 iframe.html 僅渲染元件,沒有周邊的 chrome — 無需調整大小。直接導航並截圖:
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: "<基準-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-{斷點}.png 檔名。
重要: 兩端使用相同的視窗尺寸和相同的裁切策略(元件範圍或全頁面),確保比對是在公平的基礎上進行。
步驟 4:執行比對腳本
對每個斷點,在終端機中執行視覺比對腳本:
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:檢視差異圖
啟動一個臨時 HTTP 伺服器來提供 temp/ 目錄,然後讓 Playwright 導航至差異圖:
# 在背景啟動伺服器(每個會話執行一次)
npx -y http-server temp/ -p 8787 --cors -c-1 &
然後讓 Playwright 導航至差異圖:
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-desktop.png" }
mcp_playwright_browser_take_screenshot → {} # 觸發視覺功能 — agent 現在可以直接看到差異圖
Playwright MCP 的 --caps=vision 標記意味著 agent 可以在截圖輸出中直接看到圖像。
注意: Playwright MCP 會封鎖 file:// URL,因此 HTTP 伺服器是必要的。只需啟動一次,即可在整個會話中重複使用。
解讀差異圖:
- 紅色像素 = 基準與當前之間的差異
- 黃色像素 = 反鋸齒差異(通常可忽略)
- 變暗的原圖 = 相符的區域(以 30% 不透明度顯示,提供對比)
- 大塊紅色簇 = 結構性問題(缺少元素、佈局偏移、尺寸錯誤)
- 散落的紅點 = 次像素渲染、字型平滑或反鋸齒(通常可接受)
- 邊緣的紅色帶 = 尺寸不匹配或邊距/間距差異
步驟 6:解讀與行動
結合量化的 JSON 數據與視覺化差異檢查:
| diffPercent | 解讀 | 行動 |
|---|---|---|
| < 1% | 視覺相同 | 僅有微小的次像素差異。無需操作。 |
| 1–5% | 接近相符 | 檢查差異圖中的間距、字重或邊框差異。可能需要微調 CSS。 |
| 5–15% | 明顯差異 | 佈局偏移、顏色不匹配或缺少元素。檢查差異圖中的紅色簇,找出需要修改的區塊。 |
| > 15% | 顯著差距 | 主要的結構性或樣式差異。優先處理最大的紅色區域 — 這些表示最大的佈局不一致。 |
解讀差異圖時,請注意:
- 紅色簇在頁面的哪個位置?(頂部、中間、底部、左側、右側)
- 它們是否與特定元素相關?(導航、主視覺、卡片、頁尾)
- 這些簇是否暗示間距問題、顏色差異或缺少內容?
步驟 7:修正與重新比對
- 根據差異分析進行 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 chrome):
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. 執行比對(在終端機中)
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. 啟動臨時伺服器(每個會話一次,在背景終端機中)
npx -y http-server temp/ -p 8787 --cors -c-1 &
# 5. 透過 Playwright 檢視差異圖
mcp_playwright_browser_navigate → { url: "http://localhost:8787/vdiff-diff-hero-desktop.png" }
mcp_playwright_browser_take_screenshot → {} # 觸發視覺功能
# 6. 解讀:讀取 JSON 輸出 + 視覺檢查差異
# 7. 修正樣式,重新對當前頁面截圖,重新比對
清理
所有輸出檔案都會被放到 temp/,該目錄已加入 .gitignore。會話結束後清理:
rm -f temp/vdiff-*.png