📋 目錄
這篇會帶你把 Playwright 視覺回歸測試落地到真實專案,從 baseline 策略到 CI/PR 流程一次建好。
前言
很多團隊其實已經有 E2E 測試,但 UI 回歸還是常常在上線後才被發現。原因很簡單:functional test 通常只能驗證「流程走得通」,卻不會告訴你「畫面是不是壞了」。
例如 checkout 流程全部綠燈,不代表按鈕樣式、版面間距、字體 fallback、或某個 locale 下的排版沒有跑掉。這些問題常常不是 JavaScript error,而是視覺層的小偏移,卻直接影響轉換率與使用者信任。
Playwright 的 expect(page).toHaveScreenshot() 提供一個很實際的 guardrail:用 baseline 比對把變動顯性化,讓 reviewer 在 PR 階段就能看到「這次改動到底影響了哪些畫面」。
為什麼單靠 Functional Test 不夠
功能測試擅長驗證「事件有沒有發生」,視覺回歸測試擅長驗證「結果看起來對不對」。兩者不是替代關係,而是互補關係。
常見缺口:
- CSS token 被改動,流程還能點,但按鈕對比度或 spacing 已經不符合設計規範。
- 第三方元件升版後 DOM 結構改變,導致某些 breakpoint 下 layout 崩壞。
- 動畫、時間、隨機資料導致頁面不穩定,肉眼很難在 PR 中完整檢查。
所以在測試策略上,functional test 負責「能不能用」,visual test 負責「看起來是不是還對」。
toHaveScreenshot() 的正確心智模型
先掌握三個重點:
toHaveScreenshot()是 Playwright Test 內建能力,不需要額外安裝pixelmatch或canvas。- 第一次跑測試時會建立 baseline,後續執行才做比對。
- Playwright 會先等到兩次連續截圖一致,再拿去和 baseline 比,降低瞬間閃動造成的誤報。
以下是 page 等級的基礎範例:
import { test, expect } from '@playwright/test';
test('product list visual regression', async ({ page }) => {
await page.goto('/products');
await expect(page).toHaveScreenshot('products-page.png', {
fullPage: true,
maxDiffPixels: 120, // 允許最多 120 個像素差異
maxDiffPixelRatio: 0.001, // 允許 0.1% 像素差異
threshold: 0.2, // YIQ 色差容忍度,0 越嚴格,1 越寬鬆
});
});
如果你只關心某個關鍵元件,建議用 locator screenshot,噪音更低:
import { test, expect } from '@playwright/test';
test('checkout summary card visual regression', async ({ page }) => {
await page.goto('/checkout');
const summaryCard = page.getByTestId('checkout-summary');
await expect(summaryCard).toHaveScreenshot('checkout-summary.png', {
maxDiffPixels: 40,
threshold: 0.2,
});
});
Baseline 與 Snapshot 管理策略
預設情況下,example.spec.ts 的快照會放在 example.spec.ts-snapshots 目錄。請把這些 baseline 納入版本控制,因為它們就是你團隊共同認可的 UI 參考線。
更新 baseline 時,請只在「預期的 UI 變更」下執行:
# 全量更新(通常在明確 UI 改版時才用)
npx playwright test --update-snapshots
# 只更新單一 spec,降低誤更新風險
npx playwright test tests/visual/checkout.spec.ts --update-snapshots
若要客製儲存路徑,再使用 snapshotPathTemplate 或 expect.toHaveScreenshot.pathTemplate:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
snapshotPathTemplate:
'{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}',
expect: {
toHaveScreenshot: {
maxDiffPixels: 80,
threshold: 0.2,
},
},
});
動態內容穩定化:先降噪,再談門檻
視覺測試最常失敗,不是因為 UI 真的壞,而是測試環境太「會抖」。建議先做 deterministic 控制,再調整 diff 門檻。
1. 用 stylePath 去除易變動區塊
/* tests/visual/screenshot.css */
[data-test-clock],
[data-test-rotating-banner] {
visibility: hidden !important;
}
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toHaveScreenshot: {
stylePath: './tests/visual/screenshot.css',
animations: 'disabled', // 官方支援做法,停用動畫干擾
},
},
});
2. 對特定元素使用 mask
await expect(page).toHaveScreenshot('home.png', {
mask: [
page.getByTestId('ad-slot'),
page.getByTestId('live-visitors-counter'),
],
});
3. Mock 動態 API,固定資料來源
import { test, expect } from '@playwright/test';
test('dashboard visual with stable fixture', async ({ page }) => {
await page.route('**/api/dashboard/summary', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
activeUsers: 128,
revenue: 52300,
trend: 'up',
}),
});
});
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png', {
fullPage: true,
});
});
CI / PR Review Flow:把視覺差異變成可審查資產
推薦流程是「PR 跑 visual tests,失敗就上傳 diff artifacts,讓 reviewer 直接看圖」。
name: visual-regression
on:
pull_request:
jobs:
visual-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps
- name: Run visual tests
run: npx playwright test tests/visual --reporter=html,line
- name: Upload Playwright artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-visual-artifacts
path: |
test-results/
playwright-report/
retention-days: 7
PR 審查時的決策原則可以固定成三步:
- 差異是預期改動嗎?是的話更新 baseline。
- 差異是非預期回歸嗎?先修再重跑。
- 無法判定時,要求設計或 feature owner 一起看 diff 圖。
何時用 Playwright,何時用 Percy / Chromatic
Playwright 適合:
- 你已經有 Playwright E2E 基礎,想快速補上 visual guardrail。
- 重點是核心流程頁(checkout、signup、dashboard)而不是全站像素級覆蓋。
- 團隊可接受以工程規則(mask/mock/stylePath)維持穩定度。
Percy / Chromatic 這類專門服務較適合:
- 你需要設計系統層級的大規模視覺審查與歷史追蹤。
- 希望用雲端工作流降低 baseline 維護成本。
- 需要更成熟的跨分支 UI review governance。
很多團隊會採混合策略:產品主流程用 Playwright,設計系統元件庫交給 Percy/Chromatic。
常見問題 / 注意事項
Q1:視覺測試是不是門檻設高一點就好?
不是。maxDiffPixels/maxDiffPixelRatio 設太寬鬆會讓真正回歸漏掉。優先順序應該是先穩定資料與畫面,再調門檻。
Q2:可以跨 OS 共用同一套 baseline 嗎? 通常不建議。字型與渲染差異會讓誤報增加。實務上多半固定在同一種 CI runner(例如 Ubuntu)產生與比對 baseline。
Q3:snapshot 目錄可不可以不 commit? 不建議。baseline 是視覺規格的一部分,不進版控就很難在 PR 中追蹤何時、為何改變。
總結
Playwright 的視覺回歸測試重點,不是把每個頁面都截圖,而是把高風險 UI 路徑做成可審查、可回溯、可自動化的 guardrail。
先建立正確心智模型,再落地三件事:
- 明確 baseline 管理規範(何時更新、誰可更新)
- 穩定化策略(mock、mask、stylePath、固定環境)
- CI/PR 的 diff 審查流程
這三步做完,你的團隊才會真正把 Visual Regression Testing 用在「減少上線事故」,而不是增加 CI 噪音。