fix(cli): snapshot warns when self-closed audio or video clips have swallowed the page (#6915)

This commit is contained in:
Miguel Ángel
2026-11-01 06:16:39 +00:00
committed by GitHub
parent f08f07ab64
commit 8c22ddba0f
2 changed files with 51 additions and 0 deletions
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { selfClosedMediaWarning } from "./snapshot.js";
const page = (media: string) => `<!doctype html><html><body>
<div id="root" data-composition-id="main" data-start="0" data-duration="10" data-width="1280" data-height="720">
${media}
<h1 id="title">Hello</h1></div></body></html>`;
describe("selfClosedMediaWarning", () => {
it("names the self-closed clips and what they do to the still", async () => {
const clips = Array.from(
{ length: 3 },
(_, i) => `<audio id="sfx${i}" src="s${i}.wav" data-start="${i}" data-duration="1" />`,
).join("\n");
const warning = await selfClosedMediaWarning(page(clips));
expect(warning).toContain("3 self-closed <audio>/<video> tags");
expect(warning).toContain("#sfx0");
expect(warning).toContain("stills come out blank");
});
it("stays quiet when every clip has a closing tag, even with fallback content", async () => {
const closed = `<audio id="a" src="a.wav" data-start="0" data-duration="1"></audio>
<video id="v" src="v.mp4" data-start="0" data-duration="2"><p>Your browser cannot play this.</p></video>`;
expect(await selfClosedMediaWarning(page(closed))).toBeNull();
});
});
+22
View File
@@ -938,6 +938,14 @@ export async function captureSnapshots(
// Localize fonts (embed remote @font-face as data URIs, matching the render
// path) so snapshots render the real font instead of a fallback sans.
const html = await bundleWithLocalizedFonts(projectDir);
const entry = join(projectDir, "index.html");
const swallowed = existsSync(entry)
? await selfClosedMediaWarning(readFileSync(entry, "utf8"))
: null;
if (swallowed) {
warnings.push(swallowed);
console.warn(` ${c.warn("⚠")} ${swallowed}`);
}
const server = await serveCapturePage(projectDir, html, opts.autoProxy);
const savedPaths: string[] = [];
@@ -1506,6 +1514,20 @@ function warnUnsettledImages(images: UnsettledImages, time: number, warnings: st
}
}
/** What the project's own lint says about self-closed clips, which hide the rest of the page and blank every still. */
export async function selfClosedMediaWarning(source: string): Promise<string | null> {
const { lintHyperframeHtml } = await import("@hyperframes/lint");
const found = (await lintHyperframeHtml(source)).findings.filter(
(finding) => finding.code === "self_closing_media_tag",
);
if (found.length === 0) return null;
const ids = found.flatMap((finding) => (finding.elementId ? [`#${finding.elementId}`] : []));
return (
`index.html has ${found.length} self-closed <audio>/<video> tags${ids.length ? ` (${ids.slice(0, 3).join(", ")})` : ""}: ` +
"HTML leaves them open, so everything after the first one is hidden and stills come out blank. Write <audio ...></audio>."
);
}
function warnSnapshotsNotRecorded(error: unknown): undefined {
console.warn(` ${c.warn("⚠")} Snapshots were not recorded: ${normalizeErrorMessage(error)}`);
return undefined;