From ab72b0819775137cc9ddb6ca6cab387de9feda63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 25 Jun 2026 04:07:06 +0000 Subject: [PATCH] fix(producer): retry probe navigation timeouts --- .../frameCapture-transientErrors.test.ts | 2 +- packages/engine/src/services/frameCapture.ts | 1 + .../services/render/stages/probeStage.test.ts | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/engine/src/services/frameCapture-transientErrors.test.ts b/packages/engine/src/services/frameCapture-transientErrors.test.ts index 151860fee..3d4ebdd40 100644 --- a/packages/engine/src/services/frameCapture-transientErrors.test.ts +++ b/packages/engine/src/services/frameCapture-transientErrors.test.ts @@ -14,13 +14,13 @@ describe("isTransientBrowserError", () => { "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", ])("returns true for transient error: %s", (message) => { expect(isTransientBrowserError(new Error(message))).toBe(true); }); it.each([ "net::ERR_NAME_NOT_RESOLVED", - "TimeoutError: Navigation timeout of 30000 ms exceeded", "FONT_FETCH_FAILED: Inter", "Composition duration is 0", "SYSTEM_FONT_USED: -apple-system", diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index 4599a0130..8baa527b1 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -1961,6 +1961,7 @@ const TRANSIENT_BROWSER_ERROR_PATTERNS = [ /Execution context was destroyed/i, /Cannot find context with specified id/i, /Failed to launch the browser process/i, + /Navigation timeout of \d+ ms exceeded/i, /ECONNREFUSED/i, ]; diff --git a/packages/producer/src/services/render/stages/probeStage.test.ts b/packages/producer/src/services/render/stages/probeStage.test.ts index 404df77a0..e1124756f 100644 --- a/packages/producer/src/services/render/stages/probeStage.test.ts +++ b/packages/producer/src/services/render/stages/probeStage.test.ts @@ -69,7 +69,7 @@ mock.module("@hyperframes/engine", () => ({ // live in frameCapture-transientErrors.test.ts — update both if patterns change. isTransientBrowserError: (error: unknown) => { const msg = error instanceof Error ? error.message : String(error); - return /Navigating frame was detached|Target closed|Session closed|browser has disconnected|Page crashed|Execution context was destroyed|Cannot find context with specified id|Failed to launch the browser process|ECONNREFUSED/i.test( + return /Navigating frame was detached|Target closed|Session closed|browser has disconnected|Page crashed|Execution context was destroyed|Cannot find context with specified id|Failed to launch the browser process|Navigation timeout of \d+ ms exceeded|ECONNREFUSED/i.test( msg, ); }, @@ -276,6 +276,23 @@ describe("runProbeStage — transient browser error retry (#1687)", () => { expect(result.probeSession).not.toBeNull(); }); + it("retries once on a browser-probe navigation timeout and succeeds", async () => { + resetRetryMocks(); + capturedCfgs.length = 0; + initializeSessionError = new Error("Navigation timeout of 60000 ms exceeded"); + initializeSessionFailUntilAttempt = 1; + + const { runProbeStage } = await import("./probeStage.js"); + const input = makeProbeInput({ cfgForceScreenshot: false, stageForceScreenshot: false }); + + const result = await runProbeStage(input); + + expect(initializeSessionCallCount).toBe(2); + expect(closeCaptureSessionCallCount).toBe(1); + expect(result.duration).toBe(5); + expect(result.probeSession).not.toBeNull(); + }); + it("throws immediately on a non-transient error without retrying", async () => { resetRetryMocks(); capturedCfgs.length = 0;