mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 15:17:27 +00:00
fix(browser): wait for CDP readiness after start (#21149)
This commit is contained in:
committed by
Peter Steinberger
parent
0d620a56e2
commit
d06cc77f38
@@ -0,0 +1,77 @@
|
||||
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
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";
|
||||
|
||||
function makeBrowserState(): BrowserServerState {
|
||||
return {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
server: null as any,
|
||||
port: 0,
|
||||
resolved: {
|
||||
enabled: true,
|
||||
controlPort: 18791,
|
||||
cdpProtocol: "http",
|
||||
cdpHost: "127.0.0.1",
|
||||
cdpIsLoopback: true,
|
||||
evaluateEnabled: false,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
extraArgs: [],
|
||||
color: "#FF4500",
|
||||
headless: true,
|
||||
noSandbox: false,
|
||||
attachOnly: false,
|
||||
ssrfPolicy: { allowPrivateNetwork: true },
|
||||
defaultProfile: "openclaw",
|
||||
profiles: {
|
||||
openclaw: { cdpPort: 18800, color: "#FF4500" },
|
||||
},
|
||||
},
|
||||
profiles: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("browser server-context ensureBrowserAvailable", () => {
|
||||
it("waits for CDP readiness after launching to avoid follow-up PortInUseError races (#21149)", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const launchOpenClawChrome = vi.mocked(chromeModule.launchOpenClawChrome);
|
||||
const stopOpenClawChrome = vi.mocked(chromeModule.stopOpenClawChrome);
|
||||
const isChromeReachable = vi.mocked(chromeModule.isChromeReachable);
|
||||
const isChromeCdpReady = vi.mocked(chromeModule.isChromeCdpReady);
|
||||
|
||||
isChromeReachable.mockResolvedValue(false);
|
||||
isChromeCdpReady.mockResolvedValueOnce(false).mockResolvedValue(true);
|
||||
|
||||
const proc = new EventEmitter() as unknown as ChildProcessWithoutNullStreams;
|
||||
launchOpenClawChrome.mockResolvedValue({
|
||||
pid: 123,
|
||||
exe: { kind: "chromium", path: "/usr/bin/chromium" },
|
||||
userDataDir: "/tmp/openclaw-test",
|
||||
cdpPort: 18800,
|
||||
startedAt: Date.now(),
|
||||
proc,
|
||||
});
|
||||
|
||||
const state = makeBrowserState();
|
||||
const ctx = createBrowserRouteContext({ getState: () => state });
|
||||
const profile = ctx.forProfile("openclaw");
|
||||
|
||||
const promise = profile.ensureBrowserAvailable();
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
await expect(promise).resolves.toBeUndefined();
|
||||
|
||||
expect(launchOpenClawChrome).toHaveBeenCalledTimes(1);
|
||||
expect(isChromeCdpReady).toHaveBeenCalled();
|
||||
expect(stopOpenClawChrome).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -282,6 +282,21 @@ function createProfileContext(
|
||||
const isExtension = profile.driver === "extension";
|
||||
const profileState = getProfileState();
|
||||
const httpReachable = await isHttpReachable();
|
||||
const waitForCdpReadyAfterLaunch = async () => {
|
||||
// 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)) {
|
||||
return;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
throw new Error(
|
||||
`Chrome CDP websocket for profile "${profile.name}" is not reachable after start.`,
|
||||
);
|
||||
};
|
||||
|
||||
if (isExtension && remoteCdp) {
|
||||
throw new Error(
|
||||
@@ -319,6 +334,13 @@ function createProfileContext(
|
||||
}
|
||||
const launched = await launchOpenClawChrome(current.resolved, profile);
|
||||
attachRunning(launched);
|
||||
try {
|
||||
await waitForCdpReadyAfterLaunch();
|
||||
} catch (err) {
|
||||
await stopOpenClawChrome(launched).catch(() => {});
|
||||
setProfileRunning(null);
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user