Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/cli/src/browser/preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
50 changes: 48 additions & 2 deletions packages/cli/src/browser/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading