Discover installed Chromium executable

This commit is contained in:
2026-11-07 10:56:35 -08:00
parent 907984d241
commit 946771d406
2 changed files with 45 additions and 1 deletions
+19 -1
View File
@@ -1,4 +1,5 @@
import { ChildProcess, spawn } from "child_process"; import { ChildProcess, spawn } from "child_process";
import { accessSync, constants } from "fs";
import { mkdtemp, rm } from "fs/promises"; import { mkdtemp, rm } from "fs/promises";
import os from "os"; import os from "os";
import path from "path"; import path from "path";
@@ -15,9 +16,26 @@ export class ChromiumFallback {
constructor( constructor(
private readonly onExit: (url: string) => void = () => undefined, private readonly onExit: (url: string) => void = () => undefined,
private readonly executable = process.env.QUEUECUBE_CHROME_BIN || "chromium", private readonly executable = process.env.QUEUECUBE_CHROME_BIN || ChromiumFallback.findExecutable(),
) {} ) {}
private static findExecutable(): string {
const directories = (process.env.PATH || "").split(path.delimiter);
for (const name of ["chromium", "chromium-browser", "google-chrome"]) {
for (const directory of directories) {
if (!directory) continue;
const candidate = path.join(directory, name);
try {
accessSync(candidate, constants.X_OK);
return candidate;
} catch {
// Keep checking the remaining supported executable names.
}
}
}
return "chromium";
}
public get isRunning(): boolean { public get isRunning(): boolean {
return this.active !== null; return this.active !== null;
} }
@@ -41,6 +41,32 @@ test("uses isolated profile and safe argv; stop kills only its process and clean
} }
}); });
test("discovers chromium-browser when that is the installed executable", async () => {
const fixture = await fs.mkdtemp(path.join(os.tmpdir(), "queuecube-browser-path-test-"));
const argsFile = path.join(fixture, "argv.json");
const executable = path.join(fixture, "chromium-browser");
const previousPath = process.env.PATH;
const previousOverride = process.env.QUEUECUBE_CHROME_BIN;
await fs.writeFile(executable, `#!/usr/bin/env node\nconst fs=require('fs'); fs.writeFileSync(${JSON.stringify(argsFile)}, JSON.stringify(process.argv.slice(2))); setInterval(()=>{},1000);\n`);
await fs.chmod(executable, 0o700);
await fs.symlink(process.execPath, path.join(fixture, "node"));
try {
process.env.PATH = fixture;
delete process.env.QUEUECUBE_CHROME_BIN;
const browser = new ChromiumFallback();
assert.equal(await browser.start("https://example.test/browser-discovery", () => true), true);
await waitFor(async () => { try { await fs.access(argsFile); return true; } catch { return false; } });
assert.equal(JSON.parse(await fs.readFile(argsFile, "utf8")).at(-1), "https://example.test/browser-discovery");
await browser.stop();
} finally {
process.env.PATH = previousPath;
if (previousOverride === undefined) delete process.env.QUEUECUBE_CHROME_BIN;
else process.env.QUEUECUBE_CHROME_BIN = previousOverride;
await fs.rm(fixture, { recursive: true, force: true });
}
});
test("canceled startup cleans its profile without spawning", async () => { test("canceled startup cleans its profile without spawning", async () => {
const browser = new ChromiumFallback(() => undefined, "/bin/false"); const browser = new ChromiumFallback(() => undefined, "/bin/false");
assert.equal(await browser.start("https://example.test/", () => false), false); assert.equal(await browser.start("https://example.test/", () => false), false);