Files
adolf/scripts/ensure-playwright-chromium.mjs
alvis bedb527145
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Vendor OpenClaw source as Adolf fork baseline
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
2026-07-05 09:36:54 +00:00

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(),
});
}