mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-13 06:19:46 +08:00
774ad784d8
Move test files from tests/ directory to colocate with source code, convert from .mjs to .ts using tsx runner, add workspaces and npm cache to CI workflow.
172 lines
4.9 KiB
TypeScript
172 lines
4.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs/promises";
|
|
import http from "node:http";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import test, { type TestContext } from "node:test";
|
|
|
|
import {
|
|
findChromeExecutable,
|
|
findExistingChromeDebugPort,
|
|
getFreePort,
|
|
resolveSharedChromeProfileDir,
|
|
waitForChromeDebugPort,
|
|
} from "./index.ts";
|
|
|
|
function useEnv(
|
|
t: TestContext,
|
|
values: Record<string, string | null>,
|
|
): void {
|
|
const previous = new Map<string, string | undefined>();
|
|
for (const [key, value] of Object.entries(values)) {
|
|
previous.set(key, process.env[key]);
|
|
if (value == null) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
|
|
t.after(() => {
|
|
for (const [key, value] of previous.entries()) {
|
|
if (value == null) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function makeTempDir(prefix: string): Promise<string> {
|
|
return fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
}
|
|
|
|
async function startDebugServer(port: number): Promise<http.Server> {
|
|
const server = http.createServer((req, res) => {
|
|
if (req.url === "/json/version") {
|
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
res.end(JSON.stringify({
|
|
webSocketDebuggerUrl: `ws://127.0.0.1:${port}/devtools/browser/demo`,
|
|
}));
|
|
return;
|
|
}
|
|
|
|
res.writeHead(404);
|
|
res.end();
|
|
});
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
server.once("error", reject);
|
|
server.listen(port, "127.0.0.1", () => resolve());
|
|
});
|
|
|
|
return server;
|
|
}
|
|
|
|
async function closeServer(server: http.Server): Promise<void> {
|
|
await new Promise<void>((resolve, reject) => {
|
|
server.close((error) => {
|
|
if (error) reject(error);
|
|
else resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
test("getFreePort honors a fixed environment override and otherwise allocates a TCP port", async (t) => {
|
|
useEnv(t, { TEST_FIXED_PORT: "45678" });
|
|
assert.equal(await getFreePort("TEST_FIXED_PORT"), 45678);
|
|
|
|
const dynamicPort = await getFreePort();
|
|
assert.ok(Number.isInteger(dynamicPort));
|
|
assert.ok(dynamicPort > 0);
|
|
});
|
|
|
|
test("findChromeExecutable prefers env overrides and falls back to candidate paths", async (t) => {
|
|
const root = await makeTempDir("baoyu-chrome-bin-");
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
|
|
const envChrome = path.join(root, "env-chrome");
|
|
const fallbackChrome = path.join(root, "fallback-chrome");
|
|
await fs.writeFile(envChrome, "");
|
|
await fs.writeFile(fallbackChrome, "");
|
|
|
|
useEnv(t, { BAOYU_CHROME_PATH: envChrome });
|
|
assert.equal(
|
|
findChromeExecutable({
|
|
envNames: ["BAOYU_CHROME_PATH"],
|
|
candidates: { default: [fallbackChrome] },
|
|
}),
|
|
envChrome,
|
|
);
|
|
|
|
useEnv(t, { BAOYU_CHROME_PATH: null });
|
|
assert.equal(
|
|
findChromeExecutable({
|
|
envNames: ["BAOYU_CHROME_PATH"],
|
|
candidates: { default: [fallbackChrome] },
|
|
}),
|
|
fallbackChrome,
|
|
);
|
|
});
|
|
|
|
test("resolveSharedChromeProfileDir supports env overrides, WSL paths, and default suffixes", (t) => {
|
|
useEnv(t, { BAOYU_SHARED_PROFILE: "/tmp/custom-profile" });
|
|
assert.equal(
|
|
resolveSharedChromeProfileDir({
|
|
envNames: ["BAOYU_SHARED_PROFILE"],
|
|
appDataDirName: "demo-app",
|
|
profileDirName: "demo-profile",
|
|
}),
|
|
path.resolve("/tmp/custom-profile"),
|
|
);
|
|
|
|
useEnv(t, { BAOYU_SHARED_PROFILE: null });
|
|
assert.equal(
|
|
resolveSharedChromeProfileDir({
|
|
wslWindowsHome: "/mnt/c/Users/demo",
|
|
appDataDirName: "demo-app",
|
|
profileDirName: "demo-profile",
|
|
}),
|
|
path.join("/mnt/c/Users/demo", ".local", "share", "demo-app", "demo-profile"),
|
|
);
|
|
|
|
const fallback = resolveSharedChromeProfileDir({
|
|
appDataDirName: "demo-app",
|
|
profileDirName: "demo-profile",
|
|
});
|
|
assert.match(fallback, /demo-app[\\/]demo-profile$/);
|
|
});
|
|
|
|
test("findExistingChromeDebugPort reads DevToolsActivePort and validates it against a live endpoint", async (t) => {
|
|
const root = await makeTempDir("baoyu-cdp-profile-");
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
|
|
const port = await getFreePort();
|
|
const server = await startDebugServer(port);
|
|
t.after(() => closeServer(server));
|
|
|
|
await fs.writeFile(path.join(root, "DevToolsActivePort"), `${port}\n/devtools/browser/demo\n`);
|
|
|
|
const found = await findExistingChromeDebugPort({ profileDir: root, timeoutMs: 1000 });
|
|
assert.equal(found, port);
|
|
});
|
|
|
|
test("waitForChromeDebugPort retries until the debug endpoint becomes available", async (t) => {
|
|
const port = await getFreePort();
|
|
|
|
const serverPromise = (async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
const server = await startDebugServer(port);
|
|
t.after(() => closeServer(server));
|
|
})();
|
|
|
|
const websocketUrl = await waitForChromeDebugPort(port, 4000, {
|
|
includeLastError: true,
|
|
});
|
|
await serverPromise;
|
|
|
|
assert.equal(websocketUrl, `ws://127.0.0.1:${port}/devtools/browser/demo`);
|
|
});
|