Ctrl K
ComunidadGuías…

lib/chromium-launch-error.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// lib/chromium-launch-error.mjs — what a failed chromium.launch() says went wrong.
//
// doctor validates Chromium by launching it. It used to discard the launch
// error and report every failure as "not installed → npx playwright install
// chromium". That is right for one cause only: the binary is absent. When the
// binary is there but cannot start (a slim Linux image without the system
// libraries Chromium links against, a sandbox or container that blocks the
// process, a launch timeout), the install command finds the browser already
// downloaded and does nothing, and the user is sent back to rerun it.
//
// This reads the message Playwright throws, so doctor can name the cause and
// prescribe only what fixes it. Pure string work: no Playwright import, so it
// is testable without a browser.

const LAUNCH_PREFIX = /^browserType\.launch:\s*/;
// Linux shared objects (libnss3.so, libgbm.so.1) and Windows DLLs, as Playwright
// lists them in its host-dependency box and as the dynamic loader names them.
const SHARED_LIB = /\b[\w+-]+(?:\.[\w+-]+)*\.(?:so(?:\.\d+)*|dll)\b/gi;
const MAX_DETAIL = 200;
// ANSI escape sequences (colors, dim, bold, etc.) that Playwright includes in
// error messages starting from some version around 1.64.
// eslint-disable-next-line no-control-regex
const ANSI_ESCAPE = /\x1b\[[0-9;]*m/g;

function truncate(s) {
  return s.length > MAX_DETAIL ? `${s.slice(0, MAX_DETAIL - 1)}…` : s;
}

// The browser's own stderr, as Playwright echoes it under "Browser logs:" as
// `[pid=N][err] ...`. It is the line that says why the process died; the rest
// of the message is the launch command line and Playwright's call log.
function firstBrowserStderrLine(message) {
  for (const line of message.split('\n')) {
    // Strip ANSI escape codes before matching — Playwright 1.64+ wraps log
    // lines in dim codes ([2m...[22m).
    const clean = line.replace(ANSI_ESCAPE, '');
    const m = clean.match(/^\s*(?:-\s*)?\[pid=\d+\]\[err\]\s*(.*\S)/);
    if (m) return m[1];
  }
  return null;
}

/**
 * Classify the error chromium.launch() threw.
 *
 * @param {unknown} err - The thrown value (an Error, or anything else).
 * @returns {{ kind: 'missing-executable', path: string|null }
 *   | { kind: 'missing-libraries', libraries: string[] }
 *   | { kind: 'launch-failed', detail: string }}
 */
export function classifyChromiumLaunchError(err) {
  const message = String(err?.message ?? err ?? '');

  const exe = message.match(/Executable doesn't exist at (\S+)/);
  if (exe) return { kind: 'missing-executable', path: exe[1] };

  // Linux says "...missing dependencies to run browsers.", Windows says
  // "...missing dependencies!" and lists DLLs.
  const hostDeps = /Host system is missing dependencies/.test(message);
  const loaderLine = message.split('\n').find((line) => /error while loading shared libraries/.test(line));
  if (hostDeps || loaderLine) {
    // The loader line names one library; the host-dependency box lists them all.
    // Neither the launch command line nor the browser path holds a .so name, so
    // only those two sources are scanned.
    const source = hostDeps ? message.slice(message.indexOf('Host system is missing')) : loaderLine;
    const libraries = [...new Set(source.match(SHARED_LIB) || [])];
    return { kind: 'missing-libraries', libraries };
  }

  const stderr = firstBrowserStderrLine(message);
  const firstLine = message.replace(LAUNCH_PREFIX, '').split('\n').find((line) => line.trim()) || 'unknown error';
  return { kind: 'launch-failed', detail: truncate((stderr || firstLine).trim()) };
}