Loading...
Loading...
Canvas/DOM parity testing for the foreignObject serializer. Use when comparing the SVG foreignObject render path against the native Blink canvas path, writing pixel-diff tests, or hunting canvas vs DOM rendering inconsistencies.
npx skill4agent add editframe/skills visual-regression-testingrenderToImageNativedrawElementImagecaptureTimelineToDataUri<foreignObject>elements/packages/elements/test/visualRegressionUtils.ts// 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.acceptableDiffPercentage1.000.5import { 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()drawElementImageit("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();
});elements/test-assets/test/__snapshots__/<testName>/SERIALIZED_STYLE_PROPERTIESelements/packages/elements/src/preview/rendering/serializeTimelineDirect.tsanimation:noneserializeTimelineDirect.ts