📋 目錄
改了一行 CSS,卻不知道某個三個月前寫的頁面按鈕樣式被影響了?手動檢視所有頁面不現實——Visual Regression Testing(視覺回歸測試)用截圖比對,在 UI 變更發生的路徑上自動捕捉,讓回歸錯誤無所遁形。
前言:為什麼需要視覺測試?
功能測試告訴你「程式有沒有壞」,但沒有人告訴你「看起來對不對」。
視覺回歸測試(Visual Regression Testing)的核心做法:
- 在
main分支建立「基準截圖」(baseline) - 每一次 PR 或改動產生「新截圖」
- 自動比對兩者,標記差異
差異不等於錯誤——有時候預期的 UI 更新(如新的品牌色彩)本來就會產生視覺差異。視覺測試的重點是讓你知道改動的視覺影響範圍,而不是一刀切的「紅的就是失敗」。
設定:以 Playwright 為基礎
安裝
npm install -D @playwright/test
npm install -D pixelmatch canvas # 用於像素比對
Playwright 的內建視覺比對
Playwright 原生支援 expect(page).toHaveScreenshot(),不需要額外工具:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
use: {
baseURL: 'http://localhost:3000',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
第一個視覺測試
設定測試檔案
// tests/visual/homepage.spec.ts
import { test, expect } from '@playwright/test';
test.describe('首頁視覺回歸', () => {
test('首頁應該與基準截圖一致', async ({ page }) => {
await page.goto('/');
// toHaveScreenshot 會自動比對截圖
// 第一次執行時,這個截圖會被當成基準(存入 __snapshots__ 目錄)
await expect(page).toHaveScreenshot('homepage.png', {
// 允許某些區域有微幅差異(如動態內容)
mask: [
// 遮蔽動態內容區域
page.locator('.dynamic-banner'), // 輪播 banner
page.locator('#live-clock'), // 即時時鐘
],
// 截圖時忽略抗鋸齒產生的 1px 差異
pixelThreshold: 0.1, // 允許 10% 的像素差異
threshold: 0.2, // 允許 20% 的整體色彩差異
});
});
test('登入頁應該與基準一致', async ({ page }) => {
await page.goto('/login');
await expect(page).toHaveScreenshot('login-page.png');
});
});
建立基準截圖
第一次執行測試時,Playwright 會發現沒有基準截圖,自動建立。之後每次執行都會和基準比對。
# 第一次建立基準截圖
npx playwright test --update-snapshots
# 只更新特定測試的截圖
npx playwright test tests/visual/homepage.spec.ts --update-snapshots
基準截圖會存在 __snapshots__ 目錄:
tests/
├── visual/
│ └── __snapshots__/
│ ├── homepage.png # Chromium 基準截圖
│ └── login-page.png
CI 中的視覺測試流程
# .github/workflows/visual-regression.yml
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
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Install dependencies and start app
run: |
npm run build
npm start &
- name: Wait for app to be ready
run: npx wait-on http://localhost:3000 --timeout 60000
- name: Run visual tests
run: npx playwright test --reporter=list
- uses: actions/upload-artifact@v4
if: failure()
with:
name: visual-test-results
path: test-results/
retention-days: 7
處理動態內容
電子商務網站的首頁通常有輪播 banner、推薦商品、價格計時器——這些每次刷新都會變動,不應造成測試失敗。
方法一:Mask(遮蔽)
await expect(page).toHaveScreenshot('product-page.png', {
mask: [
// 遮蔽所有動態推薦商品卡片
page.locator('.recommendation-card'),
// 遮蔽價格計時器
page.locator('.countdown-timer'),
],
});
方法二:Mock 動態 API
test('商品頁在 mock 資料下應該一致', async ({ page }) => {
// Mock 推薦 API 回傳固定資料
await page.route('/api/recommendations', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: '固定商品 A', price: 100 },
{ id: 2, name: '固定商品 B', price: 200 },
]),
});
});
await page.goto('/products/1');
await expect(page).toHaveScreenshot('product-page-mocked.png');
});
方法三:使用無頭模式減少差異
test.use({
// 固定視窗大小
viewport: { width: 1280, height: 720 },
// 關閉動畫
launchOptions: {
args: ['--disableanimations'],
},
});
多人協作的截圖管理
團隊中每個人的本機截圖可能因系統字體、GPU 渲染差異而略有不同。建議:
- 基準截圖存在 Git 主分支:只有 maintainer 才能更新
__snapshots__ - PR 中的失敗截圖自動上傳 artifact:
- uses: actions/upload-artifact@v4
if: failure()
with:
name: visual-diff-${{ github.run_id }}
path: test-results/
- 使用相同 OS 的 CI runner:用
ubuntu-latest而非macos-latest,減少系統字體差異。
常見問題
Q:基準截圖要放在哪個分支?
A:放在 main 或 master 分支。PR 會從 main 取出基準截圖比對,失敗時會自動產生 diff 圖供 reviewer 看。
Q:截圖比對失敗了怎麼辦?
A:打開 Playwright 自動產生的 .diff.png(差異圖)和 .actual.png(實際截圖)三個檔案比對:
- 如果是預期的 UI 變更(如新的按鈕顏色),更新截圖:
npx playwright test --update-snapshots - 如果是意外的回歸錯誤(如 CSS 破壞了某個元件),先修 bug,再重新跑測試
Q:跨瀏覽器的截圖要怎麼管理?
A:在 toHaveScreenshot 加 name 參數區分瀏覽器:
await expect(page).toHaveScreenshot('homepage-chrome.png');
// 或在 config 的 project name 裡區分
Q:太長的頁面(整頁截圖)要怎麼測試?
A:使用 fullPage: true:
await expect(page).toHaveScreenshot('full-homepage.png', {
fullPage: true,
});
總結:視覺測試讓 UI 變更有紀律
視覺回歸測試的核心價值:讓每次 UI 變更都有憑有據,而不是靠「我看過了所以應該沒問題」。
這篇文章涵蓋:
- Playwright 原生視覺比對(
toHaveScreenshot) - 基準截圖的建立與管理
- CI 中的視覺測試流程
- 動態內容的處理方法(mask、mock、viewport 固定)
- 多人協作的截圖管理
下一步:在你的專案選擇一個「穩定的靜態頁面」加上視覺測試,先建立起基準截圖。從登入頁或首頁的固定區塊開始,是最容易看到價值的起點。
想了解 Playwright E2E 測試,可參考 《Playwright E2E 測試——比 Cypress 更強的現代選擇》。