fix(browser): tighten WS3 status probes

# Conflicts:
#	extensions/browser/src/browser/chrome-mcp.test.ts
#	extensions/browser/src/browser/chrome-mcp.ts
#	extensions/browser/src/browser/routes/basic.ts
This commit is contained in:
Peter Steinberger
2026-04-25 06:48:00 +01:00
parent 3c31facfa2
commit ad8737af2c
4 changed files with 85 additions and 3 deletions

View File

@@ -98,6 +98,7 @@ function createFakeSession(): ChromeMcpSession {
describe("chrome MCP page parsing", () => {
beforeEach(async () => {
await resetChromeMcpSessionsForTest();
vi.useRealTimers();
});
afterEach(() => {
@@ -474,7 +475,6 @@ describe("chrome MCP page parsing", () => {
expect(factoryCalls).toBe(2);
expect(tabs).toHaveLength(2);
});
it("reconnects and retries list_pages once when Chrome MCP reports a stale selected page", async () => {
let factoryCalls = 0;
const factory: ChromeMcpSessionFactory = async () => {
@@ -613,4 +613,34 @@ describe("chrome MCP page parsing", () => {
expect(factoryCalls).toBe(2);
expect(tabs).toHaveLength(2);
});
it("honors timeoutMs for ephemeral availability probes", async () => {
vi.useFakeTimers();
const closeMock = vi.fn().mockResolvedValue(undefined);
const factory: ChromeMcpSessionFactory = async () =>
({
client: {
callTool: vi.fn(),
listTools: vi.fn(),
close: closeMock,
connect: vi.fn(),
},
transport: {
pid: 123,
},
ready: new Promise<void>(() => {}),
}) as unknown as ChromeMcpSession;
setChromeMcpSessionFactoryForTest(factory);
const promise = ensureChromeMcpAvailable("chrome-live", undefined, {
ephemeral: true,
timeoutMs: 50,
});
const expectation = expect(promise).rejects.toThrow(/timed out after 50ms/i);
await vi.advanceTimersByTimeAsync(50);
await expectation;
expect(closeMock).toHaveBeenCalledTimes(1);
});
});

View File

@@ -463,6 +463,37 @@ async function getExistingSession(
}
}
async function waitForChromeMcpReady(
session: ChromeMcpSession,
profileName: string,
timeoutMs?: number,
): Promise<void> {
if (!timeoutMs || timeoutMs <= 0) {
await session.ready;
return;
}
let timer: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([
session.ready,
new Promise<never>((_, reject) => {
timer = setTimeout(() => {
reject(
new BrowserProfileUnavailableError(
`Chrome MCP existing-session attach for profile "${profileName}" timed out after ${timeoutMs}ms.`,
),
);
}, timeoutMs);
}),
]);
} finally {
if (timer) {
clearTimeout(timer);
}
}
}
async function createEphemeralSession(
profileName: string,
userDataDir?: string,

View File

@@ -63,7 +63,7 @@ describe("basic browser routes", () => {
it("maps existing-session status failures to JSON browser errors", async () => {
const response = await callBasicRouteWithState({
state: createExistingSessionProfileState({
isHttpReachable: async () => {
isTransportAvailable: async () => {
throw new BrowserProfileUnavailableError("attach failed");
},
}),
@@ -109,4 +109,25 @@ describe("basic browser routes", () => {
cdpReady: true,
});
});
it("probes Chrome MCP transport only once for status", async () => {
const isHttpReachable = vi.fn(async () => true);
const isTransportAvailable = vi.fn(async () => true);
const response = await callBasicRouteWithState({
state: createExistingSessionProfileState({
isHttpReachable,
isTransportAvailable,
}),
});
expect(response.statusCode).toBe(200);
expect(isTransportAvailable).toHaveBeenCalledTimes(1);
expect(isHttpReachable).not.toHaveBeenCalled();
expect(response.body).toMatchObject({
cdpHttp: true,
cdpReady: true,
running: true,
});
});
});

View File

@@ -105,7 +105,7 @@ describe("browser server-context existing-session profile", () => {
expect(chromeMcp.ensureChromeMcpAvailable).toHaveBeenCalledWith(
"chrome-live",
"/tmp/brave-profile",
{ ephemeral: true },
{ ephemeral: true, timeoutMs: 300 },
);
expect(chromeMcp.listChromeMcpTabs).toHaveBeenCalledWith("chrome-live", "/tmp/brave-profile", {
ephemeral: true,