




















@@ -11,6 +11,7 @@ import {
1111navigateChromeMcpPage,
1212openChromeMcpTab,
1313resetChromeMcpSessionsForTest,
14+setChromeMcpProcessCleanupDepsForTest,
1415setChromeMcpSessionFactoryForTest,
1516takeChromeMcpScreenshot,
1617takeChromeMcpSnapshot,
@@ -268,6 +269,76 @@ describe("chrome MCP page parsing", () => {
268269]);
269270});
270271272+it("terminates the owned Chrome MCP subprocess tree when closing temporary sessions", async () => {
273+const session = createFakeSession();
274+Object.assign(session, { ownsProcessTree: true });
275+const closeMock = vi.fn().mockResolvedValue(undefined);
276+session.client.close = closeMock as typeof session.client.close;
277+const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = [];
278+setChromeMcpProcessCleanupDepsForTest({
279+platform: "linux",
280+listProcesses: vi.fn().mockResolvedValue([
281+{ pid: 123, ppid: 1 },
282+{ pid: 124, ppid: 123 },
283+{ pid: 125, ppid: 124 },
284+{ pid: 126, ppid: 1 },
285+]),
286+killProcess: (pid, signal) => {
287+killCalls.push({ pid, signal });
288+},
289+sleep: vi.fn().mockResolvedValue(undefined),
290+});
291+setChromeMcpSessionFactoryForTest(async () => session);
292+293+await ensureChromeMcpAvailable("chrome-live", undefined, { ephemeral: true });
294+295+expect(closeMock).toHaveBeenCalledTimes(1);
296+expect(killCalls).toEqual([
297+{ pid: 125, signal: "SIGTERM" },
298+{ pid: 124, signal: "SIGTERM" },
299+{ pid: 123, signal: "SIGTERM" },
300+{ pid: 125, signal: "SIGKILL" },
301+{ pid: 124, signal: "SIGKILL" },
302+{ pid: 123, signal: "SIGKILL" },
303+]);
304+});
305+306+it("uses Windows taskkill tree cleanup without waiting for SDK stdio close timeout", async () => {
307+const session = createFakeSession();
308+Object.assign(session, { ownsProcessTree: true });
309+const closeOrder: string[] = [];
310+session.client.close = vi.fn(async () => {
311+closeOrder.push("client.close");
312+}) as typeof session.client.close;
313+setChromeMcpProcessCleanupDepsForTest({
314+platform: "win32",
315+taskkillProcessTree: vi.fn(async (pid) => {
316+closeOrder.push(`taskkill:${pid}`);
317+}),
318+});
319+setChromeMcpSessionFactoryForTest(async () => session);
320+321+await ensureChromeMcpAvailable("chrome-live", undefined, { ephemeral: true });
322+323+expect(closeOrder).toEqual(["taskkill:123"]);
324+});
325+326+it("falls back to SDK stdio close when Windows taskkill cleanup fails", async () => {
327+const session = createFakeSession();
328+Object.assign(session, { ownsProcessTree: true });
329+const closeMock = vi.fn().mockResolvedValue(undefined);
330+session.client.close = closeMock as typeof session.client.close;
331+setChromeMcpProcessCleanupDepsForTest({
332+platform: "win32",
333+taskkillProcessTree: vi.fn().mockRejectedValue(new Error("taskkill failed")),
334+});
335+setChromeMcpSessionFactoryForTest(async () => session);
336+337+await ensureChromeMcpAvailable("chrome-live", undefined, { ephemeral: true });
338+339+expect(closeMock).toHaveBeenCalledTimes(1);
340+});
341+271342it("redacts remote CDP URL secrets from attach failures", async () => {
272343const secretToken = "browserless-secret-token-1234567890"; // pragma: allowlist secret
273344const user = "browser-user";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。