Merge pull request #3802 from heygen-com/fix/prinfra-344-browser-executability

fix(cli): verify Chrome can execute during preflight
This commit is contained in:
Vance Ingalls
2026-09-09 00:45:26 -07:00
committed by GitHub
2 changed files with 83 additions and 2 deletions
@@ -125,6 +125,41 @@ describe("runEnvironmentChecks", () => {
}
});
it.each([
{ failure: { status: 133, signal: "SIGTRAP" }, detail: "SIGTRAP" },
{ failure: { code: "EACCES" }, detail: "EACCES" },
{ failure: { code: "ETIMEDOUT", signal: "SIGKILL" }, detail: "ETIMEDOUT" },
])("rejects an existing browser that fails --version: $detail", async ({ failure, detail }) => {
execFileSync.mockImplementation((_path, args) => {
if (args[0] === "--version") throw Object.assign(new Error("cannot execute"), failure);
return "ffmpeg version 7.1.1\n";
});
const findBrowser = vi.spyOn(manager, "findBrowser").mockResolvedValue({
executablePath: process.execPath,
source: "cache",
});
try {
for (const browserPath of [undefined, process.execPath]) {
const result = await runEnvironmentChecks({ includeBrowser: true, browserPath });
const chrome = result.outcomes.find((outcome) => outcome.name === "Chrome");
expect(chrome).toMatchObject({ ok: false, level: "error", title: "Chrome cannot start" });
expect(chrome?.detail).toContain(detail);
expect(result.browser).toBeUndefined();
}
expect(execFileSync).toHaveBeenCalledWith(
process.execPath,
["--version"],
expect.objectContaining({
timeout: 5000,
killSignal: "SIGKILL",
windowsHide: true,
}),
);
} finally {
findBrowser.mockRestore();
}
});
it("reports an explicit missing browser path before render starts", async () => {
const result = await runEnvironmentChecks({
includeBrowser: true,
+48 -2
View File
@@ -180,10 +180,56 @@ function chromeSharedLibOutcome(
};
}
function chromeLaunchFailureDetails(error: unknown): string {
if (typeof error !== "object" || error === null) return "";
const status = "status" in error ? error.status : undefined;
const signal = "signal" in error ? error.signal : undefined;
const code = "code" in error ? error.code : undefined;
return [
typeof status === "number" ? `exit code ${status}` : "",
typeof signal === "string" ? `signal ${signal}` : "",
typeof code === "string" ? code : "",
]
.filter(Boolean)
.join(", ");
}
function chromeLaunchOutcome(
executablePath: string,
found: EnvironmentCheckOutcome,
): EnvironmentCheckOutcome {
const libraries = chromeSharedLibOutcome(executablePath, found);
if (!libraries.ok) return libraries;
try {
execFileSync(executablePath, ["--version"], {
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
timeout: 5000,
killSignal: "SIGKILL",
maxBuffer: 64 * 1024,
windowsHide: true,
});
return found;
} catch (error) {
const details = chromeLaunchFailureDetails(error);
return {
name: "Chrome",
ok: false,
level: "error",
title: "Chrome cannot start",
detail: `Failed to run "${executablePath}" --version${details ? ` (${details})` : ""}.`,
hint:
"Select a working Chrome/Chromium binary for this OS and architecture with " +
"HYPERFRAMES_BROWSER_PATH, or reinstall with: npx hyperframes browser ensure --force",
path: executablePath,
};
}
}
async function checkChrome(browserPath?: string): Promise<EnvironmentCheckOutcome> {
if (browserPath) {
if (existsSync(browserPath)) {
return chromeSharedLibOutcome(browserPath, {
return chromeLaunchOutcome(browserPath, {
name: "Chrome",
ok: true,
level: "ok",
@@ -213,7 +259,7 @@ async function checkChrome(browserPath?: string): Promise<EnvironmentCheckOutcom
info = undefined;
}
if (info) {
return chromeSharedLibOutcome(info.executablePath, {
return chromeLaunchOutcome(info.executablePath, {
name: "Chrome",
ok: true,
level: "ok",