test(studio): retain Chrome launch diagnostics (#6077)

* test(studio): retain Chrome launch diagnostics

* fix(studio): read Chrome's version from the file on Windows instead of running it

* fix(studio): shorten the Chrome version probe
This commit is contained in:
Miguel Ángel
2026-10-23 01:55:52 +00:00
committed by GitHub
parent 1accb709ff
commit 821003b112
4 changed files with 154 additions and 14 deletions
+42 -2
View File
@@ -2,7 +2,10 @@ import { TimeoutError, type Browser } from "puppeteer-core";
import { describe, expect, it, vi } from "vitest";
import { closeTestChrome, launchTestChrome } from "./chromeTestUtils";
vi.mock("../vite.browser", () => ({ findSystemChrome: () => "/test/chrome" }));
vi.mock("../vite.browser", () => ({
findSystemChrome: () => "/test/chrome",
browserVersion: () => "Chrome 152.0.7977.30",
}));
const fakeBrowser = (close: () => Promise<void>) => {
const kill = vi.fn();
@@ -27,6 +30,43 @@ describe("closeTestChrome", () => {
});
describe("launchTestChrome", () => {
it("names the selected browser and the failed attempt before retrying", async () => {
const info = vi.spyOn(console, "info").mockImplementation(() => {});
const error = vi.spyOn(console, "error").mockImplementation(() => {});
vi.useFakeTimers();
try {
const { browser } = fakeBrowser(async () => undefined);
const failure = new TimeoutError("WS endpoint was not announced");
const launch = vi
.fn()
.mockImplementationOnce(async () => {
vi.advanceTimersByTime(20_000);
throw failure;
})
.mockResolvedValueOnce(browser);
await expect(launchTestChrome(launch)).resolves.toBe(browser);
expect(info.mock.calls).toEqual([
["[studio-test-chrome] launch 1/2: /test/chrome (Chrome 152.0.7977.30)"],
["[studio-test-chrome] launch 2/2: /test/chrome (Chrome 152.0.7977.30)"],
["[studio-test-chrome] launch 2/2 ready after 0 ms"],
]);
expect(error.mock.calls).toEqual([
["[studio-test-chrome] launch 1/2 failed after 20000 ms: WS endpoint was not announced"],
]);
} finally {
vi.useRealTimers();
info.mockRestore();
error.mockRestore();
}
});
it("stops after the second stuck launch and preserves its error", async () => {
const failure = new TimeoutError("still stuck");
const launch = vi.fn().mockRejectedValue(failure);
await expect(launchTestChrome(launch)).rejects.toBe(failure);
expect(launch).toHaveBeenCalledTimes(2);
});
it("bounds each launch and tries once more after a stuck one", async () => {
const { browser } = fakeBrowser(async () => undefined);
const launch = vi
@@ -35,7 +75,7 @@ describe("launchTestChrome", () => {
.mockResolvedValueOnce(browser);
await expect(launchTestChrome(launch)).resolves.toBe(browser);
expect(launch).toHaveBeenCalledTimes(2);
expect(launch.mock.calls[0]![0]).toMatchObject({ timeout: 20_000 });
expect(launch.mock.calls[0]![0]).toMatchObject({ timeout: 20_000, dumpio: true });
});
it("tries once more after a stuck launch that Windows reports as already running", async () => {
+25 -8
View File
@@ -1,11 +1,12 @@
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import puppeteer, { TimeoutError, type Browser, type Page } from "puppeteer-core";
import { findSystemChrome } from "../vite.browser";
import { browserVersion, findSystemChrome } from "../vite.browser";
const require = createRequire(import.meta.url);
const LAUNCH_TIMEOUT_MS = 20_000;
const LAUNCH_ATTEMPTS = 2;
/** Headless Chrome with the e2e launcher's flags. A launch stuck past 20 s (seen on Windows runners)
* is killed and tried once more. On Windows Puppeteer reports it as "already running", not a timeout. */
@@ -17,17 +18,33 @@ export async function launchTestChrome(
const options = {
executablePath,
headless: true,
dumpio: true,
args: ["--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu"],
timeout: LAUNCH_TIMEOUT_MS,
};
const startedAt = Date.now();
try {
return await launch(options);
} catch (error) {
const stuck = error instanceof TimeoutError || Date.now() - startedAt >= LAUNCH_TIMEOUT_MS;
if (!stuck) throw error;
return launch(options);
const version = browserVersion(executablePath) ?? "version unavailable";
for (let attempt = 1; attempt <= LAUNCH_ATTEMPTS; attempt += 1) {
const startedAt = Date.now();
console.info(
`[studio-test-chrome] launch ${attempt}/${LAUNCH_ATTEMPTS}: ${executablePath} (${version})`,
);
try {
const browser = await launch(options);
console.info(
`[studio-test-chrome] launch ${attempt}/${LAUNCH_ATTEMPTS} ready after ${Date.now() - startedAt} ms`,
);
return browser;
} catch (error) {
const elapsed = Date.now() - startedAt;
const reason = error instanceof Error ? error.message : String(error);
console.error(
`[studio-test-chrome] launch ${attempt}/${LAUNCH_ATTEMPTS} failed after ${elapsed} ms: ${reason}`,
);
const stuck = error instanceof TimeoutError || elapsed >= LAUNCH_TIMEOUT_MS;
if (!stuck || attempt === LAUNCH_ATTEMPTS) throw error;
}
}
throw new Error("Chrome launch attempts exhausted");
}
/** Closes a test Chrome, killing it when close hangs (seen on Windows runners) so the test never times out. */
+12 -4
View File
@@ -84,12 +84,19 @@ function findPuppeteerCacheChrome(
return undefined;
}
function browserVersion(
export function browserVersion(
executable: string,
run: (file: string) => string = (file) => execFileSync(file, ["--version"], { encoding: "utf8" }),
run: typeof execFileSync = execFileSync,
platform: NodeJS.Platform = process.platform,
): string | undefined {
const options = { encoding: "utf8" as const, timeout: 5_000, windowsHide: true };
const env = { ...process.env, HYPERFRAMES_CHROME_VERSION_PATH: executable };
const script =
"(Get-Item -LiteralPath $env:HYPERFRAMES_CHROME_VERSION_PATH).VersionInfo.ProductVersion";
try {
return run(executable).trim() || undefined;
if (platform !== "win32") return run(executable, ["--version"], options).trim() || undefined;
const v = run("powershell.exe", ["-NoProfile", "-Command", script], { ...options, env }).trim();
return /^\d+(\.\d+){3}$/.test(v) ? `Chrome/${v}` : undefined;
} catch {
return undefined;
}
@@ -110,7 +117,8 @@ export function findSystemChrome(
env: NodeJS.ProcessEnv = process.env,
pathExists: (path: string) => boolean = existsSync,
platform: NodeJS.Platform = process.platform,
versionOf: (path: string) => string | undefined = browserVersion,
versionOf: (path: string) => string | undefined = (path) =>
browserVersion(path, undefined, platform),
readDirectories: (path: string) => string[] = readdirSync,
): string | undefined {
const override = [
@@ -0,0 +1,75 @@
import { expect, it, vi } from "vitest";
import type { ExecFileSyncOptionsWithStringEncoding } from "node:child_process";
import { browserVersion, findSystemChrome } from "./vite.browser";
it("bounds the Unix version command", () => {
const run = vi.fn(() => "Google Chrome 152.0.7977.42\n");
expect(browserVersion("/test/chrome", run, "linux")).toBe("Google Chrome 152.0.7977.42");
expect(run).toHaveBeenCalledWith("/test/chrome", ["--version"], {
encoding: "utf8",
timeout: 5_000,
windowsHide: true,
});
});
it("reads Windows executable metadata without executing the browser or interpolating its path", () => {
const executable = "C:\\Program Files\\Browser's $(value)&[x]\\chrome.exe";
const run = vi.fn(
(_file: string, _args: string[], _options: ExecFileSyncOptionsWithStringEncoding) =>
"152.0.7977.42\r\n",
);
expect(browserVersion(executable, run, "win32")).toBe("Chrome/152.0.7977.42");
expect(
run.mock.calls.map(([file, args, options]) => ({
file,
args,
encoding: options.encoding,
timeout: options.timeout,
windowsHide: options.windowsHide,
versionPath: options.env?.["HYPERFRAMES_CHROME_VERSION_PATH"],
})),
).toEqual([
{
file: "powershell.exe",
args: [
"-NoProfile",
"-Command",
"(Get-Item -LiteralPath $env:HYPERFRAMES_CHROME_VERSION_PATH).VersionInfo.ProductVersion",
],
encoding: "utf8",
timeout: 5_000,
windowsHide: true,
versionPath: executable,
},
]);
});
it.each(["Opening in existing browser session.", "", "not a version"])(
"does not report Windows output %j as a version",
(output) => {
expect(browserVersion("chrome.exe", () => output, "win32")).toBeUndefined();
},
);
it("reports an unavailable version when the probe fails", () => {
expect(
browserVersion(
"/test/chrome",
() => {
throw new Error("probe timed out");
},
"linux",
),
).toBeUndefined();
});
it.runIf(process.platform === "win32")(
"reads the selected real Windows Chrome version from metadata",
() => {
const executable = findSystemChrome();
expect(executable).toBeTruthy();
const version = browserVersion(executable!);
console.info(`[studio-version-probe] ${executable} (${version})`);
expect(version).toMatch(/^Chrome\/\d+\.\d+\.\d+\.\d+$/);
},
);