Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/engine/src/services/frameCapture.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, it, expect } from "vitest";
import {
classifyConsoleScriptFailure,
DrawElementVerificationError,
formatHttpErrorDiagnostic,
formatConsoleDiagnostic,
Expand All @@ -13,6 +14,27 @@ import {
shouldIgnoreRequestFailureDiagnostic,
} from "./frameCapture.js";

describe("classifyConsoleScriptFailure", () => {
it("promotes Chromium's blocked subresource-integrity diagnostic", () => {
expect(
classifyConsoleScriptFailure(
"error",
"Failed to find a valid digest in the 'integrity' attribute for resource 'http://127.0.0.1:4173/_ext/vendor/gsap.js' with computed SHA-384 integrity 'abc'. The resource has been blocked.",
),
).toBe("runtime-error:subresource-integrity");
});

it("ignores non-error and unrelated integrity messages", () => {
expect(
classifyConsoleScriptFailure(
"warning",
"Failed to find a valid digest in the 'integrity' attribute. The resource has been blocked.",
),
).toBeNull();
expect(classifyConsoleScriptFailure("error", "Integrity metadata is present.")).toBeNull();
});
});

describe("isFontResourceError", () => {
it("matches Google Fonts CSS load failures via location.url", () => {
expect(
Expand Down
23 changes: 18 additions & 5 deletions packages/engine/src/services/frameCapture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,22 @@ function recordScriptLoadFailure(session: CaptureSession, url: string): void {
}
}

export function classifyConsoleScriptFailure(type: string, text: string): string | null {
if (type !== "error") return null;
if (text.startsWith("[HyperFrames] composition script error:")) {
const detail = text.slice("[HyperFrames] composition script error:".length).trim();
const compId = detail.split(" ")[0] || "unknown";
return `runtime-error:${compId}`;
}
if (
/failed to find a valid digest in the ['"]integrity['"] attribute/i.test(text) &&
/resource has been blocked/i.test(text)
) {
return "runtime-error:subresource-integrity";
}
return null;
}

// fallow-ignore-next-line unit-size
export async function initializeSession(session: CaptureSession): Promise<void> {
const { page, serverUrl } = session;
Expand All @@ -2074,11 +2090,8 @@ export async function initializeSession(session: CaptureSession): Promise<void>
// can never arrive — same fail-fast treatment as script load failures.
// Without this, pollSubCompositionTimelines burns the full timeout and
// the render silently succeeds with a degenerate 2-frame output (#3352).
if (type === "error" && text.startsWith("[HyperFrames] composition script error:")) {
const detail = text.slice("[HyperFrames] composition script error:".length).trim();
const compId = detail.split(" ")[0] || "unknown";
recordScriptLoadFailure(session, `runtime-error:${compId}`);
}
const scriptFailure = classifyConsoleScriptFailure(type, text);
if (scriptFailure) recordScriptLoadFailure(session, scriptFailure);
});

page.on("pageerror", (err) => {
Expand Down
Loading