Visual Regression TestingPlaywrightUI 測試自動化測試前端工程師前端教學

Visual Regression Testing 教學:Playwright 視覺回歸測試指南

UI 被改壞了?用 Playwright 視覺回歸測試自動捕捉 UI 變更。含配置、截圖比對、CI 整合教學,守護你的介面品質。

· 5 分鐘閱讀

改了一行 CSS,卻不知道某個三個月前寫的頁面按鈕樣式被影響了?手動檢視所有頁面不現實——Visual Regression Testing(視覺回歸測試)用截圖比對,在 UI 變更發生的路徑上自動捕捉,讓回歸錯誤無所遁形。


前言:為什麼需要視覺測試?

功能測試告訴你「程式有沒有壞」,但沒有人告訴你「看起來對不對」。

視覺回歸測試(Visual Regression Testing)的核心做法:

  1. main 分支建立「基準截圖」(baseline)
  2. 每一次 PR 或改動產生「新截圖」
  3. 自動比對兩者,標記差異

差異不等於錯誤——有時候預期的 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 渲染差異而略有不同。建議:

  1. 基準截圖存在 Git 主分支:只有 maintainer 才能更新 __snapshots__
  2. PR 中的失敗截圖自動上傳 artifact
- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: visual-diff-${{ github.run_id }}
    path: test-results/
  1. 使用相同 OS 的 CI runner:用 ubuntu-latest 而非 macos-latest,減少系統字體差異。

常見問題

Q:基準截圖要放在哪個分支?

A:放在 mainmaster 分支。PR 會從 main 取出基準截圖比對,失敗時會自動產生 diff 圖供 reviewer 看。

Q:截圖比對失敗了怎麼辦?

A:打開 Playwright 自動產生的 .diff.png(差異圖)和 .actual.png(實際截圖)三個檔案比對:

  • 如果是預期的 UI 變更(如新的按鈕顏色),更新截圖:npx playwright test --update-snapshots
  • 如果是意外的回歸錯誤(如 CSS 破壞了某個元件),先修 bug,再重新跑測試

Q:跨瀏覽器的截圖要怎麼管理?

A:在 toHaveScreenshotname 參數區分瀏覽器:

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 更強的現代選擇》