diff --git a/packages/cli/src/utils/checkBrowser.test.ts b/packages/cli/src/utils/checkBrowser.test.ts
index 3a2a3f0d5..d7a138bf6 100644
--- a/packages/cli/src/utils/checkBrowser.test.ts
+++ b/packages/cli/src/utils/checkBrowser.test.ts
@@ -213,6 +213,61 @@ it("round-trips the browser script's raw contrast candidates back into finish",
}
});
+it("captures check overview snapshots after contrast restores hidden text", async () => {
+ vi.spyOn(Date, "now").mockReturnValue(100);
+ mountCanvasFixture(`
Readable copy
`);
+ const root = document.querySelector("[data-composition-id]");
+ const headline = document.querySelector("#headline");
+ if (!root || !headline) throw new Error("Contrast fixture failed to mount");
+ vi.spyOn(root, "getBoundingClientRect").mockReturnValue(new DOMRect(0, 0, 640, 360));
+ vi.spyOn(headline, "getBoundingClientRect").mockReturnValue(new DOMRect(50, 50, 300, 40));
+ vi.spyOn(window, "getComputedStyle").mockImplementation(
+ () =>
+ ({
+ display: "block",
+ visibility: "visible",
+ opacity: "1",
+ color: "rgb(255,255,255)",
+ fill: "",
+ clipPath: "none",
+ fontSize: "32px",
+ fontWeight: "700",
+ }) as unknown as CSSStyleDeclaration,
+ );
+
+ const page = fakePage();
+ const injectScript = page.addScriptTag;
+ page.addScriptTag = vi.fn(async (arg: { content: string }) => {
+ await injectScript(arg);
+ const w = window as unknown as {
+ __contrastAuditFinish?: (...args: unknown[]) => Promise;
+ __contrastAuditRestoreIfPending?: () => void;
+ };
+ if (w.__contrastAuditFinish) {
+ w.__contrastAuditFinish = async () => {
+ w.__contrastAuditRestoreIfPending?.();
+ return [];
+ };
+ }
+ });
+ page.screenshot = vi.fn(async () =>
+ headline.style.getPropertyValue("color") === "transparent"
+ ? "text-hidden-base64"
+ : "text-visible-base64",
+ );
+ installSessionMock(page);
+
+ const result = await runBrowserCheck(
+ PROJECT,
+ { ...DEFAULT_CHECK_OPTIONS, samples: 1, contrast: true, snapshots: true },
+ { kind: "none" },
+ runAuditGrid,
+ );
+
+ expect(page.screenshot).toHaveBeenCalledTimes(2);
+ expect(result.screenshots[0]?.pngBase64).toBe("text-visible-base64");
+});
+
it("carries validate's clip-duration audit into the runtime findings", async () => {
vi.spyOn(Date, "now").mockReturnValue(100);
mountCanvasFixture();
diff --git a/packages/cli/src/utils/checkBrowser.ts b/packages/cli/src/utils/checkBrowser.ts
index 87923e026..3f2abdc6b 100644
--- a/packages/cli/src/utils/checkBrowser.ts
+++ b/packages/cli/src/utils/checkBrowser.ts
@@ -520,6 +520,12 @@ async function collectContrast(
);
const finished = raw.flatMap(parseFinishedContrast);
const entries = joinContrastEntries(finished, prepared);
+ // __contrastAuditFinish restores the text paint hidden by prepare. The
+ // measurement screenshot intentionally has glyphs removed so contrast
+ // can sample the pixels behind them; never persist that image as visual
+ // QA evidence. Capture the restored page for the overview instead.
+ const overviewShot = await page.screenshot({ encoding: "base64", type: "png" });
+ if (typeof overviewShot !== "string") throw new Error("Overview screenshot was not base64");
// Contrast failures are only known once measurement above completes, so
// they're appended to the layout-derived annotations passed in by the
// pipeline rather than being requested up front.
@@ -527,7 +533,7 @@ async function collectContrast(
...layoutAnnotations,
...contrastFailureAnnotations(entries, layoutAnnotations.length),
];
- const pngBase64 = await captureOverviewShot(page, annotations, measurementShot);
+ const pngBase64 = await captureOverviewShot(page, annotations, overviewShot);
return { entries, pngBase64 };
} finally {
await page