diff --git a/packages/cli/src/browser/preflight.test.ts b/packages/cli/src/browser/preflight.test.ts index 6c63a30de4..53001c0eb5 100644 --- a/packages/cli/src/browser/preflight.test.ts +++ b/packages/cli/src/browser/preflight.test.ts @@ -34,7 +34,11 @@ describe("runEnvironmentChecks", () => { const originalFfmpegPath = process.env.HYPERFRAMES_FFMPEG_PATH; const originalFfprobePath = process.env.HYPERFRAMES_FFPROBE_PATH; + const realPlatform = process.platform; + beforeEach(() => { + // The `--version` tests below describe the Unix path; the Windows block sets its own platform. + Object.defineProperty(process, "platform", { value: "linux", configurable: true }); process.env.HYPERFRAMES_FFMPEG_PATH = process.execPath; process.env.HYPERFRAMES_FFPROBE_PATH = process.execPath; runProcess.mockReset(); @@ -42,6 +46,7 @@ describe("runEnvironmentChecks", () => { }); afterEach(() => { + Object.defineProperty(process, "platform", { value: realPlatform, configurable: true }); if (originalFfmpegPath === undefined) delete process.env.HYPERFRAMES_FFMPEG_PATH; else process.env.HYPERFRAMES_FFMPEG_PATH = originalFfmpegPath; if (originalFfprobePath === undefined) delete process.env.HYPERFRAMES_FFPROBE_PATH; @@ -211,6 +216,48 @@ describe("runEnvironmentChecks", () => { expect(result.browserInstall).toMatchObject({ pathAscii: true }); }); + describe("on Windows", () => { + beforeEach(() => { + Object.defineProperty(process, "platform", { value: "win32", configurable: true }); + }); + + it("reads Chrome's version from the file, never from a `--version` run that exits 21", async () => { + runProcess.mockImplementation((binaryPath: string, args: string[]) => { + if (binaryPath === process.execPath && args[0] === "--version") + throw Object.assign(new Error("exit 21"), { status: 21 }); + if (binaryPath === "powershell.exe") + return Promise.resolve({ stdout: "131.0.6778.85\r\n", stderr: "" }); + return Promise.resolve({ stdout: "ffmpeg version 7.1.1\n", stderr: "" }); + }); + + const result = await runEnvironmentChecks({ + includeBrowser: true, + browserPath: process.execPath, + }); + + expect(result.outcomes.find((outcome) => outcome.name === "Chrome")).toMatchObject({ + ok: true, + versionMajor: 131, + }); + }); + + it("still reports Chrome usable when its version cannot be read", async () => { + runProcess.mockImplementation((binaryPath: string) => { + if (binaryPath === "powershell.exe") throw new Error("powershell unavailable"); + return Promise.resolve({ stdout: "ffmpeg version 7.1.1\n", stderr: "" }); + }); + + const result = await runEnvironmentChecks({ + includeBrowser: true, + browserPath: process.execPath, + }); + + const chrome = result.outcomes.find((outcome) => outcome.name === "Chrome"); + expect(chrome?.ok).toBe(true); + expect(chrome?.versionMajor).toBeUndefined(); + }); + }); + it("reports Chrome as not found (no throw) when browser discovery throws on a corrupt cache", async () => { const spy = vi.spyOn(manager, "findBrowser").mockRejectedValue( Object.assign(new Error("ENOTDIR: not a directory, scandir 'chrome-headless-shell'"), { diff --git a/packages/cli/src/browser/preflight.ts b/packages/cli/src/browser/preflight.ts index 543f14cf57..a01c76862d 100644 --- a/packages/cli/src/browser/preflight.ts +++ b/packages/cli/src/browser/preflight.ts @@ -255,6 +255,34 @@ function chromeLaunchFailureDetails(error: unknown): string { .join(", "); } +// Windows chrome.exe prints nothing for --version and can exit 21 while rendering fine: read the file's version. +async function windowsChromeVersionMajor( + executablePath: string, + signal?: AbortSignal, +): Promise { + try { + const { stdout } = await runCancellableProcess( + "powershell.exe", + [ + "-NoProfile", + "-NonInteractive", + "-Command", + "(Get-Item -LiteralPath $env:HF_CHROME).VersionInfo.ProductVersion", + ], + { + signal, + timeoutMs: 10_000, + maxBufferBytes: 4 * 1024, + env: { ...process.env, HF_CHROME: executablePath }, + }, + ); + return extractMajorVersion(stdout); + } catch { + if (signal?.aborted) signal.throwIfAborted(); + return undefined; + } +} + async function chromeLaunchOutcome( executablePath: string, found: EnvironmentCheckOutcome, @@ -262,6 +290,9 @@ async function chromeLaunchOutcome( ): Promise { const libraries = await chromeSharedLibOutcome(executablePath, found, signal); if (!libraries.ok) return libraries; + if (process.platform === "win32") { + return { ...found, versionMajor: await windowsChromeVersionMajor(executablePath, signal) }; + } try { const { stdout } = await runCancellableProcess(executablePath, ["--version"], { signal,