Communitygithub.com

視覺比對 (Visual Diff)

透過 Playwright 在多個斷點截圖、執行像素級影像比對,並報告結果,協助比對基準 URL 與開發或 Storybook URL 的樣式與 HTML 修正。

視覺比對 (Visual Diff) 是什麼?

這個技能用於進行兩個 URL 之間的視覺比對。它會使用 Playwright 在行動、平板和桌面等不同斷點擷取螢幕截圖,並透過 pixelmatch 和 pngjs 進行像素級的影像比對。最終產出量化的差異數據和視覺化的差異圖,幫助開發者快速定位樣式與佈局的偏差。 此技能支援元件級或全頁面的比對,特別適合在將現有頁面複製到 Storybook 或 Astro 站台時,確保視覺準確性。它會自動處理元件裁切,並提供詳細的解讀指南,協助判斷差異是否來自於反鋸齒、次像素渲染等可忽略因素,或是需要修正的結構性問題。 技能運行要求 Playwright MCP 配置正確(需啟用 `--caps=vision`),且專案中已安裝 `pixelmatch` 和 `pngjs` 套件。適用於 VS Code 環境,並可結合既有的開發伺服器或 Storybook 任務使用。 整體工作流程包含截圖、比對、產出差異影像、解讀結果,以及迭代修正。它也提供了閾值調整功能,讓使用者能控制比對的嚴格程度,並針對不同的差異百分比範圍給出對應的處理建議。

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

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

技能:視覺比對 (Visual Diff)

以視覺方式比較兩個 URL — 基準(例如正式環境)與當前(例如本地開發伺服器或 Storybook 故事)— 找出像素級別的差異並引導修正。


使用時機

  • 在 Storybook 或 Astro 站台中複製現有頁面或區塊
  • 驗證元件與參考設計的視覺準確性
  • 在樣式變更後,檢查不同響應式斷點的忠實度
  • 反覆調整 CSS/HTML 直到元件與參考相符

不應使用的時機

  • 僅有內容變更(文字更新、文案修改),佈局未變
  • 比較 Figma 設計稿 — 請改用 Figma MCP 技能
  • 效能或無障礙稽核 — 請改用 Lighthouse 或 a11y addon

前置條件

開始前,請確認:

  1. Playwright MCP 已於 .vscode/mcp.json 中設定,並帶有 --caps=vision 參數
  2. pixelmatch 和 pngjs 已安裝:檢查 package.json 的 devDependencies
  3. 基準 URL 可存取(外部網站或正在執行的伺服器)
  4. 當前 URL 可存取(開發伺服器在 :4321,Storybook 在 :6006 等)
  5. 工作目錄根目錄下存在 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 故事只渲染該元件,但基準頁面有完整頁面環繞時 — 請裁切至元件的邊界框,以免差異被不相關的頁面區塊(導航列、固定覆蓋層、上方/下方的其他模組)干擾。

如何在基準頁面上裁切:

  1. 導航至基準 URL
  2. 將視窗尺寸調整為 { width: 1280, height: 720 }
  3. 將目標元素捲動至視野中:
    () => document.querySelector('<選擇器>').scrollIntoView({ behavior: 'instant', block: 'start' })
    
  4. 測量元件的渲染高度:
    () => document.querySelector('<選擇器>').getBoundingClientRect().height
    
  5. 將視窗高度調整為該精確高度(寬度保持不變):
    mcp_playwright_browser_resize → { width: 1280, height: <測量高度> }
    
  6. 進行截圖 — 現在只捕捉元件區域。

背景滲色警告: 元件背後顯示的內容在 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"
   }

對每個斷點重複:

斷點寬度高度檔名後綴
行動375667-mobile
平板7681024-tablet
桌面1280720-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:修正與重新比對

  1. 根據差異分析進行 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 chrome):

    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. 執行比對(在終端機中)
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

相關技能