






















@@ -153,7 +153,29 @@ async function withMockChromeCdpServer(params: {
153153wss.emit("connection", ws, req);
154154});
155155});
156-params.onConnection?.(wss);
156+if (params.onConnection) {
157+params.onConnection(wss);
158+} else {
159+wss.on("connection", (ws) => {
160+ws.on("message", (raw) => {
161+const message = JSON.parse(rawDataToString(raw)) as {
162+id?: unknown;
163+method?: unknown;
164+};
165+if (message.method === "Browser.getVersion" && typeof message.id === "number") {
166+ws.send(
167+JSON.stringify({
168+id: message.id,
169+result: {
170+product: "Chrome/Mock",
171+userAgent: "OpenClawTest",
172+},
173+}),
174+);
175+}
176+});
177+});
178+}
157179await new Promise<void>((resolve, reject) => {
158180server.listen(0, "127.0.0.1", () => resolve());
159181server.once("error", reject);
@@ -484,6 +506,119 @@ describe("chrome.ts internal", () => {
484506});
485507});
486508509+it("accepts a ready CDP diagnostic after the launch HTTP probe expires", async () => {
510+vi.spyOn(fs, "existsSync").mockImplementation((p) => {
511+const s = String(p);
512+if (
513+s.includes("Google Chrome") ||
514+s.includes("google-chrome") ||
515+s.includes("/usr/bin/chromium")
516+) {
517+return true;
518+}
519+if (s.endsWith("Local State") || s.endsWith("Preferences")) {
520+return true;
521+}
522+return false;
523+});
524+spawnMock.mockImplementation(() => makeFakeProc());
525+526+const originalFetch = globalThis.fetch;
527+let now = 1_000_000;
528+vi.spyOn(Date, "now").mockImplementation(() => now);
529+let discoveryCalls = 0;
530+vi.stubGlobal(
531+"fetch",
532+vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
533+const url =
534+typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
535+if (url.includes("/json/version")) {
536+discoveryCalls += 1;
537+if (discoveryCalls === 1) {
538+now += 2;
539+throw new Error("ECONNREFUSED");
540+}
541+}
542+return await originalFetch(input, init);
543+}),
544+);
545+546+await withMockChromeCdpServer({
547+wsPath: "/devtools/browser/COLD_START",
548+run: async (baseUrl) => {
549+const port = new URL(baseUrl).port;
550+const profile = makeProfile(Number(port));
551+const running = await launchOpenClawChrome(
552+makeResolved({ localLaunchTimeoutMs: 1 }),
553+profile,
554+);
555+expect(running.pid).toBe(4242);
556+expect(discoveryCalls).toBeGreaterThan(1);
557+running.proc.kill?.("SIGTERM");
558+},
559+});
560+});
561+562+it("keeps the launched process when fallback diagnostic sees HTTP before WS readiness", async () => {
563+vi.spyOn(fs, "existsSync").mockImplementation((p) => {
564+const s = String(p);
565+if (
566+s.includes("Google Chrome") ||
567+s.includes("google-chrome") ||
568+s.includes("/usr/bin/chromium")
569+) {
570+return true;
571+}
572+if (s.endsWith("Local State") || s.endsWith("Preferences")) {
573+return true;
574+}
575+return false;
576+});
577+const fakeProc = makeFakeProc();
578+spawnMock.mockImplementation(() => fakeProc);
579+580+const originalFetch = globalThis.fetch;
581+let now = 1_000_000;
582+vi.spyOn(Date, "now").mockImplementation(() => now);
583+let discoveryCalls = 0;
584+vi.stubGlobal(
585+"fetch",
586+vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
587+const url =
588+typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
589+if (url.includes("/json/version")) {
590+discoveryCalls += 1;
591+if (discoveryCalls === 1) {
592+now += 2;
593+throw new Error("ECONNREFUSED");
594+}
595+}
596+return await originalFetch(input, init);
597+}),
598+);
599+600+await withMockChromeCdpServer({
601+wsPath: "/devtools/browser/WS_WARMING",
602+onConnection: (wss) => {
603+wss.on("connection", () => {
604+// HTTP discovery is enough for launch; caller owns WS readiness.
605+});
606+},
607+run: async (baseUrl) => {
608+const port = new URL(baseUrl).port;
609+const profile = makeProfile(Number(port));
610+const running = await launchOpenClawChrome(
611+makeResolved({ localLaunchTimeoutMs: 1 }),
612+profile,
613+);
614+expect(running.pid).toBe(4242);
615+expect(discoveryCalls).toBeGreaterThan(1);
616+expect(fakeProc.kill).not.toHaveBeenCalledWith("SIGKILL");
617+running.proc.kill?.("SIGTERM");
618+},
619+});
620+});
621+487622it("uses profile executablePath over global executablePath when launching", async () => {
488623const originalPlatform = process.platform;
489624vi.spyOn(fs, "existsSync").mockImplementation((p) => {
@@ -528,16 +663,14 @@ describe("chrome.ts internal", () => {
528663);
529664vi.stubEnv("OPENCLAW_CONFIG_PATH", configPath);
530665let cdpReachable = false;
666+const originalFetch = globalThis.fetch;
531667vi.stubGlobal(
532668"fetch",
533-vi.fn(async () => {
669+vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
534670if (!cdpReachable) {
535671throw new Error("ECONNREFUSED");
536672}
537-return {
538-ok: true,
539-json: async () => ({ webSocketDebuggerUrl: "ws://127.0.0.1/devtools" }),
540-} as unknown as Response;
673+return await originalFetch(input, init);
541674}),
542675);
543676vi.spyOn(fs, "existsSync").mockImplementation((p) => {
@@ -567,27 +700,33 @@ describe("chrome.ts internal", () => {
567700return secondProc;
568701});
569702570-const profile = { ...makeProfile(18888), executablePath: "/tmp/profile-chrome" };
571-const userDataDir = resolveOpenClawUserDataDir(profile.name);
572-await fsp.mkdir(userDataDir, { recursive: true });
573-await fsp.writeFile(path.join(userDataDir, "SingletonCookie"), "cookie");
574-await fsp.writeFile(path.join(userDataDir, "SingletonSocket"), "socket");
575-await fsp.symlink("remote-host-535", path.join(userDataDir, "SingletonLock"));
576-577-try {
578-const running = await launchOpenClawChrome(
579-makeResolved({ localLaunchTimeoutMs: 20 }),
580-profile,
581-);
582-expect(running.proc).toBe(secondProc);
583-expect(firstProc.kill).toHaveBeenCalledWith("SIGKILL");
584-expect(spawnCalls).toBe(2);
585-expect(fs.existsSync(path.join(userDataDir, "SingletonLock"))).toBe(false);
586-expect(fs.existsSync(path.join(userDataDir, "SingletonSocket"))).toBe(false);
587-running.proc.kill?.("SIGTERM");
588-} finally {
589-await fsp.rm(userDataDir, { recursive: true, force: true });
590-}
703+await withMockChromeCdpServer({
704+wsPath: "/devtools/browser/SINGLETON_RETRY",
705+run: async (baseUrl) => {
706+const port = Number(new URL(baseUrl).port);
707+const profile = { ...makeProfile(port), executablePath: "/tmp/profile-chrome" };
708+const userDataDir = resolveOpenClawUserDataDir(profile.name);
709+await fsp.mkdir(userDataDir, { recursive: true });
710+await fsp.writeFile(path.join(userDataDir, "SingletonCookie"), "cookie");
711+await fsp.writeFile(path.join(userDataDir, "SingletonSocket"), "socket");
712+await fsp.symlink("remote-host-535", path.join(userDataDir, "SingletonLock"));
713+714+try {
715+const running = await launchOpenClawChrome(
716+makeResolved({ localLaunchTimeoutMs: 20 }),
717+profile,
718+);
719+expect(running.proc).toBe(secondProc);
720+expect(firstProc.kill).toHaveBeenCalledWith("SIGKILL");
721+expect(spawnCalls).toBe(2);
722+expect(fs.existsSync(path.join(userDataDir, "SingletonLock"))).toBe(false);
723+expect(fs.existsSync(path.join(userDataDir, "SingletonSocket"))).toBe(false);
724+running.proc.kill?.("SIGTERM");
725+} finally {
726+await fsp.rm(userDataDir, { recursive: true, force: true });
727+}
728+},
729+});
591730});
592731593732it("throws with stderr hint + sandbox hint when CDP never becomes reachable", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。