Files
pikasTech-HWLAB/scripts/src/browser-launcher.mjs
T
2026-06-04 14:51:54 +08:00

173 lines
5.3 KiB
JavaScript

import fs from "node:fs";
import { execFileSync } from "node:child_process";
import path from "node:path";
import { pathToFileURL } from "node:url";
export const playwrightDependencyCommand = "npm install";
export const playwrightDependencySummary =
"Install repository dependencies before running browser smokes: npm install provides the declared playwright package; the Code Queue image may also provide the same package globally.";
const chromiumExecutableEnvKeys = Object.freeze([
"HWLAB_PLAYWRIGHT_CHROMIUM",
"PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH",
"CHROMIUM_PATH"
]);
const chromiumExecutableCandidates = Object.freeze([
"/snap/bin/chromium",
"/usr/bin/chromium-browser",
"/usr/bin/chromium",
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable"
]);
export async function importPlaywright() {
try {
return normalizePlaywrightModule(await import("playwright"));
} catch (localError) {
const globalRoot = npmGlobalRoot();
if (globalRoot) {
try {
return normalizePlaywrightModule(await import(pathToFileURL(path.join(globalRoot, "playwright/index.js")).href));
} catch {
// Keep the local resolution error so remediation points at this repository's package.json.
}
}
throw localError;
}
}
export async function launchChromium(chromium, options = {}) {
const plan = chromiumLaunchPlan();
const launchOptions = {
...options,
headless: options.headless ?? true,
args: ["--no-sandbox", ...(options.args ?? [])],
...(plan.executablePath ? { executablePath: plan.executablePath } : {})
};
try {
const browser = await chromium.launch(launchOptions);
browser.hwlabLaunchPlan = { ...plan, failureCode: null };
return browser;
} catch (error) {
error.hwlabBrowserLaunch = {
...plan,
failureCode: classifyBrowserLaunchFailure(error),
remediation: browserLaunchRemediation(plan)
};
throw error;
}
}
export async function browserDoctor() {
let playwright;
try {
playwright = await importPlaywright();
} catch (error) {
return {
status: "fail",
failureCode: "playwright-import-failed",
playwrightPackageSource: "missing",
browser: chromiumLaunchPlan(),
remediation: [playwrightDependencyCommand],
error: error instanceof Error ? error.message : String(error)
};
}
const browser = await launchChromium(playwright.chromium).catch((error) => ({ error }));
if (browser?.error) {
const plan = browser.error.hwlabBrowserLaunch ?? chromiumLaunchPlan();
return {
status: "fail",
failureCode: plan.failureCode ?? classifyBrowserLaunchFailure(browser.error),
playwrightPackageSource: "playwright",
browser: plan,
remediation: browserLaunchRemediation(plan),
error: browser.error instanceof Error ? browser.error.message : String(browser.error)
};
}
try {
const page = await browser.newPage();
await page.goto("data:text/html,<title>hwlab-browser-doctor</title>", { waitUntil: "domcontentloaded", timeout: 5000 });
await page.close();
} finally {
await browser.close();
}
return {
status: "pass",
failureCode: null,
playwrightPackageSource: "playwright",
browser: browser.hwlabLaunchPlan ?? chromiumLaunchPlan(),
remediation: []
};
}
export function chromiumLaunchPlan(env = process.env) {
const candidatePaths = chromiumExecutableCandidates.filter((candidate) => fs.existsSync(candidate));
for (const key of chromiumExecutableEnvKeys) {
const value = env[key];
if (value && fs.existsSync(value)) {
return {
browserSource: "env",
executablePath: value,
executablePathEnvKey: key,
fallbackUsed: key !== "HWLAB_PLAYWRIGHT_CHROMIUM",
candidatePaths
};
}
}
for (const candidate of chromiumExecutableCandidates) {
if (fs.existsSync(candidate)) {
return {
browserSource: "system-chromium",
executablePath: candidate,
executablePathEnvKey: null,
fallbackUsed: true,
candidatePaths
};
}
}
return {
browserSource: "playwright-cache",
executablePath: "",
executablePathEnvKey: null,
fallbackUsed: false,
candidatePaths
};
}
export function classifyBrowserLaunchFailure(error) {
const message = error instanceof Error ? error.message : String(error);
if (/Executable doesn't exist|download new browsers|playwright install/iu.test(message)) return "browser-cache-missing";
if (/spawn|ENOENT|permission denied|EACCES/iu.test(message)) return "browser-executable-unavailable";
return "browser-launch-failed";
}
export function browserLaunchRemediation(plan = chromiumLaunchPlan()) {
if (plan.executablePath) return [`using ${plan.executablePath}`];
return [
"Set HWLAB_PLAYWRIGHT_CHROMIUM=/snap/bin/chromium when system Chromium exists.",
"Install Playwright browsers with npx playwright install when no system Chromium is available."
];
}
function normalizePlaywrightModule(module) {
if (module?.chromium) return module;
if (module?.default?.chromium) return module.default;
return module;
}
function npmGlobalRoot() {
try {
return execFileSync("npm", ["root", "-g"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
timeout: 5000
}).trim();
} catch {
return "";
}
}