本页为机器翻译,尚未经过人工审校。以英文版本为准。
快速开始
安装
本插件需要 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」按钮,点击即可查看问题所在。
示例
安装时仍有问题?可以看看本仓库的 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 提供商、仓库、提交、pull request、运行 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 产物放在一起,位于 <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 事件中:合并提交
"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 系统上(只设置了 CI)为 { "provider": null },不在 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 项目目录,不一定是仓库根目录:ci.workspace是 checkout 目录,因此relative(ci.workspace, projectRoot)即为仓库内的路径前缀。 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保留未展开的变量);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是临时的合并提交,因此任何需要推送到 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 系统均可)
// 插件会根据操作系统对路径分隔符做规范化,请始终使用 / 作为分隔符,例如 windows 盘符写法:C:/my-directory/nested
// 路径中可以使用以下变量:
// - {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` 永远不会被展开
// 可用它们将不同操作系统/浏览器的基线分开存放,参见下文 "Per-platform baselines"
// 默认值:'{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,
// 应用确定性渲染预设,参见下文 "Reducing cross-OS rendering noise"
// 在单次调用中,它只控制为截图注入页面的 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),用于将不同平台的基线分开存放,而不是让它们互相覆盖:
| 令牌 | 展开为 |
|---|---|
{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} 就是那个浏览器;不带 --browser 的 cypress run 使用 Electron,这就是为什么目录名是 darwin-electron 而不是 darwin-chrome——除非 CI 传入了 --browser chrome。
这些令牌是可选启用的:默认的 imagesPath 仍为 {spec_path}/__image_snapshots__,即所有人共用一套基线。当每次运行都在相同的操作系统和浏览器上进行(例如在容器内,参见 FAQ),或者剩余的差异仍低于您的 maxDiffThreshold 时,这就是正确的布局。要按平台拆分,请全局添加该令牌:
# 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/下的每个文件都视为未使用并删除。在存放了多个平台目录的机器上(开发者的 checkout 加上已提交的 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 中,后续运行时将优先于配置中的值。
Reducing cross-OS rendering noise
浏览器会把文字光栅化交给操作系统处理(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 过渡、动画和平滑滚动,并隐藏滚动条。截图完成后立即移除。 - 比对时会以
includeAA: false运行pixelmatch,因此抗锯齿边缘像素会被检测出来,不计入差异。
该预设无法解决的问题:各操作系统上字体文件和回退字体不同(典型的 Arial 与 Liberation Sans 问题)、emoji 字体、WebKit 和 Firefox 的文字光栅化,以及 WebGL 绘制的图像。对于这些情况,请为每个平台各保留一份基线(参见 title 和 imagesPath 选项,以及关于浏览器名称的 FAQ 条目),或者在与 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 将其与插件自带的处理函数组合起来。
截图钩子:插件会为它拍摄的每张截图传入自己的 onBeforeScreenshot 和 onAfterScreenshot。通过 screenshotConfig 传入的钩子仍会被调用;通过 Cypress.Screenshot.defaults() 全局设置的钩子则不会。
要全局关闭该预设(浏览器开关和 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 的截图默认不会按视口尺寸裁剪。您可以这样修改此行为:
- 全局修改,更改默认截图配置:
Cypress.Screenshot.defaults({ capture: 'viewport' }); - 局部修改,将截图配置直接传给
.matchImage命令:cy.matchImage({ screenshotConfig: { capture: 'viewport' } });
升级本插件版本后,我所有的基线图片都被自动更新了,这是为什么?
有时我们需要对图片比对或图片生成算法做破坏性变更。为了让您的升级过程尽可能顺畅,插件会自动更新您的基线图片。升级插件后,只需把这些图片提交到仓库即可!
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"
如果视口尺寸差异持续存在,可以考虑在 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 机器),剩余的差异来自字体渲染。参见 Reducing cross-OS rendering noise。
如何与其他同样在 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 或其他会内嵌截图的报告工具集成?
本插件将基线图片存放在默认的 cypress/screenshots/ 目录之外。而有些报告工具(如 cypress-mochawesome-reporter)要求截图位于该目录下。
可以使用 processImgPath task 钩子控制截图文件的存放位置。在 setupNodeEvents 中调用 initPlugin 之后覆盖它:
setupNodeEvents(on, config) {
initVisualRegressionPlugin(on, config);
// 覆盖路径处理函数,让报告工具能够访问到截图
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 文件会被保留,并且携带与基线图片相同的插件元数据。您可以放心地将它重命名或复制覆盖到基线图片上(例如在 CI 任务中用更新后的基线创建一个 Pull Request)——插件会将其视为最新的基线,下次运行时不会重新生成。
如果您需要让实际截图对报告工具(例如 cypress-mochawesome-reporter)保持可访问,请使用 processImgPath task 钩子来控制截图的存放位置。参见如何与 mochawesome 或其他会内嵌截图的报告工具集成?
如何去掉截图文件名中的空格?
截图文件名来自 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} 令牌;它会展开为渲染该截图的浏览器名称(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 界面中内置了视觉比对浮层:在失败的测试上点击「See comparison」,即可并排查看基线、差异和实际截图。
如需用于 CI 或便于分享的 HTML 报告,可以与 cypress-mochawesome-reporter 之类的报告工具集成。配置细节参见如何与 mochawesome 或其他会内嵌截图的报告工具集成?。