研发协作
当功能测试通过但 UI 仍可能出现像素级回归时,用…
适用场景:需要捕获截图、比较像素差异、管理 baseline、遮蔽动态内容,并在 CI 中阻断视觉回归时。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
当功能测试通过但 UI 仍可能出现像素级特定
解决思路
- 先检查 `.agents/qa-project-context.md`;如果存在,使用其中信息并跳过已被回答的问题。
- 选择方案:仅在 CI 中需要像素差异且没有审核 SaaS 时,用 Playwright Visual Comparisons;有 Storybook 时用 Chromatic,或按 recipes.md 的 Storybook without Chromatic 用 Playwright 驱动;需要托管审核/批准 UI 和 AI diff triage 时用专用视觉测试工具;截图因时间戳/头像/广告 flake 时用遮蔽与冻结动态内容;baseline 使 git 历史膨胀时用 Baseline Management → Git LFS。
- 判断 Playwright 内置还是专用工具:`toHaveScreenshot` 免费,baseline 存 repo,git/LFS 成本由你承担;Chromatic、Percy、Argos 把 artifacts 存 repo 外,并提供审核工作流、browser farms、历史追踪和 AI diff triage;按团队规模、审核需求和 artifacts 存放位置选择。
- 如果项目有 Storybook,优先考虑 Chromatic,因为每个 story 变成视觉测试;也可用 Playwright 驱动 stories 而不付费;如果没有 Storybook,用 Playwright 内置或 Percy。
- 确认 CI 平台和 artifact 预算:视觉测试生成大截图/diff artifacts;内置 baselines 在 git 中,可能造成 repo 膨胀,可用 LFS;托管工具保留在 repo 外但需订阅费;扩大规模前确认 CI 存储和时间。
- 确定范围:用全页截图捕获布局问题,但它对无关变化敏感;或用组件级截图,更稳定、更聚焦。
- 确定哪些页面/组件视觉关键:不是所有东西都需要,聚焦用户可见页面、营销页、设计系统组件和复杂布局。
- 确定 viewport:desktop、tablet、mobile;基于 analytics 预先定义 viewport 矩阵,不要用所有可能宽度。
- 识别运行间变化内容:日期、时间戳、用户生成内容、analytics IDs、随机内容、广告、头像,必须 mask 或 freeze。
- 处理动画/转场:在截图前禁用或等待完成,避免假阳性。
- 处理外部资源:字体、CDN 图片、第三方 widget 可能在运行间变化。
- 坚持视觉测试补充功能测试:功能测试断言行为,视觉测试断言外观;两者都需要,不互相替代。
- 尽早投入 baseline 审核工作流:截图容易,管理 baseline(有意设计变更时更新、审查 diff、团队协调批准)才是难点。
- 积极遮蔽或冻结动态内容:任何运行间变化的内容都会造成假阳性;假阳性率 10% 的套件一个月内会被忽略;托管工具可自动过滤大量假阳性,但 mask/freeze 仍是第一道防线。
- 迭代调优阈值:从严格(零容忍)开始,观察假阳性,按组件放宽,并记录每个阈值选择原因。
- 把截图当 artifacts 而非测试结果:存储、版本化、可审核;始终上传 expected/actual/diff 图片作为 CI artifacts。
- 用 Playwright 内置 `toHaveScreenshot`/`toMatchSnapshot` 做视觉回归:import `test, expect`,`page.goto('/dashboard')`,用 locator 等待数据加载,例如 `expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()` 和 `expect(page.getByTestId('chart-container')).toBeVisible()`,不要 `waitForTimeout`,然后 `await expect(page).toHaveScreenshot('dashboard.png', { animations: 'disabled' })`;首次运行创建 baseline,后续比较,像素差异超阈值则失败。
- 配置截图选项:`maxDiffPixels: 100` 或 `maxDiffPixelRatio: 0.01`,`threshold: 0.2`,`animations: 'disabled'`,`caret: 'hide'`,`stylePath: './screenshot.css'`,`timeout: 15000`;默认 comparator 是 `pixelmatch`(YIQ 色彩空间);使用 `comparator: 'pixelmatch'` 加 `threshold`/`maxDiffPixel*`;不要设置 `mode:`,perceptual SSIM 是内部/实验 API,不是稳定配置。
- 按场景选阈值:`maxDiffPixels: 0` 用于像素级完美组件(icons、logos、design-system atoms);`maxDiffPixels: 50-100` 用于全页布局抗锯齿略有变化;`maxDiffPixelRatio: 0.01` 用于全页截图绝对像素数随 viewport 变化;`threshold: 0.2` 用于跨浏览器颜色渲染略有不同。
- 在 `playwright.config.ts` 设置视觉配置:`expect.toHaveScreenshot` 用 `comparator: 'pixelmatch'`、`maxDiffPixelRatio: 0.005`、`animations: 'disabled'`、`caret: 'hide'`;`expect.toMatchSnapshot` 用 `maxDiffPixelRatio: 0.005`;projects 定义 `visual-desktop`(`viewport: { width: 1280, height: 720 }`, `colorScheme: 'light'`, `testMatch: /.*visual.*\.spec\.ts/`)和 `visual-mobile`(`viewport: { width: 375, height: 667 }`, `colorScheme: 'light'`, `isMobile: true`, 同 testMatch)。
- 遮蔽动态元素:在 `toHaveScreenshot` 中传 `mask: [page.getByTestId('user-avatar'), page.getByTestId('last-login-time'), page.getByTestId('activity-feed')]`,并用 `maskColor: '#FF00FF'` 便于调试。
- 对光标、动画和动态 chrome 优先用 `stylePath`(capture 时注入样式表),而不是逐元素 mask;它声明式且能适应 DOM 变化。
- 消除时间戳和实时数据:用 `page.clock.setFixedTime` 固定时钟(截图确定性最佳;`page.clock.install` 让时钟从 seed 走动),并用 `page.route` + `route.fulfill` stub API。
- 完整冻结数据配方见 `references/recipes.md`:clock + route + font-abort + `getAnimations().finish()`;还有 `stylePath` 示例和组件状态截图。
- 更新 baseline:全部更新用 `npx playwright test --update-snapshots`;指定测试用 `npx playwright test visual-dashboard --update-snapshots`;提交前用 `git diff --stat` 查看哪些 baseline 文件变了,并用 `npx playwright show-report` 视觉审查 expected/actual/diff。
- 遵循 baseline 更新流程:设计变更实现;运行视觉测试,失败并出现预期 diff;在 `show-report` 审查每个 diff 是否有意;用 `npx playwright test --update-snapshots` 更新;提交更新后的 baseline 并在信息中引用设计变更;PR 审查者验证 baseline 图片正确,而不只是文件 diff。
- 组件级与响应式测试:截图组件,例如 `getByRole('table')`,而非全页,更稳定聚焦;通过按测试 stub API 驱动 empty/error/normal 状态;响应式覆盖用循环 `VISUAL_VIEWPORTS` 数组(mobile/tablet/desktop 带 `isMobile` 标志)或每个 viewport 定义一个 Playwright project;见 recipes.md。
- 需要托管 review/approval UI、跨浏览器渲染 farm、历史追踪或 AI diff triage,且接受 artifacts 存 repo 外并订阅时,选用专用工具。
- Chromatic:项目使用 Storybook 时最佳;每个 story = 一个视觉测试;提供 review/approval UI、跨浏览器、TurboSnap + AI triage。
- Percy:没有 Storybook、需要多浏览器时最佳;通过 SDK 支持任意框架;提供 multi-width captures、CSS overrides、AI Visual Review Agent(自动过滤约 40% 假阳性)、BrowserStack Test Observability。
- Argos CI:偏好开源、预算敏感时最佳;Playwright reporter;可自托管;慷慨云免费 tier。
- 利用 AI diff triage:Percy Visual Review Agent 和 Chromatic AI triage 自动分类大量 diff 为噪音或真实变更,减少审核疲劳;这是付费托管工具优于内置 baseline 的最强理由,但它减少而非消除 mask/freeze 动态内容的需要。
- 避免 Lost Pixel:repo 已 2026-04-22 archived(只读);改用 Argos、Chromatic 或 Playwright 内置 `toHaveScreenshot`。
- 查看 `references/recipes.md` 获取 Chromatic GitHub Action、Percy `percySnapshot`、Argos `argosScreenshot` 和 Storybook-without-Chromatic 片段。
- Playwright baselines 与测试文件并存,并按平台 tag,因为不同 OS 渲染不同;典型目录:`e2e/tests/visual/dashboard.visual.spec.ts` 和 `dashboard.visual.spec.ts-snapshots/dashboard-chromium-linux.png`、`dashboard-chromium-darwin.png`、`dashboard-firefox-linux.png`。
- 权衡 git 存 baseline:优点是随代码版本化、PR 审查、离线可用;缺点是 repo 变大,大 PNG 膨胀 git 历史。
- 用 Git LFS 防止膨胀:`.gitattributes` 写 `*.png filter=lfs diff=lfs merge=lfs -text`;用 `snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}-{projectName}{ext}'` 自定义布局。
- 因为 baselines 平台 tag,在 CI 使用的同一 Docker image 中生成,确保匹配 CI 渲染环境;绝不提交开发笔记本上捕获的 baseline;见 recipes.md 的 Docker CI job(`mcr.microsoft.com/playwright:v1.60.0-noble`,与 `@playwright/test` 版本匹配)。
- 定义 review/approval 工作流:CI 检测视觉 diff 并上传 expected/actual/diff artifacts;PR reviewer 在 `show-report` 或托管工具 UI 检查;有意变更更新 baseline 并重新提交;非预期回归修代码重跑。
- 避免全页截图不做 mask:会每次运行都产生 diff,团队会失去信任;始终 mask 动态区域并 freeze 时间相关内容。
- 避免 CI 不存 artifacts:失败时没有 expected/actual/diff 可检查;始终上传截图、diff 和报告作为 CI artifacts。
- 避免 baseline 更新无审核流程:直接 `--update-snapshots` 并提交会把回归固化;每次更新都审查 before/after 图片,而不只是文件 diff。
- 避免对不稳定组件做视觉测试:A/B 测试、个性化内容、频繁轮换 banner 会因有意变更不断失败;排除它们或 stub 内容。
- 避免在全页上用像素级完美阈值:`maxDiffPixels: 0` 会把浏览器/OS/字体更新导致的亚像素渲染差异当失败;全页用 `maxDiffPixelRatio: 0.005`,零容忍只留给小型关键组件(logos、icons)。
- 避免没有一致渲染环境:不同开发者机器字体渲染、抗锯齿和缩放不同;在一致 CI Docker 环境运行并生成 baseline。
- 避免跳过动画处理:不禁用动画会截到过渡中间帧,产生随机 diff;用 `animations: 'disabled'`、`stylePath` 将 duration 置零,或截图前 `getAnimations().finish()`。
- 用最小步骤验证:`npx playwright test --grep @visual` 生成(首次)或比较(后续)baselines;`npx playwright test --grep @visual --update-snapshots` 有意重新生成;`npx playwright show-report` 确认 diff artifacts(expected/actual/diff)可渲染;`git check-attr filter -- "e2e/**/*.png"` 若 LFS 接好会打印 `filter: lfs`。
- 干净首跑写出 `*-snapshots/*.png`,第二次跑通过它们,即确认 pipeline 端到端工作。
- 完成条件:baseline 截图在一致 CI Docker 环境(非本地)捕获并提交。
- `playwright.config.ts` 设置全局 `maxDiffPixelRatio`,并且至少一个 icon/logo 测试覆盖为 `maxDiffPixels: 0`。
- 动态内容在截图前通过 `mask`、`stylePath` 或 clock/route stubbing 被 mask 或 freeze(时间戳、头像、live API data)。
- CI pipeline 在视觉差异超过配置阈值时阻止 merge。
- baselines 通过 Git LFS(`.gitattributes` 有 `*.png filter=lfs`)或 repo 外托管工具跟踪,避免 repo 膨胀。
- 定义 review 工作流:谁审查 diffs、有意变更如何更新 baseline、PR reviewer 签署 baseline 图片。
- 参考 `references/recipes.md`:frozen-data capture(clock + route + font-abort)、`stylePath` masking、component-state screenshots、responsive-viewport loop、Chromatic/Percy/Argos snippets、Storybook-without-Chromatic、Docker CI job。
- 相关技能:`playwright-automation` 是 Playwright 视觉测试基础;`ci-cd-integration` 用于 pipeline 配置、artifact 上传和审核工作流;`cross-browser-testing` 用于跨浏览器渲染矩阵;`selector-drift-recovery` 用于 redesign 后 bulk baseline regeneration;`qa-project-context` 用于记录哪些页面视觉关键和存在哪些动态内容。
适用边界 · 注意事项
- 不适合在重新设计后大量 baseline 被破坏时做批量重新生成——应使用 selector-drift-recovery。
- 不适合做跨浏览器渲染矩阵——应使用 cross-browser-testing。
- 不适合通用 Playwright 测试结构——应使用 playwright-automation。