From 3ff2f85bad8899da5e8dd2aa0cb8423dfc0a282a Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 23 Mar 2026 03:14:57 -0700 Subject: [PATCH] fix: stop browser server tests from launching real chrome --- src/browser/routes/tabs.ts | 8 +++++ src/browser/server.auth-fail-closed.test.ts | 8 +++-- .../server.control-server.test-harness.ts | 29 +++++++++++++++++-- ...te-disabled-does-not-block-storage.test.ts | 8 +++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/browser/routes/tabs.ts b/src/browser/routes/tabs.ts index 87cb36c562c..d15838787b3 100644 --- a/src/browser/routes/tabs.ts +++ b/src/browser/routes/tabs.ts @@ -1,4 +1,8 @@ import { BrowserProfileUnavailableError, BrowserTabNotFoundError } from "../errors.js"; +import { + assertBrowserNavigationAllowed, + withBrowserNavigationPolicy, +} from "../navigation-guard.js"; import type { BrowserRouteContext, ProfileContext } from "../server-context.js"; import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js"; import { getProfileContext, jsonError, toNumber, toStringOrEmpty } from "./utils.js"; @@ -128,6 +132,10 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse ctx, mapTabError: true, run: async (profileCtx) => { + await assertBrowserNavigationAllowed({ + url, + ...withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy), + }); await profileCtx.ensureBrowserAvailable(); const tab = await profileCtx.openTab(url); res.json(tab); diff --git a/src/browser/server.auth-fail-closed.test.ts b/src/browser/server.auth-fail-closed.test.ts index 451b6196473..ae105234775 100644 --- a/src/browser/server.auth-fail-closed.test.ts +++ b/src/browser/server.auth-fail-closed.test.ts @@ -56,8 +56,8 @@ vi.mock("./pw-ai-state.js", () => ({ isPwAiLoaded: vi.fn(() => false), })); -const { startBrowserControlServerFromConfig, stopBrowserControlServer } = - await import("./server.js"); +let startBrowserControlServerFromConfig: typeof import("./server.js").startBrowserControlServerFromConfig; +let stopBrowserControlServer: typeof import("./server.js").stopBrowserControlServer; describe("browser control auth bootstrap failures", () => { beforeEach(async () => { @@ -65,10 +65,14 @@ describe("browser control auth bootstrap failures", () => { mocks.ensureBrowserControlAuth.mockClear(); mocks.resolveBrowserControlAuth.mockClear(); mocks.ensureExtensionRelayForProfiles.mockClear(); + vi.resetModules(); + ({ startBrowserControlServerFromConfig, stopBrowserControlServer } = + await import("./server.js")); }); afterEach(async () => { await stopBrowserControlServer(); + vi.resetModules(); }); it("fails closed when auth bootstrap throws and no auth is configured", async () => { diff --git a/src/browser/server.control-server.test-harness.ts b/src/browser/server.control-server.test-harness.ts index 57b8d191655..04a0f98c9ec 100644 --- a/src/browser/server.control-server.test-harness.ts +++ b/src/browser/server.control-server.test-harness.ts @@ -184,6 +184,18 @@ export function getChromeMcpMocks(): Record { const chromeUserDataDir = vi.hoisted(() => ({ dir: "/tmp/openclaw" })); installChromeUserDataDirHooks(chromeUserDataDir); +type BrowserServerModule = typeof import("./server.js"); +let browserServerModule: BrowserServerModule | null = null; + +async function loadBrowserServerModule(): Promise { + if (browserServerModule) { + return browserServerModule; + } + vi.resetModules(); + browserServerModule = await import("./server.js"); + return browserServerModule; +} + function makeProc(pid = 123) { const handlers = new Map void>>(); return { @@ -303,9 +315,19 @@ vi.mock("./screenshot.js", () => ({ })), })); -const server = await import("./server.js"); -export const startBrowserControlServerFromConfig = server.startBrowserControlServerFromConfig; -export const stopBrowserControlServer = server.stopBrowserControlServer; +export async function startBrowserControlServerFromConfig() { + const server = await loadBrowserServerModule(); + return await server.startBrowserControlServerFromConfig(); +} + +export async function stopBrowserControlServer(): Promise { + const server = browserServerModule; + browserServerModule = null; + if (!server) { + return; + } + await server.stopBrowserControlServer(); +} export function makeResponse( body: unknown, @@ -387,6 +409,7 @@ export function installBrowserControlServerHooks() { }); await resetBrowserControlServerTestContext(); + await loadBrowserServerModule(); // Minimal CDP JSON endpoints used by the server. let putNewCalls = 0; diff --git a/src/browser/server.evaluate-disabled-does-not-block-storage.test.ts b/src/browser/server.evaluate-disabled-does-not-block-storage.test.ts index 22c027b2d4c..b4331e82c0f 100644 --- a/src/browser/server.evaluate-disabled-does-not-block-storage.test.ts +++ b/src/browser/server.evaluate-disabled-does-not-block-storage.test.ts @@ -65,8 +65,8 @@ vi.mock("./server-context.js", async (importOriginal) => { }; }); -const { startBrowserControlServerFromConfig, stopBrowserControlServer } = - await import("./server.js"); +let startBrowserControlServerFromConfig: typeof import("./server.js").startBrowserControlServerFromConfig; +let stopBrowserControlServer: typeof import("./server.js").stopBrowserControlServer; describe("browser control evaluate gating", () => { beforeEach(async () => { @@ -83,6 +83,9 @@ describe("browser control evaluate gating", () => { pwMocks.evaluateViaPlaywright.mockClear(); routeCtxMocks.profileCtx.ensureTabAvailable.mockClear(); routeCtxMocks.profileCtx.stopRunningBrowser.mockClear(); + vi.resetModules(); + ({ startBrowserControlServerFromConfig, stopBrowserControlServer } = + await import("./server.js")); }); afterEach(async () => { @@ -104,6 +107,7 @@ describe("browser control evaluate gating", () => { } await stopBrowserControlServer(); + vi.resetModules(); }); it("blocks act:evaluate but still allows cookies/storage reads", async () => {