mcpField
V
visual-testing

visual-testing

github.com/petrkindlmann测试与质量2221.2k 次安装6 天前更新
代码仓库

关于 visual-testing

visual-testing 是介绍视觉回归测试与基线管理的技能,面向需要发现功能测试无法覆盖的界面变化的团队。内容涵盖 Playwright 截图比较、专用视觉测试工具、动态内容处理及基线审查流程。

主要功能

  1. 01使用 Playwright 比较截图基线
  2. 02配置像素差异阈值
  3. 03遮罩或冻结动态内容
  4. 04管理并审查视觉基线
  5. 05介绍 Chromatic、Percy 与 Argos CI

适用场景

  1. 01在 CI 中检测界面视觉回归
  2. 02为动态页面减少截图误报
  3. 03评审设计变更并更新基线
  4. 04管理存储在 Git 中的截图基线

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/petrkindlmann/qa-skills --skill visual-testing

安装后在 Claude Code 中输入/visual-testing确认已加载。

附带文件references/recipes.md

详细介绍

捕获功能测试遗漏的视觉回归。即使按钮的高度只有 2px,但功能完全正常,也能通过 `toBeVisible()` 和 `click()`——视觉测试会将截图与已批准的基线进行比较,并标记像素级差异。本技能涵盖 Playwright 内置的 `toHaveScreenshot`、专用工具(Chromatic、Percy、Argos CI),以及围绕这些工具的基线管理工作流。

快速导航

情况前往
只需要在 CI 中进行像素差异比较,不需要审核 SaaSPlaywright 视觉比较(下文)
仓库中有 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

基线更新工作流:

  1. 实现设计变更。
  2. 运行视觉测试——测试会失败并显示预期差异。
  3. 在 show-report 中审核每个差异:该变更是否符合预期?
  4. 更新基线:npx playwright test --update-snapshots。
  5. 提交更新后的基线,并在提交信息中注明相关设计变更。
  6. 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。

审核与批准工作流
  1. CI 检测到视觉差异后,将预期图像、实际图像和差异图像作为产物上传。
  2. PR 审核者在 show-report(或托管工具的界面)中检查差异。
  3. 有意的变更:更新基线(--update-snapshots)并重新提交。
  4. 非预期的回归:修复代码并重新运行测试。

反模式

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 —— 记录哪些页面在视觉上至关重要,以及存在哪些动态内容。

评论

支持 Markdown