visual-regression-testing について
ネイティブ Canvas 描画とシリアライズされた foreignObject 描画のピクセル一致性を調べるテストスキルです。開発者が CSS の回帰テストを作成し、プレビューの差異を調査できます。
主な機能
- 01Canvas 間の直接比較
- 02ベースライン PNG スナップショット検査
- 03ピクセル差分しきい値の設定
- 04最小限の CSS 再現テスト
- 05ネイティブ API の利用可否確認
活用シーン
- 01CSS 機能の描画一致性の確認
- 02プレビューサムネイルの差異調査
- 03シリアライザー変更の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ editframe/ skills --skill visual-regression-testingインストール後、Claude Code で/visual-regression-testingを入力して読み込まれたか確認します。
詳細
ビジュアルリグレッションテスト
2 つのレンダリングパスで、同一のピクセルが生成される必要があります。ネイティブキャンバスパス(drawElementImage を介した renderToImageNative)は、ライブ DOM を Blink で直接レンダリングするため、基準となる結果です。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の変更後に、新たなリグレッションがないことを確認するとき