diff --git a/extensions/browser/src/browser/routes/agent.act.existing-session-navigation-guard.test.ts b/extensions/browser/src/browser/routes/agent.act.existing-session-navigation-guard.test.ts index 5b5268a8d45..72f9314d9ce 100644 --- a/extensions/browser/src/browser/routes/agent.act.existing-session-navigation-guard.test.ts +++ b/extensions/browser/src/browser/routes/agent.act.existing-session-navigation-guard.test.ts @@ -97,6 +97,10 @@ describe("existing-session interaction navigation guard", () => { } async function expectActionToReject(body: Record) { + await expectActionToThrow(body, "Unable to verify stable post-interaction navigation"); + } + + async function expectActionToThrow(body: Record, message: string) { const handler = getActPostHandler(); const response = createBrowserRouteResponse(); const pending = handler?.({ params: {}, query: {}, body }, response.res) ?? Promise.resolve(); @@ -106,7 +110,7 @@ describe("existing-session interaction navigation guard", () => { await pending; })(); - await expect(completion).rejects.toThrow("Unable to verify stable post-interaction navigation"); + await expect(completion).rejects.toThrow(message); } function expectNavigationProbeUrls(urls: string[]) { @@ -215,18 +219,7 @@ describe("existing-session interaction navigation guard", () => { }, ); - const handler = getActPostHandler(); - const response = createBrowserRouteResponse(); - const pending = - handler?.({ params: {}, query: {}, body: { kind: "click", ref: "btn-1" } }, response.res) ?? - Promise.resolve(); - void pending.catch(() => {}); - const completion = (async () => { - await vi.runAllTimersAsync(); - await pending; - })(); - - await expect(completion).rejects.toThrow("blocked new tab"); + await expectActionToThrow({ kind: "click", ref: "btn-1" }, "blocked new tab"); expect(chromeMcpMocks.clickChromeMcpElement).toHaveBeenCalledOnce(); }); @@ -338,18 +331,7 @@ describe("existing-session interaction navigation guard", () => { throw new Error("stale element"); }); - const handler = getActPostHandler(); - const response = createBrowserRouteResponse(); - const pending = - handler?.({ params: {}, query: {}, body: { kind: "click", ref: "btn-1" } }, response.res) ?? - Promise.resolve(); - void pending.catch(() => {}); - const completion = (async () => { - await vi.runAllTimersAsync(); - await pending; - })(); - - await expect(completion).rejects.toThrow("stale element"); + await expectActionToThrow({ kind: "click", ref: "btn-1" }, "stale element"); expect(chromeMcpMocks.evaluateChromeMcpScript).toHaveBeenCalled(); expect(navigationGuardMocks.assertBrowserNavigationResultAllowed).toHaveBeenCalled(); }); diff --git a/extensions/browser/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts b/extensions/browser/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts index 8d084d55bd5..ce14981668e 100644 --- a/extensions/browser/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts +++ b/extensions/browser/src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts @@ -25,6 +25,27 @@ function setupEnsureBrowserAvailableHarness() { return { launchOpenClawChrome, stopOpenClawChrome, isChromeCdpReady, profile, state }; } +function createAttachOnlyLoopbackProfile(cdpUrl: string) { + const state = makeBrowserServerState({ + profile: { + name: "manual-cdp", + cdpUrl, + cdpHost: "127.0.0.1", + cdpIsLoopback: true, + cdpPort: 9222, + color: "#00AA00", + driver: "openclaw", + attachOnly: true, + }, + resolvedOverrides: { + defaultProfile: "manual-cdp", + ssrfPolicy: {}, + }, + }); + const ctx = createBrowserRouteContext({ getState: () => state }); + return { profile: ctx.forProfile("manual-cdp"), state }; +} + afterEach(() => { vi.useRealTimers(); vi.clearAllMocks(); @@ -142,24 +163,7 @@ describe("browser server-context ensureBrowserAvailable", () => { const isChromeReachable = vi.mocked(chromeModule.isChromeReachable); const isChromeCdpReady = vi.mocked(chromeModule.isChromeCdpReady); - const state = makeBrowserServerState({ - profile: { - name: "manual-cdp", - cdpUrl: "http://127.0.0.1:9222", - cdpHost: "127.0.0.1", - cdpIsLoopback: true, - cdpPort: 9222, - color: "#00AA00", - driver: "openclaw", - attachOnly: true, - }, - resolvedOverrides: { - defaultProfile: "manual-cdp", - ssrfPolicy: {}, - }, - }); - const ctx = createBrowserRouteContext({ getState: () => state }); - const profile = ctx.forProfile("manual-cdp"); + const { profile, state } = createAttachOnlyLoopbackProfile("http://127.0.0.1:9222"); isChromeReachable.mockResolvedValueOnce(true); isChromeCdpReady.mockResolvedValueOnce(true); @@ -195,24 +199,7 @@ describe("browser server-context ensureBrowserAvailable", () => { const isChromeReachable = vi.mocked(chromeModule.isChromeReachable); const isChromeCdpReady = vi.mocked(chromeModule.isChromeCdpReady); - const state = makeBrowserServerState({ - profile: { - name: "manual-cdp", - cdpUrl: "ws://127.0.0.1:9222", - cdpHost: "127.0.0.1", - cdpIsLoopback: true, - cdpPort: 9222, - color: "#00AA00", - driver: "openclaw", - attachOnly: true, - }, - resolvedOverrides: { - defaultProfile: "manual-cdp", - ssrfPolicy: {}, - }, - }); - const ctx = createBrowserRouteContext({ getState: () => state }); - const profile = ctx.forProfile("manual-cdp"); + const { profile, state } = createAttachOnlyLoopbackProfile("ws://127.0.0.1:9222"); isChromeReachable.mockResolvedValueOnce(true); isChromeCdpReady.mockResolvedValueOnce(true); diff --git a/extensions/browser/src/browser/server-context.remote-profile-tab-ops.playwright.test.ts b/extensions/browser/src/browser/server-context.remote-profile-tab-ops.playwright.test.ts index 4698c9a1c35..e180ac6d3aa 100644 --- a/extensions/browser/src/browser/server-context.remote-profile-tab-ops.playwright.test.ts +++ b/extensions/browser/src/browser/server-context.remote-profile-tab-ops.playwright.test.ts @@ -8,6 +8,15 @@ import { const deps: RemoteProfileTestDeps = await loadRemoteProfileTestDeps(); installRemoteProfileTestLifecycle(deps); +function page(targetId: string, url = `https://${targetId.toLowerCase()}.example`) { + return { + targetId, + title: targetId === "T1" ? "Tab 1" : targetId, + url, + type: "page" as const, + }; +} + describe("browser remote profile tab ops via Playwright", () => { it("uses Playwright tab operations when available", async () => { const listPagesViaPlaywright = vi.fn(async () => [ @@ -91,10 +100,7 @@ describe("browser remote profile tab ops via Playwright", () => { }); it("rejects stale targetId for remote profiles even when only one tab remains", async () => { - const responses = [ - [{ targetId: "T1", title: "Tab 1", url: "https://example.com", type: "page" }], - [{ targetId: "T1", title: "Tab 1", url: "https://example.com", type: "page" }], - ]; + const responses = Array.from({ length: 2 }, () => [page("T1", "https://example.com")]); const listPagesViaPlaywright = vi.fn(deps.createSequentialPageLister(responses)); vi.spyOn(deps.pwAiModule, "getPwAiModule").mockResolvedValue({ @@ -106,16 +112,7 @@ describe("browser remote profile tab ops via Playwright", () => { }); it("keeps rejecting stale targetId for remote profiles when multiple tabs exist", async () => { - const responses = [ - [ - { targetId: "A", title: "A", url: "https://a.example", type: "page" }, - { targetId: "B", title: "B", url: "https://b.example", type: "page" }, - ], - [ - { targetId: "A", title: "A", url: "https://a.example", type: "page" }, - { targetId: "B", title: "B", url: "https://b.example", type: "page" }, - ], - ]; + const responses = Array.from({ length: 2 }, () => [page("A"), page("B")]); const listPagesViaPlaywright = vi.fn(deps.createSequentialPageLister(responses)); vi.spyOn(deps.pwAiModule, "getPwAiModule").mockResolvedValue({