Files
oh-my-pi/packages/coding-agent/test/tools/browser-attach.test.ts
T
Christian Stewart 78a4d2ea5a test(browser): stop gating chromium tests on a top-level-await export
`bun test` fails the coding-agent native/unit job with

  ReferenceError: Cannot access 'CHROMIUM_AVAILABLE' before initialization

in test/tools/browser-attach.test.ts and browser-tab-evaluate.test.ts,
taking the job red on every pull request.

chromium-probe.ts initializes its only export with top-level await. When
a test file importing it is the first file the runner loads, the await
settles before that file evaluates and the binding reads fine. When any
other test file was loaded first, the importer's body runs while the
binding is still in its temporal dead zone, and registration throws at
the `skipIf` call. `bun test <one file>` passes and `bun test --parallel=1
<any earlier file> <that file>` fails, which is why the job fails while a
single-file run does not.

Export the memoized probe as a function and let each test file await it
during its own evaluation, which the runner sequences. The probe still
runs once per process and resolves the executable exactly as before.

Signed-off-by: Christian Stewart <christian@aperture.us>
2026-08-02 05:00:26 -07:00

186 lines
6.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
pickElectronTarget,
shouldPreserveConnectedBrowserFocus,
} from "@oh-my-pi/pi-coding-agent/tools/browser/attach";
import {
acquireBrowser,
type BrowserHandle,
normalizeConnectedCdpUrl,
releaseBrowser,
} from "@oh-my-pi/pi-coding-agent/tools/browser/registry";
import { acquireTab, releaseTab } from "@oh-my-pi/pi-coding-agent/tools/browser/tab-supervisor";
import type { Browser, Page, Target } from "puppeteer-core";
import { chromiumAvailable } from "./chromium-probe";
const CHROMIUM_AVAILABLE = await chromiumAvailable();
interface FakePageOptions {
url: string;
title: string;
visible?: boolean;
}
function fakePage(options: FakePageOptions): Page {
return {
url: () => options.url,
title: async () => options.title,
evaluate: async () => options.visible === true,
} as unknown as Page;
}
function fakeTarget(type: string, page: Page | null): Target {
return {
type: () => type,
page: async () => page,
} as unknown as Target;
}
describe("pickElectronTarget", () => {
test("uses discovered CDP page targets when browser.pages is empty", async () => {
const page = fakePage({ url: "https://www.google.com/", title: "Google" });
let pagesCalled = false;
const browser = {
targets: () => [fakeTarget("browser", null), fakeTarget("page", page)],
pages: async () => {
pagesCalled = true;
return [];
},
} as unknown as Browser;
await expect(pickElectronTarget(browser, { matcher: "google" })).resolves.toBe(page);
expect(pagesCalled).toBe(false);
});
test("falls back to browser.pages when discovered targets have no usable page", async () => {
const page = fakePage({ url: "https://example.com/", title: "Example" });
const browser = {
targets: () => [fakeTarget("browser", null), fakeTarget("service_worker", null)],
pages: async () => [page],
} as unknown as Browser;
await expect(pickElectronTarget(browser)).resolves.toBe(page);
});
test("reports available pages when the matcher misses", async () => {
const page = fakePage({ url: "https://example.com/", title: "Example" });
const browser = {
targets: () => [fakeTarget("page", page)],
pages: async () => [],
} as unknown as Browser;
await expect(pickElectronTarget(browser, { matcher: "missing" })).rejects.toThrow(
'No page target matched "missing". Available pages:\n- Example https://example.com/',
);
});
test("prefers the foreground tab when asked to, without disturbing default order", async () => {
const background = fakePage({ url: "https://example.com/", title: "Example" });
const foreground = fakePage({ url: "https://example.org/", title: "Example Org", visible: true });
const browser = {
targets: () => [fakeTarget("page", background), fakeTarget("page", foreground)],
pages: async () => [],
} as unknown as Browser;
await expect(pickElectronTarget(browser, { preferVisible: true })).resolves.toBe(foreground);
await expect(pickElectronTarget(browser)).resolves.toBe(background);
});
test("falls back to the first usable tab when no tab reports itself visible", async () => {
const first = fakePage({ url: "https://example.com/", title: "Example" });
const second = fakePage({ url: "https://example.org/", title: "Example Org" });
const browser = {
targets: () => [fakeTarget("page", first), fakeTarget("page", second)],
pages: async () => [],
} as unknown as Browser;
await expect(pickElectronTarget(browser, { preferVisible: true })).resolves.toBe(first);
});
test("preserves connected-browser focus only for automatic target selection", () => {
expect(shouldPreserveConnectedBrowserFocus()).toBe(true);
expect(shouldPreserveConnectedBrowserFocus("example.com")).toBe(false);
});
test("rejects websocket cdp_url values with an actionable diagnostic", () => {
expect(() => normalizeConnectedCdpUrl("ws://127.0.0.1:9222/devtools/browser/id")).toThrow(
"browser app.cdp_url must be the HTTP CDP discovery endpoint",
);
expect(normalizeConnectedCdpUrl("http://127.0.0.1:9222/")).toBe("http://127.0.0.1:9222");
});
// Launches real headless Chromium; skipped where Chrome's system libraries are absent.
test.skipIf(!CHROMIUM_AVAILABLE)(
"navigates a fresh attached tab to the requested URL",
async () => {
const launched = await acquireBrowser({ kind: "headless", headless: true }, { cwd: process.cwd() });
if (!("browser" in launched)) throw new Error("Expected a Puppeteer browser");
const endpoint = new URL(launched.browser.wsEndpoint());
let attached: BrowserHandle | undefined;
let opened = false;
const tabName = `attach-navigation-${process.pid}-${Math.random().toString(36).slice(2)}`;
const requested = "data:text/html,<title>attached-navigation-target</title>";
try {
attached = await acquireBrowser(
{ kind: "connected", cdpUrl: `http://${endpoint.host}` },
{ cwd: process.cwd() },
);
const { tab } = await acquireTab(tabName, attached, {
url: requested,
waitUntil: "domcontentloaded",
timeoutMs: 10_000,
});
opened = true;
expect(tab.info.url).toBe(requested);
} finally {
if (opened) await releaseTab(tabName, { kill: false });
else if (attached) await releaseBrowser(attached, { kill: false });
await releaseBrowser(launched, { kill: true });
}
},
30_000,
);
test.skipIf(!CHROMIUM_AVAILABLE)(
"does not retry an attached navigation failure as worker startup",
async () => {
let requestCount = 0;
const server = Bun.serve({
port: 0,
fetch: () => {
requestCount++;
return new Promise<Response>(() => {});
},
});
const launched = await acquireBrowser({ kind: "headless", headless: true }, { cwd: process.cwd() });
if (!("browser" in launched)) throw new Error("Expected a Puppeteer browser");
const endpoint = new URL(launched.browser.wsEndpoint());
let attached: BrowserHandle | undefined;
let attempted = false;
try {
attached = await acquireBrowser(
{ kind: "connected", cdpUrl: `http://${endpoint.host}` },
{ cwd: process.cwd() },
);
attempted = true;
await expect(
acquireTab(`attach-failure-${process.pid}-${Math.random().toString(36).slice(2)}`, attached, {
url: `http://127.0.0.1:${server.port}/hang`,
waitUntil: "domcontentloaded",
timeoutMs: 100,
}),
).rejects.toThrow(/Navigation timeout/i);
expect(requestCount).toBe(1);
} finally {
if (attached && !attempted) await releaseBrowser(attached, { kill: false });
await releaseBrowser(launched, { kill: true });
await server.stop(true);
}
},
30_000,
);
});