feat(baoyu-chrome-cdp): add gracefulKillChrome and fix killChrome process state check
This commit is contained in:
parent
8e2967d4a2
commit
60ab574559
|
|
@ -11,6 +11,7 @@ import {
|
||||||
discoverRunningChromeDebugPort,
|
discoverRunningChromeDebugPort,
|
||||||
findChromeExecutable,
|
findChromeExecutable,
|
||||||
findExistingChromeDebugPort,
|
findExistingChromeDebugPort,
|
||||||
|
gracefulKillChrome,
|
||||||
getFreePort,
|
getFreePort,
|
||||||
openPageSession,
|
openPageSession,
|
||||||
resolveSharedChromeProfileDir,
|
resolveSharedChromeProfileDir,
|
||||||
|
|
@ -110,6 +111,44 @@ async function stopProcess(child: ChildProcess | null): Promise<void> {
|
||||||
await new Promise((resolve) => child.once("exit", resolve));
|
await new Promise((resolve) => child.once("exit", resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function startPortHoldingProcess(port: number): Promise<ChildProcess> {
|
||||||
|
const child = spawn(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
"-e",
|
||||||
|
`
|
||||||
|
const http = require("node:http");
|
||||||
|
const port = Number(process.argv[1]);
|
||||||
|
const server = http.createServer((_req, res) => res.end("ok"));
|
||||||
|
server.listen(port, "127.0.0.1", () => process.stdout.write("ready\\n"));
|
||||||
|
setInterval(() => {}, 1000);
|
||||||
|
`,
|
||||||
|
String(port),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
stdio: ["ignore", "pipe", "ignore"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => reject(new Error("Timed out waiting for child server to start.")), 3_000);
|
||||||
|
child.once("error", (error) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
child.stdout?.once("data", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
child.once("exit", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(new Error("Child server exited before becoming ready."));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
test("getFreePort honors a fixed environment override and otherwise allocates a TCP port", async (t) => {
|
test("getFreePort honors a fixed environment override and otherwise allocates a TCP port", async (t) => {
|
||||||
useEnv(t, { TEST_FIXED_PORT: "45678" });
|
useEnv(t, { TEST_FIXED_PORT: "45678" });
|
||||||
assert.equal(await getFreePort("TEST_FIXED_PORT"), 45678);
|
assert.equal(await getFreePort("TEST_FIXED_PORT"), 45678);
|
||||||
|
|
@ -305,3 +344,19 @@ test("waitForChromeDebugPort retries until the debug endpoint becomes available"
|
||||||
|
|
||||||
assert.equal(websocketUrl, `ws://127.0.0.1:${port}/devtools/browser/demo`);
|
assert.equal(websocketUrl, `ws://127.0.0.1:${port}/devtools/browser/demo`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("gracefulKillChrome waits for the Chrome process to exit and release its port", async (t) => {
|
||||||
|
const port = await getFreePort();
|
||||||
|
const child = await startPortHoldingProcess(port);
|
||||||
|
t.after(async () => { await stopProcess(child); });
|
||||||
|
|
||||||
|
assert.equal(await waitForChromeDebugPort(port, 1_000).catch(() => null), null);
|
||||||
|
|
||||||
|
await gracefulKillChrome(child, port, 4_000);
|
||||||
|
|
||||||
|
assert.ok(child.exitCode !== null || child.signalCode !== null);
|
||||||
|
assert.equal(
|
||||||
|
await fetch(`http://127.0.0.1:${port}`).then(() => true).catch(() => false),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||||
chrome.kill("SIGTERM");
|
chrome.kill("SIGTERM");
|
||||||
} catch {}
|
} catch {}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!chrome.killed) {
|
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||||
try {
|
try {
|
||||||
chrome.kill("SIGKILL");
|
chrome.kill("SIGKILL");
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||||
}, 2_000).unref?.();
|
}, 2_000).unref?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function gracefulKillChrome(
|
||||||
|
chrome: ChildProcess,
|
||||||
|
port?: number,
|
||||||
|
timeoutMs = 6_000,
|
||||||
|
): Promise<void> {
|
||||||
|
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||||
|
|
||||||
|
const exitPromise = new Promise<void>((resolve) => {
|
||||||
|
chrome.once("exit", () => resolve());
|
||||||
|
});
|
||||||
|
|
||||||
|
killChrome(chrome);
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||||
|
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||||
|
|
||||||
|
const exited = await Promise.race([
|
||||||
|
exitPromise.then(() => true),
|
||||||
|
sleep(100).then(() => false),
|
||||||
|
]);
|
||||||
|
if (exited) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.race([
|
||||||
|
exitPromise,
|
||||||
|
sleep(250),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||||
let targetId: string;
|
let targetId: string;
|
||||||
let createdTarget = false;
|
let createdTarget = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue