diff --git a/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts b/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts index 92146de60b9..b16017e521b 100644 --- a/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts +++ b/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts @@ -1,8 +1,18 @@ import type { ChildProcessWithoutNullStreams } from "node:child_process"; import { EventEmitter } from "node:events"; import { afterEach, describe, expect, it, vi } from "vitest"; + +vi.mock("./chrome.js", () => ({ + isChromeCdpReady: vi.fn(async () => true), + isChromeReachable: vi.fn(async () => true), + launchOpenClawChrome: vi.fn(async () => { + throw new Error("unexpected launch"); + }), + resolveOpenClawUserDataDir: vi.fn(() => "/tmp/openclaw-test"), + stopOpenClawChrome: vi.fn(async () => {}), +})); + import * as chromeModule from "./chrome.js"; -import "./server-context.chrome-test-harness.js"; import type { BrowserServerState } from "./server-context.js"; import { createBrowserRouteContext } from "./server-context.js"; diff --git a/src/browser/server-context.ts b/src/browser/server-context.ts index 26e8ce5c38b..1890f56efcc 100644 --- a/src/browser/server-context.ts +++ b/src/browser/server-context.ts @@ -286,9 +286,15 @@ function createProfileContext( // launchOpenClawChrome() can return before Chrome is fully ready to serve /json/version + CDP WS. // If a follow-up call (snapshot/screenshot/etc.) races ahead, we can hit PortInUseError trying to // launch again on the same port. Poll briefly so browser(action="start"/"open") is stable. - const maxAttempts = 50; - for (let attempt = 0; attempt < maxAttempts; attempt++) { - if (await isReachable(1200)) { + // + // Bound the wait by wall-clock time to avoid long stalls when /json/version is reachable + // but the CDP WebSocket never becomes ready. + const deadlineMs = Date.now() + 8000; + while (Date.now() < deadlineMs) { + const remainingMs = Math.max(0, deadlineMs - Date.now()); + // Keep each attempt short; loopback profiles derive a WS timeout from this value. + const attemptTimeoutMs = Math.max(75, Math.min(250, remainingMs)); + if (await isReachable(attemptTimeoutMs)) { return; } await new Promise((r) => setTimeout(r, 100));