fix(qa): recover Playwright Chromium on Ubuntu 26

This commit is contained in:
Vincent Koc
2026-06-24 13:24:43 +08:00
parent 2ab3b223ed
commit fd66b44f5e
4 changed files with 194 additions and 6 deletions

View File

@@ -179,7 +179,7 @@ describe("qa test file scenario runner", () => {
expect(result.executionKind).toBe("playwright");
expect(commands.map((command) => command.args)).toEqual([
["scripts/ensure-playwright-chromium.mjs"],
["scripts/ensure-playwright-chromium.mjs", "--skip-ffmpeg"],
[
"scripts/run-vitest.mjs",
"run",

View File

@@ -121,7 +121,7 @@ function playwrightSteps(scenario: QaTestFileScenario): QaScenarioCommandStep[]
return [
{
command: process.execPath,
args: ["scripts/ensure-playwright-chromium.mjs"],
args: ["scripts/ensure-playwright-chromium.mjs", "--skip-ffmpeg"],
},
{
command: process.execPath,

View File

@@ -10,6 +10,7 @@ import { resolvePnpmRunner } from "./pnpm-runner.mjs";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const playwrightInstallBaseArgs = ["--dir", "ui", "exec", "playwright", "install"];
const executableOverrideEnvKey = "PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH";
const chromiumPackageNames = ["chromium-browser", "chromium"];
/**
* System Chromium executable paths used before downloading Playwright browsers.
*/
@@ -89,6 +90,68 @@ export function shouldInstallPlaywrightSystemDependencies(options = {}) {
);
}
function resolveLinuxPrivilegePrefix(options = {}) {
const getuid = options.getuid ?? process.getuid;
const spawnSync = options.spawnSync ?? spawnSyncImpl;
if (typeof getuid === "function" && getuid() === 0) {
return [];
}
const result = spawnSync("sudo", ["-n", "true"], { stdio: "ignore" });
if (result.status === 0) {
return ["sudo", "-n"];
}
return undefined;
}
/**
* Installs a distro Chromium package for CI images newer than Playwright's
* bundled browser support matrix.
*/
export function installLinuxSystemChromiumPackage(options = {}) {
const platform = options.platform ?? process.platform;
if (platform !== "linux") {
return 1;
}
const spawnSync = options.spawnSync ?? spawnSyncImpl;
const privilegePrefix = resolveLinuxPrivilegePrefix({
getuid: options.getuid,
spawnSync,
});
if (!privilegePrefix) {
return 1;
}
const env = {
...(options.env ?? process.env),
DEBIAN_FRONTEND: "noninteractive",
};
const cwd = options.cwd ?? repoRoot;
const stdio = options.stdio ?? "inherit";
const runAptGet = (args) => {
const command = privilegePrefix[0] ?? "apt-get";
const commandArgs =
privilegePrefix.length === 0 ? args : [...privilegePrefix.slice(1), "apt-get", ...args];
return (
spawnSync(command, commandArgs, {
cwd,
env,
stdio,
}).status ?? 1
);
};
const updateStatus = runAptGet(["update", "-qq"]);
if (updateStatus !== 0) {
return updateStatus;
}
for (const packageName of chromiumPackageNames) {
const installStatus = runAptGet(["install", "-y", packageName]);
if (installStatus === 0) {
return 0;
}
}
return 1;
}
/**
* Checks whether this module is the direct script entrypoint.
*/
@@ -135,6 +198,31 @@ export function ensurePlaywrightChromium(options = {}) {
});
return result.status ?? 1;
};
const useLinuxSystemChromiumPackage = () => {
log(`[ui-e2e] Playwright install is unavailable; installing a system Chromium package.`);
const installStatus = installLinuxSystemChromiumPackage({
cwd: options.cwd,
env,
getuid: options.getuid,
platform: options.platform,
spawnSync,
stdio: options.stdio,
});
if (installStatus !== 0) {
log(`[ui-e2e] System Chromium package install failed with status ${installStatus}.`);
return installStatus;
}
const installedSystemExecutablePath = resolveSystemChromiumExecutablePath(
existsSync,
spawnSync,
);
if (installedSystemExecutablePath) {
log(`[ui-e2e] Using system Chromium at ${installedSystemExecutablePath}.`);
return ensureFfmpeg();
}
log(`[ui-e2e] System Chromium package install completed but no runnable Chromium was found.`);
return 1;
};
const ensureFfmpeg = () => {
if (!options.ensureFfmpeg) {
return 0;
@@ -188,7 +276,7 @@ export function ensurePlaywrightChromium(options = {}) {
);
const depsStatus = runPlaywrightInstall(["chromium"], true);
if (depsStatus !== 0) {
return depsStatus;
return useLinuxSystemChromiumPackage();
}
if (existsSync(executablePath) && canRunChromiumExecutable(executablePath, spawnSync)) {
return ensureFfmpeg();
@@ -208,11 +296,12 @@ export function ensurePlaywrightChromium(options = {}) {
);
const depsStatus = runPlaywrightInstall(["chromium"], true);
if (depsStatus !== 0) {
return depsStatus;
return useLinuxSystemChromiumPackage();
}
if (existsSync(executablePath) && canRunChromiumExecutable(executablePath, spawnSync)) {
return ensureFfmpeg();
}
return useLinuxSystemChromiumPackage();
}
log(
`[ui-e2e] Playwright install completed but Chromium is still not runnable at ${executablePath}.`,
@@ -222,6 +311,12 @@ export function ensurePlaywrightChromium(options = {}) {
return ensureFfmpeg();
}
if (isDirectScriptExecution()) {
process.exitCode = ensurePlaywrightChromium({ ensureFfmpeg: true });
export function shouldEnsureFfmpegFromArgv(argv = process.argv) {
return !argv.includes("--skip-ffmpeg");
}
if (isDirectScriptExecution()) {
process.exitCode = ensurePlaywrightChromium({
ensureFfmpeg: shouldEnsureFfmpegFromArgv(),
});
}

View File

@@ -2,7 +2,9 @@
import { describe, expect, it, vi } from "vitest";
import {
ensurePlaywrightChromium,
installLinuxSystemChromiumPackage,
resolvePlaywrightInstallRunner,
shouldEnsureFfmpegFromArgv,
shouldInstallPlaywrightSystemDependencies,
} from "../../scripts/ensure-playwright-chromium.mjs";
@@ -276,6 +278,55 @@ describe("ensurePlaywrightChromium", () => {
expect(logs.join("\n")).toContain("installing Linux system dependencies");
});
it("falls back to distro Chromium when Playwright does not support the Linux runner image", () => {
const logs: string[] = [];
let installedSystemChromium = false;
const spawnSync = vi.fn((command: string, args: string[]) => {
if (command === "pnpm" && args.includes("chromium")) {
return { status: 1 };
}
if (command === "apt-get" && args.includes("update")) {
return { status: 0 };
}
if (command === "apt-get" && args.includes("chromium-browser")) {
installedSystemChromium = true;
return { status: 0 };
}
if (command === "/usr/bin/chromium-browser") {
return { status: installedSystemChromium ? 0 : 127 };
}
return { status: 1 };
});
expect(
ensurePlaywrightChromium({
cwd: "/repo",
env: { CI: "1", PATH: "/bin" },
executablePath: "/cache/chromium/chrome",
existsSync: (path: string) =>
installedSystemChromium && path === "/usr/bin/chromium-browser",
getuid: () => 0,
log: (line: string) => logs.push(line),
platform: "linux",
spawnSync,
stdio: "pipe",
systemExecutablePath: "",
}),
).toBe(0);
expect(spawnSync).toHaveBeenCalledWith(
"apt-get",
["update", "-qq"],
expect.objectContaining({ cwd: "/repo", stdio: "pipe" }),
);
expect(spawnSync).toHaveBeenCalledWith(
"apt-get",
["install", "-y", "chromium-browser"],
expect.objectContaining({ cwd: "/repo", stdio: "pipe" }),
);
expect(logs.join("\n")).toContain("installing a system Chromium package");
expect(logs.join("\n")).toContain("Using system Chromium at /usr/bin/chromium-browser");
});
it("does not install Linux system dependencies for an unprivileged local lane", () => {
const spawnSync = vi
.fn()
@@ -343,6 +394,7 @@ describe("ensurePlaywrightChromium", () => {
ensurePlaywrightChromium({
executablePath: "/cache/chromium/chrome",
existsSync: () => false,
platform: "darwin",
spawnSync: vi.fn(() => ({ status: 23 })),
stdio: "pipe",
systemExecutablePath: "",
@@ -404,4 +456,45 @@ describe("ensurePlaywrightChromium", () => {
}),
).toBe(true);
});
it("installs Linux system Chromium packages with sudo for non-root lanes", () => {
const spawnSync = vi.fn(() => ({ status: 0 }));
expect(
installLinuxSystemChromiumPackage({
cwd: "/repo",
env: { PATH: "/bin" },
getuid: () => 501,
platform: "linux",
spawnSync,
stdio: "pipe",
}),
).toBe(0);
expect(spawnSync).toHaveBeenNthCalledWith(1, "sudo", ["-n", "true"], { stdio: "ignore" });
expect(spawnSync).toHaveBeenNthCalledWith(
2,
"sudo",
["-n", "apt-get", "update", "-qq"],
expect.objectContaining({ cwd: "/repo", stdio: "pipe" }),
);
expect(spawnSync).toHaveBeenNthCalledWith(
3,
"sudo",
["-n", "apt-get", "install", "-y", "chromium-browser"],
expect.objectContaining({ cwd: "/repo", stdio: "pipe" }),
);
});
it("allows QA scenario runners to skip optional Playwright ffmpeg", () => {
expect(shouldEnsureFfmpegFromArgv(["node", "scripts/ensure-playwright-chromium.mjs"])).toBe(
true,
);
expect(
shouldEnsureFfmpegFromArgv([
"node",
"scripts/ensure-playwright-chromium.mjs",
"--skip-ffmpeg",
]),
).toBe(false);
});
});