fix: fall back to screenshot mode when any CDP call times out during calibration (#567)

## Summary

- **Root cause**: `shouldFallbackToScreenshotAfterCalibrationError` only matched `HeadlessExperimental.beginFrame` errors. When a composition with many heavy videos (e.g. 7 videos with sparse keyframes) caused Chrome to be unresponsive in BeginFrame mode during calibration, a `Runtime.callFunctionOn timed out` or `Runtime.evaluate timed out` error was treated as an opaque failure — not a BeginFrame-mode signal. The render kept BeginFrame mode, spawned 3 workers with `captureCostMultiplier=8`, and all 3 workers also timed out initialising their sessions (0 frames captured, render fails).
- **Fix**: Add `Runtime.callFunctionOn timed out` and `Runtime.evaluate timed out` to the screenshot-fallback pattern. Any CDP call timing out during the short-timeout calibration probe now routes the render into single-worker screenshot mode — the safe fallback already used for explicit BeginFrame timeouts.
- **Result**: Compositions that overwhelm BeginFrame mode (reported in #566: 7 videos, 8 audios, 330-second render) now fall back cleanly and complete instead of failing with 0 frames.

## Test plan

- [x] New unit test: `falls back to screenshot mode after Runtime.callFunctionOn timeout during calibration` — asserts both `Runtime.callFunctionOn timed out` and `Runtime.evaluate timed out` return `true`
- [x] All existing `capture calibration safeguards` unit tests still pass
- [x] Pre-commit hooks (lint, format, typecheck) pass

Fixes #566
This commit is contained in:
Miguel Ángel
2026-04-29 20:03:40 +02:00
committed by GitHub
parent 403c00eeae
commit bcd7230557
2 changed files with 18 additions and 1 deletions
@@ -424,6 +424,23 @@ describe("capture calibration safeguards", () => {
).toBe(true);
expect(shouldFallbackToScreenshotAfterCalibrationError(new Error("ffmpeg exited"))).toBe(false);
});
it("falls back to screenshot mode after Runtime.callFunctionOn timeout during calibration", () => {
expect(
shouldFallbackToScreenshotAfterCalibrationError(
new Error(
"Runtime.callFunctionOn timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.",
),
),
).toBe(true);
expect(
shouldFallbackToScreenshotAfterCalibrationError(
new Error(
"Runtime.evaluate timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.",
),
),
).toBe(true);
});
});
describe("adaptive missing-frame retry helpers", () => {
@@ -890,7 +890,7 @@ export function isRecoverableParallelCaptureError(error: unknown): boolean {
export function shouldFallbackToScreenshotAfterCalibrationError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return /HeadlessExperimental\.beginFrame timed out|beginFrame probe timeout|Another frame is pending|Frame still pending|Protocol error.*HeadlessExperimental\.beginFrame/i.test(
return /HeadlessExperimental\.beginFrame timed out|beginFrame probe timeout|Another frame is pending|Frame still pending|Protocol error.*HeadlessExperimental\.beginFrame|Runtime\.callFunctionOn timed out|Runtime\.evaluate timed out/i.test(
message,
);
}