mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 19:06:04 +00:00
Four independent capture-infra hardening changes for the P2-5 failure bucket (~15K err / ~7K users): - protocolTimeout auto-scales by device-scaled output area (applied before probe launch, since it's immutable post ppt.launch()). - Single bounded transient retry (MAX_TRANSIENT_CAPTURE_RETRIES=1) on Target closed / Page crashed in the parallel disk-capture path; abort short-circuits before retry. - Narrow OOM classification (Set maximum size exceeded etc., disjoint from transient) → actionable guidance naming output dims. - StreamingEncoder.getExitError() threads FFmpeg's real exit reason into frame-0 encoder-death errors. Render-reliability workstream P2-5. Success measured on PostHog dashboard 1783183. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
4.1 KiB
TypeScript
92 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { isMemoryExhaustionError, isTransientBrowserError } from "./frameCapture.js";
|
|
|
|
describe("isTransientBrowserError", () => {
|
|
it.each([
|
|
"Navigating frame was detached",
|
|
"Target closed",
|
|
"Session closed. Most likely the page has been closed.",
|
|
"Protocol error (Runtime.callFunctionOn): Target closed",
|
|
"Navigation failed because browser has disconnected",
|
|
"browser has disconnected",
|
|
"Page crashed!",
|
|
"Execution context was destroyed",
|
|
"Cannot find context with specified id",
|
|
"Failed to launch the browser process! TROUBLESHOOTING: https://pptr.dev/troubleshooting",
|
|
"connect ECONNREFUSED 127.0.0.1:9222",
|
|
"Navigation timeout of 60000 ms exceeded",
|
|
// pollHfReady timed out before window.__renderReady flipped true — the
|
|
// classic symptom of a slow/contended host (e.g. several renders running
|
|
// concurrently); a fresh browser session on retry usually clears it.
|
|
"[FrameCapture] Composition has zero duration.\n Runtime ready: false, __player: true, __hf.seek: true, GSAP timeline: true, data-duration: 53.3s",
|
|
])("returns true for transient error: %s", (message) => {
|
|
expect(isTransientBrowserError(new Error(message))).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
"net::ERR_NAME_NOT_RESOLVED",
|
|
"FONT_FETCH_FAILED: Inter",
|
|
"Composition duration is 0",
|
|
"SYSTEM_FONT_USED: -apple-system",
|
|
"",
|
|
// The runtime finished initializing (renderReady: true) and still reports
|
|
// zero duration — a genuine authoring bug (no timeline, no data-duration),
|
|
// not a transient host hiccup. Must keep fast-failing without a retry.
|
|
"[FrameCapture] Composition has zero duration.\n Runtime ready: true, __player: true, __hf.seek: true, GSAP timeline: false, data-duration: not set",
|
|
])("returns false for non-transient error: %s", (message) => {
|
|
expect(isTransientBrowserError(new Error(message))).toBe(false);
|
|
});
|
|
|
|
it("handles non-Error values", () => {
|
|
expect(isTransientBrowserError("Navigating frame was detached")).toBe(true);
|
|
expect(isTransientBrowserError("some other string")).toBe(false);
|
|
expect(isTransientBrowserError(null)).toBe(false);
|
|
expect(isTransientBrowserError(undefined)).toBe(false);
|
|
expect(isTransientBrowserError(42)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("isMemoryExhaustionError", () => {
|
|
it.each([
|
|
"Set maximum size exceeded",
|
|
"Map maximum size exceeded",
|
|
"Invalid array length",
|
|
"Invalid string length",
|
|
"Array buffer allocation failed",
|
|
"Cannot create a string longer than 0x1fffffe8 characters",
|
|
"FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory",
|
|
"JavaScript heap out of memory",
|
|
])("returns true for memory-exhaustion error: %s", (message) => {
|
|
expect(isMemoryExhaustionError(new Error(message))).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
"Target closed",
|
|
"Runtime.callFunctionOn timed out",
|
|
"net::ERR_NAME_NOT_RESOLVED",
|
|
"Composition duration is 0",
|
|
"",
|
|
// Deliberately NOT matched — a bare "out of memory" substring appears in
|
|
// benign WebGL/GPU console noise; only the specific V8/Node allocation
|
|
// signatures (and "JavaScript heap out of memory") count.
|
|
"WebGL: CONTEXT_LOST_WEBGL loseContext: context out of memory",
|
|
"GL_OUT_OF_MEMORY: out of memory",
|
|
])("returns false for non-memory error: %s", (message) => {
|
|
expect(isMemoryExhaustionError(new Error(message))).toBe(false);
|
|
});
|
|
|
|
it("handles non-Error values", () => {
|
|
expect(isMemoryExhaustionError("Set maximum size exceeded")).toBe(true);
|
|
expect(isMemoryExhaustionError("some other string")).toBe(false);
|
|
expect(isMemoryExhaustionError(null)).toBe(false);
|
|
expect(isMemoryExhaustionError(undefined)).toBe(false);
|
|
});
|
|
|
|
// A memory-exhaustion error is a resource ceiling, not a flaky-tab hiccup —
|
|
// it must NOT be classified as transient (a retry re-hits the same wall).
|
|
it("is disjoint from transient classification", () => {
|
|
expect(isTransientBrowserError(new Error("Set maximum size exceeded"))).toBe(false);
|
|
expect(isMemoryExhaustionError(new Error("Target closed"))).toBe(false);
|
|
});
|
|
});
|