diff --git a/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts b/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts index 2677d840ebd..455d543fff6 100644 --- a/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts +++ b/src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts @@ -25,9 +25,61 @@ vi.mock("./chrome.js", () => ({ stopOpenClawChrome: vi.fn(async () => {}), })); +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, + color: "#FF4500", + headless: true, + noSandbox: false, + attachOnly: false, + defaultProfile: "chrome", + profiles: { + chrome: { + driver: "extension", + cdpUrl: "http://127.0.0.1:18792", + cdpPort: 18792, + color: "#00AA00", + }, + openclaw: { cdpPort: 18800, color: "#FF4500" }, + }, + }, + profiles: new Map(), + }; +} + +function stubChromeJsonList(responses: unknown[]) { + const fetchMock = vi.fn(); + const queue = [...responses]; + + fetchMock.mockImplementation(async (url: unknown) => { + const u = String(url); + if (!u.includes("/json/list")) { + throw new Error(`unexpected fetch: ${u}`); + } + const next = queue.shift(); + if (!next) { + throw new Error("no more responses"); + } + return { + ok: true, + json: async () => next, + } as unknown as Response; + }); + + global.fetch = fetchMock; + return fetchMock; +} + describe("browser server-context ensureTabAvailable", () => { it("sticks to the last selected target when targetId is omitted", async () => { - const fetchMock = vi.fn(); // 1st call (snapshot): stable ordering A then B (twice) // 2nd call (act): reversed ordering B then A (twice) const responses = [ @@ -48,52 +100,8 @@ describe("browser server-context ensureTabAvailable", () => { { id: "A", type: "page", url: "https://a.example", webSocketDebuggerUrl: "ws://x/a" }, ], ]; - - fetchMock.mockImplementation(async (url: unknown) => { - const u = String(url); - if (!u.includes("/json/list")) { - throw new Error(`unexpected fetch: ${u}`); - } - const next = responses.shift(); - if (!next) { - throw new Error("no more responses"); - } - return { - ok: true, - json: async () => next, - } as unknown as Response; - }); - - global.fetch = fetchMock; - - const state: BrowserServerState = { - // unused in these tests - // 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, - color: "#FF4500", - headless: true, - noSandbox: false, - attachOnly: false, - defaultProfile: "chrome", - profiles: { - chrome: { - driver: "extension", - cdpUrl: "http://127.0.0.1:18792", - cdpPort: 18792, - color: "#00AA00", - }, - openclaw: { cdpPort: 18800, color: "#FF4500" }, - }, - }, - profiles: new Map(), - }; + stubChromeJsonList(responses); + const state = makeBrowserState(); const ctx = createBrowserRouteContext({ getState: () => state, @@ -107,53 +115,12 @@ describe("browser server-context ensureTabAvailable", () => { }); it("falls back to the only attached tab when an invalid targetId is provided (extension)", async () => { - const fetchMock = vi.fn(); const responses = [ [{ id: "A", type: "page", url: "https://a.example", webSocketDebuggerUrl: "ws://x/a" }], [{ id: "A", type: "page", url: "https://a.example", webSocketDebuggerUrl: "ws://x/a" }], ]; - - fetchMock.mockImplementation(async (url: unknown) => { - const u = String(url); - if (!u.includes("/json/list")) { - throw new Error(`unexpected fetch: ${u}`); - } - const next = responses.shift(); - if (!next) { - throw new Error("no more responses"); - } - return { ok: true, json: async () => next } as unknown as Response; - }); - - global.fetch = fetchMock; - - const state: BrowserServerState = { - // 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, - color: "#FF4500", - headless: true, - noSandbox: false, - attachOnly: false, - defaultProfile: "chrome", - profiles: { - chrome: { - driver: "extension", - cdpUrl: "http://127.0.0.1:18792", - cdpPort: 18792, - color: "#00AA00", - }, - openclaw: { cdpPort: 18800, color: "#FF4500" }, - }, - }, - profiles: new Map(), - }; + stubChromeJsonList(responses); + const state = makeBrowserState(); const ctx = createBrowserRouteContext({ getState: () => state }); const chrome = ctx.forProfile("chrome"); @@ -162,49 +129,9 @@ describe("browser server-context ensureTabAvailable", () => { }); it("returns a descriptive message when no extension tabs are attached", async () => { - const fetchMock = vi.fn(); const responses = [[]]; - fetchMock.mockImplementation(async (url: unknown) => { - const u = String(url); - if (!u.includes("/json/list")) { - throw new Error(`unexpected fetch: ${u}`); - } - const next = responses.shift(); - if (!next) { - throw new Error("no more responses"); - } - return { ok: true, json: async () => next } as unknown as Response; - }); - - global.fetch = fetchMock; - - const state: BrowserServerState = { - // 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, - color: "#FF4500", - headless: true, - noSandbox: false, - attachOnly: false, - defaultProfile: "chrome", - profiles: { - chrome: { - driver: "extension", - cdpUrl: "http://127.0.0.1:18792", - cdpPort: 18792, - color: "#00AA00", - }, - openclaw: { cdpPort: 18800, color: "#FF4500" }, - }, - }, - profiles: new Map(), - }; + stubChromeJsonList(responses); + const state = makeBrowserState(); const ctx = createBrowserRouteContext({ getState: () => state }); const chrome = ctx.forProfile("chrome");