mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-30 04:33:36 +00:00
323 lines
10 KiB
JavaScript
323 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
// Ensures Playwright Chromium is installed or a usable system browser is available.
|
|
import { spawnSync as spawnSyncImpl } from "node:child_process";
|
|
import { existsSync as existsSyncImpl, realpathSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { chromium } from "playwright";
|
|
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.
|
|
*/
|
|
export const systemChromiumExecutableCandidates = [
|
|
"/snap/bin/chromium",
|
|
"/usr/bin/chromium-browser",
|
|
"/usr/bin/chromium",
|
|
"/usr/bin/google-chrome",
|
|
"/usr/bin/google-chrome-stable",
|
|
];
|
|
|
|
/**
|
|
* Checks whether a Chromium executable can start enough to print its version.
|
|
*/
|
|
export function canRunChromiumExecutable(executablePath, spawnSync = spawnSyncImpl) {
|
|
const result = spawnSync(executablePath, ["--version"], {
|
|
stdio: "ignore",
|
|
});
|
|
return result.status === 0;
|
|
}
|
|
|
|
/**
|
|
* Resolves the first runnable system Chromium executable path.
|
|
*/
|
|
export function resolveSystemChromiumExecutablePath(
|
|
existsSync = existsSyncImpl,
|
|
spawnSync = spawnSyncImpl,
|
|
) {
|
|
return (
|
|
systemChromiumExecutableCandidates.find(
|
|
(candidate) => existsSync(candidate) && canRunChromiumExecutable(candidate, spawnSync),
|
|
) ?? ""
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Builds the pnpm runner invocation for Playwright browser install.
|
|
*/
|
|
export function resolvePlaywrightInstallRunner(options = {}) {
|
|
const env = options.env ?? process.env;
|
|
const targets = options.targets ?? ["chromium"];
|
|
return resolvePnpmRunner({
|
|
comSpec: options.comSpec,
|
|
env,
|
|
npmExecPath: env === process.env ? env.npm_execpath : (env.npm_execpath ?? ""),
|
|
platform: options.platform,
|
|
pnpmArgs: [
|
|
...playwrightInstallBaseArgs,
|
|
...(options.withDeps ? ["--with-deps"] : []),
|
|
...targets,
|
|
],
|
|
});
|
|
}
|
|
|
|
function isTruthyEnvFlag(value) {
|
|
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
}
|
|
|
|
/**
|
|
* Reports whether Linux system dependencies should be installed with Chromium.
|
|
*/
|
|
export function shouldInstallPlaywrightSystemDependencies(options = {}) {
|
|
const env = options.env ?? process.env;
|
|
const platform = options.platform ?? process.platform;
|
|
const getuid = options.getuid ?? process.getuid;
|
|
if (platform !== "linux") {
|
|
return false;
|
|
}
|
|
if (typeof getuid === "function" && getuid() === 0) {
|
|
return true;
|
|
}
|
|
return (
|
|
isTruthyEnvFlag(env.CI) ||
|
|
isTruthyEnvFlag(env.GITHUB_ACTIONS) ||
|
|
isTruthyEnvFlag(env.OPENCLAW_TESTBOX)
|
|
);
|
|
}
|
|
|
|
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.
|
|
*/
|
|
export function isDirectScriptExecution(
|
|
argvEntry = process.argv[1],
|
|
modulePath = fileURLToPath(import.meta.url),
|
|
realpath = realpathSync.native,
|
|
) {
|
|
if (!argvEntry) {
|
|
return false;
|
|
}
|
|
try {
|
|
return realpath(argvEntry) === realpath(modulePath);
|
|
} catch {
|
|
return resolve(argvEntry) === resolve(modulePath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures a runnable Chromium exists for Playwright-based UI tests.
|
|
*/
|
|
export function ensurePlaywrightChromium(options = {}) {
|
|
const env = options.env ?? process.env;
|
|
const executableOverride =
|
|
typeof env[executableOverrideEnvKey] === "string" ? env[executableOverrideEnvKey].trim() : "";
|
|
const executablePath = options.executablePath ?? chromium.executablePath();
|
|
const existsSync = options.existsSync ?? existsSyncImpl;
|
|
const log = options.log ?? console.error;
|
|
const spawnSync = options.spawnSync ?? spawnSyncImpl;
|
|
const runPlaywrightInstall = (targets = ["chromium"], withDeps = false) => {
|
|
const runner = resolvePlaywrightInstallRunner({
|
|
comSpec: options.comSpec,
|
|
env,
|
|
platform: options.platform,
|
|
targets,
|
|
withDeps,
|
|
});
|
|
const result = spawnSync(runner.command, runner.args, {
|
|
cwd: options.cwd ?? repoRoot,
|
|
env,
|
|
shell: runner.shell,
|
|
stdio: options.stdio ?? "inherit",
|
|
windowsVerbatimArguments: runner.windowsVerbatimArguments,
|
|
});
|
|
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;
|
|
}
|
|
const status = runPlaywrightInstall(["ffmpeg"]);
|
|
if (status !== 0) {
|
|
log(`[ui-e2e] Playwright ffmpeg install failed with status ${status}.`);
|
|
}
|
|
return status;
|
|
};
|
|
|
|
if (executableOverride) {
|
|
if (existsSync(executableOverride) && canRunChromiumExecutable(executableOverride, spawnSync)) {
|
|
return ensureFfmpeg();
|
|
}
|
|
log(
|
|
`[ui-e2e] ${executableOverrideEnvKey} points to ${executableOverride}, but that browser is not runnable.`,
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
if (existsSync(executablePath) && canRunChromiumExecutable(executablePath, spawnSync)) {
|
|
return ensureFfmpeg();
|
|
}
|
|
|
|
const systemExecutablePath =
|
|
options.systemExecutablePath ?? resolveSystemChromiumExecutablePath(existsSync, spawnSync);
|
|
if (systemExecutablePath && canRunChromiumExecutable(systemExecutablePath, spawnSync)) {
|
|
log(`[ui-e2e] Using system Chromium at ${systemExecutablePath}.`);
|
|
return ensureFfmpeg();
|
|
}
|
|
|
|
if (env.OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM === "1") {
|
|
log(
|
|
`[ui-e2e] Playwright Chromium is missing at ${executablePath}; OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM=1 leaves the lane skipped.`,
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
log(`[ui-e2e] Playwright Chromium is not runnable at ${executablePath}; installing chromium.`);
|
|
const canInstallSystemDependencies = shouldInstallPlaywrightSystemDependencies({
|
|
env,
|
|
getuid: options.getuid,
|
|
platform: options.platform,
|
|
});
|
|
const status = runPlaywrightInstall();
|
|
if (status !== 0) {
|
|
if (canInstallSystemDependencies) {
|
|
log(
|
|
`[ui-e2e] Chromium install failed in a Linux CI/root lane; installing Linux system dependencies.`,
|
|
);
|
|
const depsStatus = runPlaywrightInstall(["chromium"], true);
|
|
if (depsStatus !== 0) {
|
|
return useLinuxSystemChromiumPackage();
|
|
}
|
|
if (existsSync(executablePath) && canRunChromiumExecutable(executablePath, spawnSync)) {
|
|
return ensureFfmpeg();
|
|
}
|
|
log(
|
|
`[ui-e2e] Playwright install completed but Chromium is still not runnable at ${executablePath}.`,
|
|
);
|
|
return 1;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
if (!existsSync(executablePath) || !canRunChromiumExecutable(executablePath, spawnSync)) {
|
|
if (canInstallSystemDependencies) {
|
|
log(
|
|
`[ui-e2e] Chromium is installed but still cannot start; installing Linux system dependencies.`,
|
|
);
|
|
const depsStatus = runPlaywrightInstall(["chromium"], true);
|
|
if (depsStatus !== 0) {
|
|
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}.`,
|
|
);
|
|
return 1;
|
|
}
|
|
return ensureFfmpeg();
|
|
}
|
|
|
|
export function shouldEnsureFfmpegFromArgv(argv = process.argv) {
|
|
return !argv.includes("--skip-ffmpeg");
|
|
}
|
|
|
|
if (isDirectScriptExecution()) {
|
|
process.exitCode = ensurePlaywrightChromium({
|
|
ensureFfmpeg: shouldEnsureFfmpegFromArgv(),
|
|
});
|
|
}
|