本頁為機器翻譯,尚未經過人工審閱。以英文版本為準。
快速開始
安裝
此外掛需要 Node.js 20.9 或更新版本。您可以使用慣用的套件管理工具安裝這個函式庫:
# npm
npm install --save-dev @pixsame/cypress-plugin-visual-regression-diff
# yarn
yarn add -D @pixsame/cypress-plugin-visual-regression-diff
# pnpm
pnpm add -D @pixsame/cypress-plugin-visual-regression-diff
接著,您需要匯入這個函式庫:
- 首先,在 support 檔案中(預設位於
cypress/support/index.js):
// typescript / ES6
import '@pixsame/cypress-plugin-visual-regression-diff';
// javascript
require('@pixsame/cypress-plugin-visual-regression-diff');
- 接著,在
cypress.config.js(或cypress.config.ts)中:
// typescript / ES6
import { defineConfig } from 'cypress';
import { initPlugin as initVisualRegressionPlugin } from '@pixsame/cypress-plugin-visual-regression-diff/plugins';
export default defineConfig({
// initPlugin 必須在實際使用它的區段中呼叫:e2e 或 component
e2e: {
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
},
},
component: {
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
},
},
});
這樣就完成了,接下來請到使用方式章節看看如何使用這個函式庫。
使用方式
安裝完成後,只要在測試中這樣寫就能使用:
cy.get('.an-element-of-your-choice').matchImage();
或者,若您想擷取整份文件的截圖:
cy.matchImage();
matchImage 指令會擷取一張截圖,並與上一次執行時的影像進行比對。一旦發生視覺回歸,測試就會失敗,並且會出現「See comparison」按鈕,讓您查看問題的根源。
範例
安裝上還是遇到問題嗎?請參考這個 repo 的 examples 目錄,看看如何在您專案的 Cypress e2e 或元件測試環境中使用這個外掛。
自動清理未使用的影像
把視覺回歸外掛產生、但已經沒有任何測試在使用的截圖清掉,是很實用的功能。 透過 expose 變數啟用這項功能,即可釋放儲存空間 🚀:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionCleanupUnusedImages=true"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionCleanupUnusedImages=true"
執行清單(CI 整合)
從 1.1 起,每次執行都會寫出一份 JSON 清單(manifest),列出每一次 matchImage 比對:來自哪個測試、是通過、失敗、建立還是更新了基準影像、差異比例,以及基準影像、.actual.png 與 .diff.png 檔案相對於專案的路徑。在這些條目旁邊,它也會記錄這次執行發生的位置(CI 供應商、repository、commit、pull request、run id)與執行所用的環境(瀏覽器、spec、設定檔、外掛選項),因此讀取這個檔案的工具可以回報到正確的 PR、下載正確的 artifact,或重新執行同樣的比對。它是為了在 Cypress 跑完之後才執行的 CI 工具(PR 留言、審閱儀表板、核准機器人)而設計的。
這個格式是以 CTRF 為基礎、與執行器無關的開放標準,文件位於 visual-regression.dev/manifest。npm 套件 visual-regression-manifest 提供 JSON Schema、TypeScript 型別、會驗證的讀取器、能合併整次執行所有清單的合併器,以及給本身不會寫出清單的工具使用的轉換器(Playwright 的 toHaveScreenshot、Vitest、jest-image-snapshot、BackstopJS、CTRF 報告、單純的影像檔案)。請以該套件而非這個外掛為基礎來建構您的工具。
預設情況下,這個檔案會和您其他的 Cypress artifact 放在一起,位於 <screenshotsFolder>/visual-regression-manifest.<testingType>.json(例如 cypress/screenshots/visual-regression-manifest.e2e.json)。請把它和您的快照目錄一起上傳為 CI artifact。
{
"reportFormat": "visual-regression-manifest",
"specVersion": "1.0.0-beta.1",
"createdAt": "2026-09-22T10:00:00.000Z",
"updatedAt": "2026-09-22T10:03:12.481Z",
"projectRoot": "/home/runner/work/app/app",
"platform": {
"osPlatform": "linux",
"arch": "x64",
"osRelease": "6.8.0-1021-azure",
},
"ci": {
"provider": "github",
"repositoryName": "acme/app",
"repositoryUrl": "https://github.com/acme/app",
"commit": "0f1e2d…", // 在 pull_request 事件中:合併 commit
"ref": "refs/pull/42/merge",
"branchName": "feat/new-header",
"pullRequest": {
"number": 42,
"headSha": "9a8b7c…",
"headRef": "feat/new-header",
"baseRef": "main",
},
"event": "pull_request",
"buildId": "1234567890",
"buildName": "CI",
"buildUrl": "https://github.com/acme/app/actions/runs/1234567890/attempts/1",
"runAttempt": "1",
"job": "e2e",
"workspace": "/home/runner/work/app/app",
},
"tool": {
"name": "@pixsame/cypress-plugin-visual-regression-diff",
"version": "1.1.0",
}, // 寫出這個檔案的工具
"options": { "updateImages": "failures-only" }, // 全域的 pluginVisualRegression* 選項,已去除前綴
"runner": {
"name": "cypress",
"version": "16.1.0",
"testingType": "e2e",
"mode": "run",
"configFile": "cypress.config.ts",
"browser": {
"name": "chrome",
"version": "130.0.0.0",
"family": "chromium",
"headless": true,
},
"specs": ["cypress/e2e/home.cy.ts"],
"specPattern": "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}",
"baseUrl": "http://localhost:3000",
"viewport": { "width": 1280, "height": 720 },
},
"summary": {
"entries": 1,
"passed": 0,
"failed": 1,
"missingBaseline": 0,
"created": 0,
"updated": 0,
"approved": 0,
"needsHuman": 1,
},
"entries": [
{
"name": "home page renders_#0",
"test": {
"filePath": "cypress/e2e/home.cy.ts",
"titlePath": ["home page", "renders"],
"retry": 0,
},
"status": "failed",
"comparison": { "diffRatio": 0.0231, "threshold": 0.01 },
"images": {
"baseline": {
"path": "cypress/e2e/__image_snapshots__/home page renders_#0.png",
"width": 1280,
"height": 720,
},
"actual": {
"path": "cypress/e2e/__image_snapshots__/home page renders_#0.actual.png",
"width": 1280,
"height": 720,
},
"diff": {
"path": "cypress/e2e/__image_snapshots__/home page renders_#0.diff.png",
},
},
"baselineWritten": false,
"recordedAt": "2026-09-22T10:03:12.480Z",
"platform": {
"osPlatform": "linux",
"arch": "x64",
"browser": {
"name": "chrome",
"version": "130.0.0.0",
"family": "chromium",
"headless": true,
},
},
"viewport": { "width": 1280, "height": 720 },
"options": {
"imagesPath": "{spec_path}/__image_snapshots__",
"maxDiffThreshold": 0.01,
"diffConfig": {},
"createMissingImages": true,
"updateImages": false,
"forceDeviceScaleFactor": true,
"screenshotConfig": {},
},
"renderer": {
"backend": "native",
"browser": "chrome",
"browserVersion": "130.0.0.0",
},
"hashes": {
"baseline": "3b7e…", // `images` 中所列檔案的 sha256,只有實際存在的檔案才會有
"actual": "9d21…",
"diff": "c0ff…",
},
"message": "2310 pixels (2.31%) differ; the limit is 1%.",
},
],
}
status | 意義 | images.actual.path | images.diff.path |
|---|---|---|---|
passed | 在門檻值範圍內 | null | null |
failed | 超過門檻值,保留 .actual.png 與 .diff.png 供審閱 | 路徑 | 路徑 |
missing-baseline | 沒有基準影像,且 createMissingImages: false | 路徑 | null |
created | 沒有基準影像,.actual.png 成為基準影像 | null | null |
updated | 基準影像已被覆寫(updateImages: true 或 'failures-only') | null | null |
approved | 基準影像已從有頭模式的審閱介面取代(「Replace image」) | null | null |
執行層級的欄位:
| 欄位 | 意義 |
|---|---|
createdAt、updatedAt | 執行開始的時間,以及檔案最後一次被重寫的時間(ISO 8601) |
platform | osPlatform(linux、darwin、win32)、arch、核心的 osRelease,以及在 run 模式下,執行測試的機器可讀性較高的 osVersion |
tool | 寫出這個檔案的工具:這個外掛及其版本 |
ci | 在 GitHub Actions 與 GitLab CI 上由環境變數偵測;在其他 CI 系統上為 { "provider": null }(只有 CI 被設定),不在 CI 上時為 null。只有列出的這些鍵會從環境變數複製 |
options | 來自 expose/env 的每個 pluginVisualRegression* 選項,已去除前綴,值與設定時相同(CLI 傳入的值維持字串) |
runner | 執行測試的工具。mode 為 run 或 open;browser 是啟動的瀏覽器;specs 只在 run 模式下才知道;configFile 與 specs 相對於 projectRoot。Cypress Cloud 的執行還會有 cloud.runUrl、group、tag、parallel |
summary | 各狀態的條目數量;needsHuman 是 failed + missingBaseline,讓消費端不必讀取條目就能做決定 |
給消費端的注意事項:
- 所有路徑都相對於
projectRoot,並使用/作為分隔符號;當imagesPath是絕對路徑時,路徑可能指向專案之外(../…)。projectRoot是 Cypress 專案,不一定是 repository 的根目錄:ci.workspace是 checkout 目錄,所以relative(ci.workspace, projectRoot)就是 repository 內的路徑前綴。 failed與missing-baseline是需要人工處理的條目:把images.actual.path複製到images.baseline.path即可核准它們。baselineWritten表示該張截圖的工作樹是否有變更,與status無關。- 比對發現差異的項目帶有
extra.pixsame.analysis(除非關閉了analysis):一個label(content-change、moved、noise-like或size-change)、一句話的description、發生變化的regions及其邊框、偵測到的shift,以及一個bundleKey,以相同方式變化的項目其值相同。@pixsame/analysis中的bundle()會依它將項目分組。 - 每個條目都帶有自己的
platform與viewport,因此把多份清單串接後,仍能分辨來自不同機器矩陣的條目。條目上的options是matchImage確切的輸入(imagesPath保留未展開的 token);comparison則是結果。 platform是測試執行的位置,renderer是像素的來源。目前每張截圖都是由 Cypress 驅動的瀏覽器拍攝的,所以renderer.backend是native,而renderer.browser會重複platform.browser.name。這個欄位的存在,是為了讓本機瀏覽器以外的算繪器(固定版本的 Docker 映像、託管的算繪器)能以rendererVersion與imageDigest表明身分,也讓不得不退回本機瀏覽器的執行能以renderer.fallback: true標記。請把算繪器不同的條目視為不同的基準影像。hashes保存images中每個實際存在於磁碟上的檔案的 sha256。可以用來略過重新上傳未變更的基準影像、檢查 artifact 是否與清單相符,或找出產生完全相同像素的兩個條目。- 對應到 pull request:請使用
ci.repositoryName與ci.pullRequest.number。在 GitHub 的pull_request事件中,ci.commit是暫時性的合併 commit,所以任何要推送到 PR 分支的操作都必須使用ci.pullRequest.headSha/headRef。工作流程的 artifact 可透過ci.buildId與ci.runAttempt找到。 - 重現一次執行:
npx cypress run --<runner.testingType> --browser <runner.browser.name> --config-file <runner.configFile> --spec <runner.specs joined with ,>,再為options的每個條目加上一個--expose "pluginVisualRegression<Key>=<value>"。清單以各個部分而非一整串指令字串的形式儲存這些資訊,因此 shell 的引號處理與套件管理工具仍由您決定。 - 這個檔案會在每次比對後重寫,所以即使執行中途中止,它仍是完整的。當 spec 在多台機器上平行執行時,每台機器會寫出自己的清單;
**/visual-regression-manifest{,.*}.json(套件中的MANIFEST_FILE_GLOB)可以在 artifact 中找出所有清單,而visual-regression-manifest的readManifestFiles+mergeManifests會把它們合併成一份清單,較晚的 CI attempt 會覆蓋較早的。 - 只有
runner區塊與options的鍵是 Cypress 專屬的;格式的其餘部分與所有其他寫入者共用。specVersion遵循 semver,只有在格式發生破壞性變更時才會提高主版號;消費端會忽略不認識的鍵。由於欄位名稱遵循 CTRF,套件中的toCtrf可以把清單轉成 CTRF 報告,供讀取該格式的工具使用。 - 為了方便起見,
Manifest*型別也從@pixsame/cypress-plugin-visual-regression-diff/plugins重新匯出;它們就是visual-regression-manifest的型別。
清單預設為開啟。若要把它寫到其他位置(路徑會以專案根目錄為基準解析)或關閉它,請使用 pluginVisualRegressionManifestPath 鍵(false 會停用它,其他行為不變):
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionManifestPath=reports/visual-regression.json"
npx cypress run --expose "pluginVisualRegressionManifestPath=false"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionManifestPath=false"
當您以自訂路徑同時執行 e2e 與元件測試時,請自行把測試類型加進路徑中;否則兩次執行會互相覆寫對方的檔案。
設定
您可以透過以下方式設定這個外掛:
- 將設定以參數的形式傳給
matchImage指令:
cy.matchImage({
// 截圖設定,會直接傳給 Cypress 的 screenshot 方法:https://docs.cypress.io/api/cypress-api/screenshot-api#Arguments
// 預設值:{ }
screenshotConfig: {
blackout: ['.element-to-be-blackouted'],
},
// pixelmatch 選項,請參考:https://www.npmjs.com/package/pixelmatch#pixelmatchimg1-img2-output-width-height-options
// 預設值:{ },即 pixelmatch 的預設值,其中特別是 includeAA: false(抗鋸齒的邊緣像素不會被計為差異)
diffConfig: {
threshold: 0.01,
},
// 是否自動建立缺少的基準影像
// 預設值:true
createMissingImages: false,
// 是否自動更新影像,在 CI 上很實用
// - true:不比對,直接覆寫所有基準影像(最快)
// - 'failures-only':先比對,只在差異超過門檻值時才更新基準影像
// - false:永遠不自動更新基準影像
// 預設值:false
updateImages: true,
// 儲存截圖影像的目錄路徑
// 相對路徑會以專案根目錄為基準解析
// 支援絕對路徑(unix 與 windows 作業系統皆可)
// 外掛會依作業系統正規化路徑分隔符號,您應一律使用 / 作為分隔符號,例如:C:/my-directory/nested 表示 windows 風格的磁碟機路徑
// 路徑中可使用下列變數:
// - {spec_path}:從專案根目錄到目前 spec 檔案所在目錄的相對路徑(例如 `/src/components/my-tested-component`);必須自成一個完整的路徑片段
// - {platform}:`{os}-{browser}`,例如 `linux-chrome`、`darwin-electron`、`win32-firefox`
// - {os}:Cypress.platform,即 `linux`、`darwin` 或 `win32`
// - {browser}:Cypress 執行該 spec 所用的瀏覽器(Cypress.browser.name):未以 `--browser` 指定時為 `electron`,否則為 `chrome`、`firefox`、`edge` 等
// {platform}、{os} 與 {browser} 也可以只是片段的一部分(`shots-{platform}`);`matchAgainstPath` 永遠不會被展開
// 可用它們把不同作業系統/瀏覽器的基準影像分開存放,請見下方「依平台區分的基準影像」
// 預設值:'{spec_path}/__image_snapshots__'
imagesPath: 'this-might-be-your-custom/maybe-nested-directory',
// 測試失敗的最大門檻值,差異超過此值即失敗
// 預設值:0.01
maxDiffThreshold: 0.1,
// 當比對發現差異時,描述發生了什麼變化,例如 "Everything below y=340 moved down by 12 px."
// 這句話會附在失敗訊息結尾,顯示在審查輪播中,並記錄到執行清單裡
// 它不會改變比對是否通過
// 預設值:true
analysis: false,
// 強制將縮放比例設為 "1"
// 可解決在 Mac Retina 等高解析度螢幕上截圖被放大 2 倍的問題
// 預設值:true
forceDeviceScaleFactor: false,
// 套用確定性算繪預設組合,請見下方「降低跨作業系統的算繪雜訊」
// 在單次呼叫中,它只控制為截圖注入頁面的 CSS(游標、動畫、捲軸);
// 瀏覽器的啟動參數是整個程序共用的,由全域的 pluginVisualRegressionDeterministicRendering 選項決定
// 預設值:true
deterministicRendering: false,
// 用來命名影像檔案的標題
// 預設值:Cypress.currentTest.titlePath(您的測試標題)
title: `${Cypress.currentTest.titlePath.join(' ')} (${Cypress.browser.displayName})`,
// 傳入自訂影像的路徑作為比對對象
// 而不是與上一次執行的影像比對
// 預設值:undefined
matchAgainstPath: '/path/to/reference-image.png',
});
- 透過全域 expose 設定(Cypress 15.10+)。設定鍵的名稱與上方設定物件的鍵相同,只是多了
pluginVisualRegression前綴,例如:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionUpdateImages=true,pluginVisualRegressionDiffConfig={\"threshold\":0.01}"
請在同一個旗標中以逗號分隔傳入多個選項。若重複使用 --expose(或 --env),Cypress 只會保留最後一個。
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionUpdateImages: true,
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
},
});
// cypress.config.ts(Cypress <15.10,新版已棄用)
import { defineConfig } from 'cypress';
export default defineConfig({
env: {
pluginVisualRegressionUpdateImages: true,
pluginVisualRegressionDiffConfig: { threshold: 0.01 },
},
});
更多設定 expose 變數的方式請參考這裡。
依平台區分的基準影像
Chrome 會把文字點陣化交給作業系統處理,因此同一個頁面在 macOS、Linux 與 Windows 上永遠不會算繪出逐位元組完全相同的結果,而各瀏覽器之間又還有各自的差異。所以在開發者的 Mac 上錄製的基準影像,很少能和 Linux CI 工作對同一個頁面拍下的截圖吻合。imagesPath 支援三個 token,可以把不同平台的基準影像分開存放,而不是讓它們互相覆蓋:
| token | 展開為 |
|---|---|
{os} | Cypress.platform:linux、darwin 或 win32 |
{browser} | Cypress.browser.name,即 Cypress 執行該 spec 所用的瀏覽器:未傳入 --browser 時為 electron,否則為 chrome、firefox、edge 等 |
{platform} | {os}-{browser},例如 linux-chrome、darwin-electron |
每張截圖都是由 Cypress 驅動的瀏覽器拍攝的,所以 {browser} 就是那個瀏覽器;cypress run 未加 --browser 時使用 Electron,這就是為什麼除非 CI 傳入 --browser chrome,否則資料夾會是 darwin-electron 而不是 darwin-chrome。
這些 token 是選用的:預設的 imagesPath 仍然是 {spec_path}/__image_snapshots__,也就是所有人共用一組基準影像。當每次執行都在同一個作業系統與瀏覽器上進行(例如在容器內,請見常見問題),或是剩餘的差異仍低於您的 maxDiffThreshold 時,這就是正確的配置。若要依平台拆分,請全域加入這個 token:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{platform}"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{platform}"
// cypress.config.ts(Cypress 15.10+;Cypress <15.10 請改用 `env` 取代 `expose`)
export default defineConfig({
expose: {
pluginVisualRegressionImagesPath:
'{spec_path}/__image_snapshots__/{platform}',
},
});
cypress/e2e/__image_snapshots__/
├── darwin-electron/ # 在 Mac 上執行 `cypress open` 所寫入的內容
│ └── home page renders_#0.png
└── linux-chrome/ # 您的 CI 工作所寫入的內容
└── home page renders_#0.png
拆分之後需要決定的事項:
- 要提交哪些基準影像。 多數團隊只提交 CI 平台的基準影像,本機的則不納入 git,這樣每位開發者都有自己的一組,又不會污染儲存庫:
# 忽略所有平台資料夾,只保留 CI 產生的那一個 **/__image_snapshots__/*/ !**/__image_snapshots__/linux-chrome/
如此一來,新的 CI 基準影像就必須在 CI 上產生並從那裡提交(或從 PR 核准,請見執行清單)。 - 只依瀏覽器或只依作業系統拆分。 單獨使用
{browser}或{os},例如{spec_path}/__image_snapshots__/{browser}。 - 清理功能會刪除其他平台的基準影像。
pluginVisualRegressionCleanupUnusedImages會在整個專案中搜尋外掛產生的 PNG,而且完全不知道平台的存在:在darwin-electron上執行時,會把linux-chrome/底下的每個檔案都視為未使用並刪除。在存有多個平台資料夾的機器上(開發者的本機檢出加上已提交的 CI 基準影像正是這種情況)請保持關閉,只在單一平台基準影像所在之處啟用,通常就是產生這些影像的 CI 工作。萬一真的誤刪了,用git checkout -- <folder>就能把被刪除的資料夾找回來。
未來的方向:#212 的計畫是一個算繪器,不論 Cypress 在哪裡執行,都在固定版本的 Docker 映像中產生像素,讓本機與 CI 的影像從根本上不再漂移。屆時 {browser} 指的會是算繪器的瀏覽器,而不是 Cypress 驅動的那個;{platform} 則仍可用來區分多個被算繪的瀏覽器,而不再是為了處理本機與 CI 之間的漂移。清單已經同時記錄了這兩邊:platform 是測試執行的位置,renderer 是像素的來源。
批次審閱模式
批次審閱模式(Batch Review Mode)預設為啟用。它讓您可以跑完整個測試套件,而不會在第一個視覺差異失敗時就停下來。所有失敗的快照會被收集起來,在測試結束後呈現在互動式的審閱介面中,讓您可以批次核准或略過變更。
有頭模式(非 CI 環境)下的額外功能
Cypress 測試執行器的右下角會出現一個浮動操作按鈕(FAB)。測試執行期間:
- 每當
matchImage()呼叫的差異超過門檻值時,FAB 上的計數徽章會加一,而不是立即拋出錯誤。 - 所有測試跑完後,會拋出一個錯誤,列出失敗的總數。
- 點擊 FAB 會開啟一個輪播介面,讓您逐一並排檢視每張失敗的快照(新 vs. 舊),並選擇取代基準影像或略過這項變更。

