mcpField
V
visual-regression-testing

visual-regression-testing

github.com/editframe测试与质量1970 次安装2 周前更新
代码仓库

关于 visual-regression-testing

用于检查原生 Canvas 渲染与序列化 foreignObject 渲染之间像素一致性的测试技能,帮助开发者编写针对性 CSS 回归测试并排查预览差异。

主要功能

  1. 01画布间直接比较
  2. 02基线 PNG 快照检查
  3. 03可配置像素差异阈值
  4. 04最小化 CSS 复现测试
  5. 05原生 API 可用性保护

适用场景

  1. 01检查 CSS 功能的渲染一致性
  2. 02排查预览缩略图差异
  3. 03验证序列化器更改

安装

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

$npx skills add https://github.com/editframe/skills --skill visual-regression-testing

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

详细介绍

视觉回归测试

两条渲染路径必须生成完全相同的像素。原生画布路径(通过 drawElementImage 调用 renderToImageNative)直接通过 Blink 渲染实时 DOM,是基准结果。foreignObject 路径(captureTimelineToDataUri)会将 DOM 序列化为 SVG <foreignObject> 中的 XHTML。两者之间的任何差异都是序列化器缺陷。

工具函数

elements/packages/elements/test/visualRegressionUtils.ts 提供了测试 API。主要函数:

// Compare two canvases directly — returns diffPercentage, no baseline file needed
compareTwoCanvases(canvas1, canvas2, testName, comparisonName, options)
expectCanvasesToMatch(canvas1, canvas2, testName, comparisonName, options)

// Compare a canvas against a stored baseline PNG
assertCanvasSnapshot(canvas, testName, snapshotName, options)
expectCanvasToMatchSnapshot(source, testName, snapshotName, options)

options.acceptableDiffPercentage(默认值为 1.0)用于控制测试通过或失败。进行严格的对齐检查时,可设为 0 到 0.5。

标准比较模式

import { captureTimelineToDataUri } from "./rendering/serializeTimelineDirect.js";
import { loadImageFromDataUri } from "./rendering/loadImage.js";
import { renderToImageNative } from "./rendering/renderToImageNative.js";
import { isNativeCanvasApiAvailable } from "./previewSettings.js";
import { expectCanvasesToMatch } from "../../test/visualRegressionUtils.js";

async function captureForComparison(tg: EFTimegroup, W: number, H: number) {
  // foreignObject path
  const dataUri = await captureTimelineToDataUri(tg, W, H, { canvasScale: 1, timeMs: 0 });
  const img = await loadImageFromDataUri(dataUri);
  const fc = document.createElement("canvas");
  fc.width = W; fc.height = H;
  fc.getContext("2d")!.drawImage(img, 0, 0);

  // native path (ground truth — Blink renders the live DOM)
  const nc = await renderToImageNative(tg, W, H, { skipDprScaling: true });

  return { foreignCanvas: fc, nativeCanvas: nc };
}

原生路径测试必须始终使用 isNativeCanvasApiAvailable() 进行保护,因为 WICG drawElementImage API 仅适用于 Chromium。

编写最小复现测试

最小复现应隔离单个 CSS 属性。测试应当在修复前失败,修复后通过。

it("text-shadow from inline style", async () => {
  if (!isNativeCanvasApiAvailable()) return;
  const tg = document.createElement("ef-timegroup") as EFTimegroup;
  tg.style.cssText = "width:400px;height:200px;background:#000;position:relative;";
  const el = document.createElement("div");
  el.style.cssText = "position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);"
    + "font-size:60px;font-weight:900;color:white;text-shadow:0 0 40px red;";
  el.textContent = "GLOW";
  tg.appendChild(el);
  document.body.appendChild(tg);
  await tg.updateComplete;

  const { foreignCanvas, nativeCanvas } = await captureForComparison(tg, 400, 200);
  await expectCanvasesToMatch(foreignCanvas, nativeCanvas, "canvasDomParity", "text-shadow-inline", {
    acceptableDiffPercentage: 0.5,
  });
  tg.remove();
});

快照文件

差异 PNG 文件会写入 elements/test-assets/test/__snapshots__/<testName>/。打开文件即可直观检查哪些像素存在差异。

序列化器属性

foreignObject 序列化器会从 elements/packages/elements/src/preview/rendering/serializeTimelineDirect.ts 中的 SERIALIZED_STYLE_PROPERTIES 获取计算样式。如果此处缺少某个属性,那么在通过 animation:none 冻结动画时,该属性就会丢失。当某项 CSS 功能出现对齐差异时,请先检查此列表。

何时使用此技能

  • 为 CSS 功能差异编写新的对齐测试
  • 调查预览缩略图与 DOM 实际显示内容不一致的报告
  • 修改 serializeTimelineDirect.ts 后,验证是否引入了新的回归问题

评论

支持 Markdown