santifer/career-ops · error · Error

--verify could not launch Chromium (run "npx playwright…

Error message

--verify could not launch Chromium (run "npx playwright install chromium" or re-run without --verify): ${err.message}

What it means

After Playwright imports successfully, scan.mjs launches headless Chromium for --verify; if chromium.launch() rejects, this error is thrown. Unlike the import error, this means Playwright itself is present but the browser could not start (missing browser executable, missing system libraries, or sandbox restrictions).

Solutions

  1. Run `npx playwright install chromium` (add `--with-deps` on Linux to install system libraries)
  2. On bare containers, install OS deps: `npx playwright install-deps chromium` or use the official Playwright Docker image
  3. If the executable path is custom, set PLAYWRIGHT_BROWSERS_PATH or launch with executablePath
  4. Re-run the scan without --verify, which the message itself suggests as a fallback

Example fix

// before (Dockerfile)
RUN npm ci
CMD ["node", "scan.mjs", "--verify"]   # launch fails: missing libs
// after (Dockerfile)
RUN npm ci && npx playwright install --with-deps chromium
CMD ["node", "scan.mjs", "--verify"]
Defensive patterns

Strategy: try-catch

Validate before calling

let chromium;
try { ({ chromium } = await import('playwright')); } catch { /* handled elsewhere */ }
if (chromium) {
  try { await chromium.launch({ headless: true }); console.log('chromium OK'); }
  catch (e) { console.error('Chromium cannot launch:', e.message); }
}

Try / catch

try {
  browser = await chromium.launch({ headless: true });
} catch (err) {
  if (err.message.includes('--verify could not launch') || /Executable doesn't exist|missing libraries/i.test(err.cause?.message ?? '')) {
    console.error('Run `npx playwright install --with-deps chromium` or re-run without --verify');
    process.exit(2);
  }
  throw err;
}

Prevention

When it happens

Trigger: chromium.launch({ headless: true }) fails because Chromium binaries were never downloaded (`playwright install` skipped), host OS libraries are missing (common in minimal Docker images), or launch is blocked in a restricted sandbox/container.

Common situations: CI containers without libnss3/libatk etc.; environments with no HOME or no write access to the Playwright cache; running inside restricted Docker without --no-sandbox workarounds; partial browser download.

Understand the failure class

Background: "X is not installed. Please install it with pip install Y": missing optional dependency errors — ImportError/ValueError raised when a library's optional extra was never installed — this error's family across 22 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@e7abd431fc (2026-09-16). Data as JSON: /api/errors/9ceb89b61cbe7a54. Report an issue: GitHub.

Appendix: source

Thrown at scan.mjs:2701

  let createHeadedPageProvider;
  let newLivenessPage;
  let jitteredDelayMs;
  let sleep;
  try {
    ({ chromium } = await import('playwright'));
    ({ checkUrlLiveness, checkUrlLivenessWithFallback, createHeadedPageProvider, newLivenessPage, jitteredDelayMs, sleep } = await import('./liveness-browser.mjs'));
  } catch (err) {
    throw new Error(
      `--verify requires Playwright with Chromium (run "npx playwright install chromium"): ${err.message}`,
      { cause: err },
    );
  }

  let browser;
  try {
    browser = await chromium.launch({ headless: true });
  } catch (err) {
    throw new Error(
      `--verify could not launch Chromium (run "npx playwright install chromium" or re-run without --verify): ${err.message}`,
      { cause: err },
    );
  }

  // Three permanent buckets + one transient passthrough:
  //   verified  → active pages and transient nav errors (retry next scan)
  //   expired   → classifier-confirmed dead postings (HTTP 4xx, redirect markers,
  //               body patterns, listing pages, insufficient content)
  //   dropped   → page loaded but classifier saw no Apply control. --verify is an
  //               opt-in stricter filter; keeping these defeats the purpose.
  //   invalid   → up-front URL guard rejections (malformed / non-http / private)
  const verified = [];
  const expired = [];
  const dropped = [];
  const invalid = [];
  const migrated = [];

View on GitHub (pinned to e7abd431fc)