关于 visual-testing
visual-testing 是介绍视觉回归测试与基线管理的技能,面向需要发现功能测试无法覆盖的界面变化的团队。内容涵盖 Playwright 截图比较、专用视觉测试工具、动态内容处理及基线审查流程。
主要功能
- 01使用 Playwright 比较截图基线
- 02配置像素差异阈值
- 03遮罩或冻结动态内容
- 04管理并审查视觉基线
- 05介绍 Chromatic、Percy 与 Argos CI
适用场景
- 01在 CI 中检测界面视觉回归
- 02为动态页面减少截图误报
- 03评审设计变更并更新基线
- 04管理存储在 Git 中的截图基线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill visual-testing安装后在 Claude Code 中输入/visual-testing确认已加载。
附带文件references/recipes.md
详细介绍
快速导航
| 情况 | 前往 |
|---|---|
| 只需要在 CI 中进行像素差异比较,不需要审核 SaaS | Playwright 视觉比较(下文) |
| 仓库中有 Storybook | 专用工具 → Chromatic,或 recipes.md 中的“无需 Chromatic 的 Storybook” |
| 需要托管的审核/批准界面 + AI 差异分类 | 专用视觉测试工具 |
| 截图因时间戳/头像/广告而不稳定 | 遮罩与冻结动态内容 |
| 基线导致 git 历史膨胀 | 基线管理 → Git LFS |
调研问题
先检查 .agents/qa-project-context.md——如果存在,使用其中的信息,并跳过已解答的问题。
工具选择
- 使用 Playwright 内置功能还是专用工具?
toHaveScreenshot免费,基线存储在仓库中(git/LFS 的成本由你承担)。专用工具(Chromatic、Percy、Argos)会将产物存储在仓库之外,并提供审核工作流、浏览器集群、历史记录跟踪,以及——自 2025 年末起——AI 差异分类。根据团队规模、审核需求,以及你希望产物存储在自己的仓库还是它们的仓库中进行选择。 - 项目中有 Storybook 吗? 如果有,Chromatic 是自然之选(每个 story 都会成为视觉测试)——但你也可以用 Playwright 驱动 stories,无需付费(参见 recipes.md)。如果没有 Storybook,可选择 Playwright 内置功能或 Percy。
- CI 平台和产物存储预算如何? 视觉测试会生成体积较大的截图/差异产物。内置基线存储在 git 中(导致仓库膨胀,可使用 LFS);托管工具将保留的产物存储在仓库外,但需要订阅费用。扩大使用规模前,确认 CI 有足够的存储空间和执行时间。
范围
- 全页截图还是组件截图? 全页截图能捕获布局问题,但容易受无关改动影响。组件级截图更稳定,关注范围也更明确。
- 哪些页面/组件在视觉上至关重要? 并非所有内容都需要测试。重点关注面向用户的页面、营销页面、设计系统组件和复杂布局。
- 使用哪些视口? 桌面、平板、移动端——根据分析数据提前定义视口矩阵,而不是覆盖所有可能的宽度。
动态内容
- 不同运行之间有哪些内容会变化? 日期、时间戳、用户生成内容、分析 ID、随机内容、广告、头像——这些都必须遮罩或冻结。
- 是否有动画或过渡效果? 如果截图前未禁用或等待其完成,就会导致误报。
- 是否有外部资源? 字体、CDN 图片、第三方组件可能会在不同运行之间发生变化。
核心原则
1. 视觉测试能捕获功能测试遗漏的问题
功能测试断言行为(“点击提交后会显示成功消息”)。视觉测试断言外观(“成功消息为绿色、位置正确,且不会与表单重叠”)。两者缺一不可。视觉测试是对功能测试的补充,而非替代。
2. 基线管理才是难点
截图很容易。管理基线——在设计有意变更时更新基线、审核差异、协调团队审批——才是真正的挑战。尽早完善审核工作流。
3. 动态内容会导致误报
任何在不同运行之间会变化的内容(时间戳、头像、广告、随机 ID)都会产生并非真实回归的像素差异。应积极遮罩或冻结这些内容。如果测试套件的误报率达到 10%,一个月内就会被弃用。托管工具(Percy 的 Visual Review Agent、Chromatic AI 分类)现在能够自动过滤其中很大一部分——但无论使用哪种工具,遮罩和冻结始终是首要防线。
4. 阈值调优是一个迭代过程
合适的差异阈值取决于组件、渲染引擎,以及对“视觉差异”的定义。从严格设置开始(零容忍),观察误报,再按组件逐步放宽。记录选择每个阈值的原因。
5. 截图是产物,不是测试结果
截图文件就是证据。保存它、对其进行版本管理,并确保审核者可以访问。如果测试只提示“检测到视觉差异”却不展示差异,就毫无用处——始终将预期图、实际图和差异图作为 CI 产物上传。
Playwright 视觉比较
Playwright 内置的 toHaveScreenshot 和 toMatchSnapshot 可提供视觉回归测试,无需外部服务。
基本截图比较
import { test, expect } from '@playwright/test';
test('dashboard matches baseline', async ({ page }) => {
await page.goto('/dashboard');
// Wait for data to load before capturing — never waitForTimeout
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('chart-container')).toBeVisible();
await expect(page).toHaveScreenshot('dashboard.png', { animations: 'disabled' });
});
首次运行会创建基线。之后的运行会进行比较,如果像素差异超过阈值,就会失败。
配置选项
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixels: 100, // Allow up to 100 pixels to differ
// OR
maxDiffPixelRatio: 0.01, // Allow up to 1% of pixels to differ
threshold: 0.2, // Per-pixel color difference tolerance (0-1, YIQ space)
animations: 'disabled', // Freeze CSS animations and transitions
caret: 'hide', // Hide blinking cursor
stylePath: './screenshot.css', // Inject a stylesheet at capture to hide dynamic chrome
timeout: 15000, // Wait up to 15s for a stable screenshot
});
默认比较器是 pixelmatch(YIQ 色彩空间)。相关参数是 comparator: 'pixelmatch' 加上 threshold/maxDiffPixel*——没有稳定的感知色彩模式键,因此不要设置 mode:(感知 SSIM 是内部/实验性的下划线 API,不是稳定配置)。
阈值选用指南:
| 选项 | 适用场景 |
|---|---|
maxDiffPixels: 0 | 像素级精确匹配的组件(图标、徽标、设计系统原子组件) |
maxDiffPixels: 50-100 | 抗锯齿略有差异的整页布局 |
maxDiffPixelRatio: 0.01 | 绝对像素数会随视口变化的整页截图 |
threshold: 0.2 | 颜色渲染略有差异的跨浏览器测试 |
playwright.config.ts 视觉设置
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toHaveScreenshot: {
comparator: 'pixelmatch', // Default comparator (YIQ color space)
maxDiffPixelRatio: 0.005, // Global default: 0.5% tolerance
animations: 'disabled',
caret: 'hide',
},
toMatchSnapshot: {
maxDiffPixelRatio: 0.005,
},
},
projects: [
{
name: 'visual-desktop',
use: { viewport: { width: 1280, height: 720 }, colorScheme: 'light' },
testMatch: /.*visual.*\.spec\.ts/,
},
{
name: 'visual-mobile',
use: { viewport: { width: 375, height: 667 }, colorScheme: 'light', isMobile: true },
testMatch: /.*visual.*\.spec\.ts/,
},
],
});
遮罩与冻结动态内容
有两种互补的方法。使用遮罩隐藏特定元素;使用 stylePath/冻结来消除由时间和动画驱动的噪声。
test('profile page visual test', async ({ page }) => {
await page.goto('/profile');
await expect(page.getByRole('heading', { name: 'Profile' })).toBeVisible();
await expect(page).toHaveScreenshot('profile.png', {
mask: [
page.getByTestId('user-avatar'), // User-specific image
page.getByTestId('last-login-time'), // Timestamp
page.getByTestId('activity-feed'), // Dynamic content
],
maskColor: '#FF00FF', // Visible mask color for debugging
});
});
对于光标、动画和动态界面元素,优先使用 stylePath(在捕获时注入的样式表),而不是逐个元素添加遮罩——这种方式是声明式的,也能适应 DOM 变化。要消除时间戳和实时数据,请使用 page.clock.setFixedTime 固定时钟(让时钟完全静止——最适合确保截图确定性;page.clock.install 则允许时钟从种子值开始走动),并通过 page.route + route.fulfill 模拟 API。
完整的冻结数据示例(时钟 + 路由 + 字体中止 + getAnimations().finish())、stylePath 示例及组件状态截图,请参阅 references/recipes.md。
更新基线
# Update all baselines (when design intentionally changes)
npx playwright test --update-snapshots
# Update baselines for specific tests only
npx playwright test visual-dashboard --update-snapshots
# Review what changed before committing
git diff --stat # See which baseline files changed
npx playwright show-report # Visually review expected/actual/diff for each
基线更新工作流:
- 实现设计变更。
- 运行视觉测试——测试会失败并显示预期差异。
- 在
show-report中审核每个差异:该变更是否符合预期? - 更新基线:
npx playwright test --update-snapshots。 - 提交更新后的基线,并在提交信息中注明相关设计变更。
- PR 审核者要确认基线图像看起来正确,而不只是检查文件差异。
组件级与响应式测试
截取组件(例如 getByRole('table')),而非整页——这样更稳定,也更聚焦。通过在每项测试中模拟 API 来驱动空、错误和正常状态。要覆盖响应式场景,可循环遍历 VISUAL_VIEWPORTS 数组(包含带有 isMobile 标志的移动端/平板/桌面视口),或为每个视口定义一个 Playwright 项目。组件状态和响应式循环的示例,请参阅 references/recipes.md。
专用视觉测试工具
如果你需要托管的审核/批准界面、跨浏览器渲染集群、历史跟踪或 AI 辅助差异分类,并且不介意将产物存放在代码仓库之外并支付订阅费用,可以选择这些工具。
| 工具 | 最适用场景 | 集成方式 | 主要功能 |
|---|---|---|---|
| Chromatic | 项目使用 Storybook | 每个 story 都是一次视觉测试 | 审核/批准界面、跨浏览器、TurboSnap + AI 分类 |
| Percy | 不使用 Storybook,需要支持多个浏览器 | 通过 SDK 支持任何框架 | 多宽度截图、CSS 覆盖、AI Visual Review Agent(自动过滤约 40% 的误报)、BrowserStack Test Observability |
| Argos CI | 倾向开源、注重预算 | Playwright reporter | 可自托管版本;慷慨的云端免费额度 |
**AI 差异分类(2026 年的重要转变):**Percy 的 Visual Review Agent 和 Chromatic 的 AI 分类会自动将大量差异归类为噪声或真实变更,从而减轻审核疲劳。这是相较于内置基线选择付费托管工具的最有力理由,但它只能减少而无法消除屏蔽/冻结动态内容的需求。
**避免使用:**Lost Pixel — 代码仓库已于 2026 年 4 月 22 日归档(只读)。请改用 Argos、Chromatic 或 Playwright 内置的
toHaveScreenshot。
参见 references/recipes.md,了解 Chromatic GitHub Action、Percy percySnapshot、Argos argosScreenshot,以及不使用 Chromatic 的 Storybook 代码片段。
基线管理
Git 存储的基线、LFS 和平台标签
Playwright 会将基线与测试文件一同存储,并按平台标记,因为不同操作系统上的渲染效果会有所不同:
e2e/tests/visual/
dashboard.visual.spec.ts
dashboard.visual.spec.ts-snapshots/
dashboard-chromium-linux.png # Platform-specific baselines
dashboard-chromium-darwin.png
dashboard-firefox-linux.png
**优点:**与代码一同版本管理,在 PR 中审核,可离线使用。**缺点:**代码仓库体积会增大;大型 PNG 会使 Git 历史膨胀。
使用 Git LFS 防止体积膨胀,并通过 snapshotPathTemplate 自定义目录结构:
# .gitattributes
*.png filter=lfs diff=lfs merge=lfs -text
// playwright.config.ts
export default defineConfig({
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}-{projectName}{ext}',
});
由于基线带有平台标签,请在 CI 使用的同一 Docker 镜像中生成基线,确保其始终匹配 CI 渲染环境 — 切勿提交在开发者笔记本电脑上截取的基线。有关 Docker CI 任务(mcr.microsoft.com/playwright:v1.60.0-noble,需与 @playwright/test 版本匹配),请参见 references/recipes.md。
审核与批准工作流
- CI 检测到视觉差异后,将预期图像、实际图像和差异图像作为产物上传。
- PR 审核者在
show-report(或托管工具的界面)中检查差异。 - 有意的变更:更新基线(
--update-snapshots)并重新提交。 - 非预期的回归:修复代码并重新运行测试。
反模式
1. 未屏蔽内容的整页截图
截取整页时不屏蔽动态内容(时间戳、头像、实时数据),会导致每次运行都产生差异。团队会逐渐不再信任视觉测试。请始终屏蔽动态区域并冻结依赖时间的内容。
2. CI 中不存储产物
运行视觉测试却不上传截图产物,意味着失败时没有预期图像、实际图像或差异图像可供检查,迫使你在本地复现,而本地渲染结果可能不同。请始终将截图、差异图像和报告作为 CI 产物上传。
3. 更新基线时没有审核流程
运行 --update-snapshots 并直接提交而不审核变更,会将回归问题固化到基线中,使其无法被发现。每次更新都应审核更新前后的图像,而不仅仅是文件差异。
4. 对不稳定组件进行视觉测试
对按设计会变化的组件(A/B 测试、个性化内容、频繁轮播的横幅)进行视觉测试,会因有意的变更而不断失败。请将其排除或模拟其内容。
5. 对整页设置像素级精确阈值
在整页截图上使用 maxDiffPixels: 0,会将浏览器、操作系统或字体更新造成的亚像素渲染差异判为失败。整页请使用 maxDiffPixelRatio: 0.005;零容差仅用于小型关键组件(徽标、图标)。
6. 渲染环境不一致
在各种开发者机器上运行视觉测试,并期望基线保持一致——不同平台的字体渲染、抗锯齿和缩放各不相同。请在统一的 CI Docker 环境中运行测试并在那里生成基线。
7. 未处理动画
未禁用动画会在过渡进行到一半时捕获画面,从而产生随机差异。请使用 animations: 'disabled'、通过 stylePath 将动画时长设为零,或在捕获前调用 getAnimations().finish()。
验证
先从最小范围开始,在宣布完成前确认基线能生成且比较器能运行:
npx playwright test --grep @visual # baselines generate (first run) / compare (later)
npx playwright test --grep @visual --update-snapshots # regenerate intentionally
npx playwright show-report # diff artifacts (expected/actual/diff) render
git check-attr filter -- "e2e/**/*.png" # prints "filter: lfs" if LFS is wired
首次运行正常并写入 *-snapshots/*.png 文件,且第二次运行通过比较,便可确认整个流水线正常运行。
完成标准
- 在统一的 CI Docker 环境中(而非本地)捕获并提交基线截图。
playwright.config.ts设置全局maxDiffPixelRatio,并且至少有一个图标/Logo 测试将其覆盖为maxDiffPixels: 0。- 在捕获前通过
mask、stylePath或时钟/路由模拟来屏蔽或冻结动态内容(时间戳、头像、实时 API 数据)。 - 当视觉差异超过配置的阈值时,CI 流水线会阻止合并。
- 通过 Git LFS(
.gitattributes中包含*.png filter=lfs)或托管在仓库之外的工具来管理基线,避免仓库膨胀。 - 明确审核工作流:由谁审核差异、如何更新有意变更的基线,以及 PR 审核者如何确认基线图片。
参考文件(位于 references/ 中)
- recipes.md —— 冻结数据捕获(时钟 + 路由 + 字体中止)、
stylePath遮罩、组件状态截图、响应式视口循环、Chromatic/Percy/Argos 代码片段、不使用 Chromatic 的 Storybook,以及 Docker CI 任务。
相关技能
- playwright-automation —— 基于 Playwright 的视觉测试基础;页面对象模型、夹具和测试结构同样适用。
- ci-cd-integration —— 用于运行视觉测试、上传制品和配置审核工作流的流水线配置。
- cross-browser-testing —— 适用于目标是构建跨浏览器渲染矩阵,而非比较单次渲染的基线差异的场景;视口/项目配置也有重叠。
- selector-drift-recovery —— 当重新设计导致多个基线同时失效,且你需要批量重新生成而非逐个测试视觉差异时使用。
- qa-project-context —— 记录哪些页面在视觉上至关重要,以及存在哪些动态内容。