注意:在
@pixsame/cypress-plugin-visual-regression-diff第 2 版之前,批次審閱模式是選用功能。如果您是從 4.x 升級,請參閱遷移指南。
如何停用
批次審閱模式是全域選項(無法直接傳給 matchImage)。若要恢復成第一次不符時就立即讓 matchImage() 失敗,請將 pluginVisualRegressionBatchReviewMode 鍵設為 false:
# Cypress 15.10+
npx cypress open --expose "pluginVisualRegressionBatchReviewMode=false"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress open --env "pluginVisualRegressionBatchReviewMode=false"
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionBatchReviewMode: false,
},
});
// cypress.config.ts(Cypress <15.10,新版已棄用)
import { defineConfig } from 'cypress';
export default defineConfig({
env: {
pluginVisualRegressionBatchReviewMode: false,
},
});
審閱通過的影像
與基準影像有差異、但仍在 maxDiffThreshold 範圍內的影像不會讓測試失敗,但同樣會被收集起來。輪播介面中有一個 Show passing 核取方塊,勾選後即可瀏覽這些影像(每張都會標示 passed 徽章),並同樣可以取代它們的基準影像,例如用來消除逐漸累積的細微偏移。
這個核取方塊預設為未勾選。若想讓它預設勾選,請使用 pluginVisualRegressionBatchReviewModeShowPassingImages 鍵:
# Cypress 15.10+
npx cypress open --expose "pluginVisualRegressionBatchReviewModeShowPassingImages=true"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress open --env "pluginVisualRegressionBatchReviewModeShowPassingImages=true"
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionBatchReviewModeShowPassingImages: true,
},
});
在執行器中切換這個核取方塊的狀態會記錄在瀏覽器的 local storage 中,之後的執行會以它為準,優先於設定檔中的值。
降低跨作業系統的算繪雜訊
瀏覽器會把文字的點陣化交給作業系統處理(macOS 用 CoreText、Windows 用 DirectWrite、Linux 用 FreeType),因此同一個頁面在開發者的筆電與 Linux CI 執行器上,永遠不會算繪出逐位元組完全相同的結果。外掛無法完全消除這個問題,但 deterministicRendering 預設組合(預設啟用)可以去除發生在瀏覽器內部的那一部分雜訊:
- Chrome、Chromium、Edge 會以
--font-render-hinting=none --disable-font-subpixel-positioning --disable-lcd-text --force-color-profile=srgb --disable-gpu啟動(文字不使用作業系統的 hinting 與次像素技巧、不做色彩管理、以 CPU 點陣化),無頭模式下另外加上--hide-scrollbars。這份清單以DETERMINISTIC_RENDERING_CHROMIUM_ARGS的名稱從@pixsame/cypress-plugin-visual-regression-diff/constants匯出。 - Firefox 會設定
gfx.webrender.software偏好設定(CPU 算繪)。Firefox 沒有跨平台的字型 hinting 開關,因此文字在不同作業系統之間仍可能有差異。 - 所有瀏覽器:在
matchImage截圖期間,會在受測頁面注入一個<style>元素,用來隱藏文字游標、停用 CSS transition、動畫與平滑捲動,並隱藏捲軸。截圖一結束就會移除。 - 比對時會以
includeAA: false執行pixelmatch,因此抗鋸齒的邊緣像素會被偵測出來,且不會被計為差異。
這個預設組合無法解決的問題:各作業系統使用不同的字型檔與備用字型(經典的 Arial 對 Liberation Sans 情況)、emoji 字型、WebKit 與 Firefox 的文字點陣化,以及 WebGL 繪製的影像。對於這些情況,請為每個平台各保留一份基準影像(請參考 title 與 imagesPath 選項,以及常見問題中關於瀏覽器名稱的條目),或是在與 CI 相同的環境中產生基準影像。
Electron 會忽略外掛設定的啟動參數。請改用環境變數傳入這些參數:
ELECTRON_EXTRA_LAUNCH_ARGS="--font-render-hinting=none --disable-font-subpixel-positioning --disable-lcd-text --force-color-profile=srgb --disable-gpu --hide-scrollbars" npx cypress run
WebGL:--disable-gpu 可能讓依賴 WebGL 的頁面無法取得算繪環境。您可以停用預設組合,或是在自己的 before:browser:launch 處理常式中加入 --use-gl=angle --use-angle=swiftshader(較新的 Chrome 還需要 --enable-unsafe-swiftshader),並透過 cypress-on-fix 與外掛本身的處理常式組合使用。
截圖 hook:外掛會為它拍攝的每一張截圖傳入自己的 onBeforeScreenshot 與 onAfterScreenshot。透過 screenshotConfig 傳入的 hook 仍會被呼叫;以 Cypress.Screenshot.defaults() 全域設定的 hook 則不會。
若要全域關閉這個預設組合(瀏覽器啟動參數與 CSS):
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionDeterministicRendering=false"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionDeterministicRendering=false"
// cypress.config.ts (Cypress 15.10+)
import { defineConfig } from 'cypress';
export default defineConfig({
expose: {
pluginVisualRegressionDeterministicRendering: false,
},
});
若要保留瀏覽器啟動參數,但在單張截圖中略過 CSS(例如您想擷取動畫結束時的狀態):
cy.matchImage({ deterministicRendering: false });
常見問題
為什麼截圖不符合我在 Cypress 設定中指定的 viewport?
Cypress 的截圖預設不會依 viewport 大小縮放。您可以這樣調整:
- 全域:修改預設的截圖設定:
Cypress.Screenshot.defaults({ capture: 'viewport' }); - 區域:直接把截圖設定傳給
.matchImage指令:cy.matchImage({ screenshotConfig: { capture: 'viewport' } });
我升級了這個外掛的版本,結果所有基準影像都被自動更新了。為什麼?
有時候我們必須對影像比對或影像產生的演算法做出破壞性變更。為了讓您的升級過程盡可能輕鬆,外掛會自動更新您的基準影像。升級外掛後,只要把這些影像 commit 進您的 repository 就可以了!
cypress run 和 cypress open 的截圖看起來不一樣,該怎麼解決?
這通常是無頭模式與有頭模式之間 device pixel ratio 不同所造成的。請使用 forceDeviceScaleFactor 選項將縮放比例統一為 1:
cy.matchImage({ forceDeviceScaleFactor: true });
或透過 expose 變數全域設定:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionForceDeviceScaleFactor=true"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionForceDeviceScaleFactor=true"
若 viewport 大小的差異持續存在,可以考慮在 setupNodeEvents 中明確指定瀏覽器視窗大小(請透過 cypress-on-fix 與外掛本身的 before:browser:launch 處理常式組合使用):
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium' && browser.isHeadless) {
launchOptions.args.push('--window-size=1280,720');
}
return launchOptions;
});
若兩次執行是在不同的作業系統上(macOS 筆電對 Linux CI 執行器),剩下的差異來自字型算繪。請參考降低跨作業系統的算繪雜訊。
如何讓這個外掛與其他同樣在 setupNodeEvents 註冊事件的 Cypress 外掛並用?
Cypress 每個事件只支援一個處理函式。如果多個外掛註冊了同一個事件(例如 after:screenshot),只有最後一個會被呼叫。若要安全地組合多個外掛,請使用 cypress-on-fix:
import { defineConfig } from 'cypress';
import { initPlugin as initVisualRegressionPlugin } from '@pixsame/cypress-plugin-visual-regression-diff/plugins';
import fix from 'cypress-on-fix';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
const fixedOn = fix(on);
initVisualRegressionPlugin(fixedOn, config);
// 在這裡使用 fixedOn 註冊其他外掛
},
},
});
如何整合 mochawesome 或其他會嵌入截圖的 reporter?
這個外掛會把基準影像存放在預設的 cypress/screenshots/ 目錄之外。有些 reporter(例如 cypress-mochawesome-reporter)則預期截圖會放在那個資料夾裡。
請使用 processImgPath task hook 來控制截圖檔案的存放位置。在 setupNodeEvents 中呼叫 initPlugin 之後覆寫它:
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
// 覆寫路徑處理函式,讓 reporter 能存取截圖
on('task', {
'cp-visual-regression-diff-processImgPath': ({ path }: { path: string }) => {
// 回傳新的路徑,或回傳原路徑以保留預設行為
return path;
},
});
}
如何讓任何視覺差異(不論多細微)都導致測試失敗?
將 maxDiffThreshold 設為 0:
cy.matchImage({ maxDiffThreshold: 0 });
或透過 expose 變數全域設定:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionMaxDiffThreshold=0"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionMaxDiffThreshold=0"
為什麼影像比對成功後,實際截圖就被刪除了?
當影像相符時,.actual.png 檔案只是暫時性的,比對完成後就會被清掉,只會保留基準影像(沒有後綴的那個)。這樣可以避免儲存多餘的檔案。
當影像不相符時,.actual.png 檔案會被保留,並帶有與基準影像相同的外掛 metadata。您可以放心地將它重新命名或複製來覆蓋基準影像(例如在 CI 工作中開一個帶有更新後基準影像的 Pull Request),外掛會把它視為最新的基準影像,下次執行時不會再重寫它。
如果您需要讓實際截圖持續可供 reporter 存取(例如 cypress-mochawesome-reporter),請使用 processImgPath task hook 來控制截圖的儲存位置。請參考如何整合 mochawesome 或其他會嵌入截圖的 reporter?
如何移除截圖檔名中的空格?
截圖檔名取自 Cypress 的測試標題,而標題中可能包含空格。若想使用不含空格的檔名,請在每次呼叫 matchImage 時傳入自訂的 title 選項:
cy.matchImage({ title: 'my-screenshot-without-spaces' });
若要套用到所有 matchImage 呼叫,請在 support 檔案(cypress/support/commands.ts)中覆寫這個指令:
Cypress.Commands.overwrite('matchImage', (originalFn, subject, options = {}) =>
originalFn(subject, {
title: Cypress.currentTest.titlePath.join(' ').replace(/\s+/g, '-'),
...options,
}),
);
如何在影像檔名中加入瀏覽器名稱(用於跨瀏覽器測試)?
請在 imagesPath 中使用 {browser} token;它會展開為拍攝該截圖的瀏覽器名稱(chrome、firefox、electron 等):
cy.matchImage({ imagesPath: '{spec_path}/__image_snapshots__/{browser}' });
或是全域設定:
# Cypress 15.10+
npx cypress run --expose "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{browser}"
# Cypress <15.10(15.10 起棄用,16 已移除)
npx cypress run --env "pluginVisualRegressionImagesPath={spec_path}/__image_snapshots__/{browser}"
這樣會為每個瀏覽器建立各自的影像目錄(__image_snapshots__/chrome/、__image_snapshots__/firefox/)。關於同時也依作業系統拆分的 {platform} 與 {os}、該提交哪些資料夾,以及為什麼在存有多個瀏覽器資料夾的機器上必須保持關閉 pluginVisualRegressionCleanupUnusedImages(它會刪除其他瀏覽器的基準影像),請見依平台區分的基準影像。
如何產生同時顯示基準、差異與實際影像的 HTML 報告?
這個外掛在 Cypress UI 中內建了視覺比對的覆蓋介面,在失敗的測試上點擊「See comparison」,就能並排檢視基準、差異與實際影像。
若需要用於 CI 或可分享的 HTML 報告,請整合像 cypress-mochawesome-reporter 這類的 reporter。設定細節請參考如何整合 mochawesome 或其他會嵌入截圖的 reporter?。