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,hwlab-browser-doctor", { 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 ""; } }