{"record":{"id":"f9ab2e8b1704f9e7","repo":"pbakaus/impeccable","slug":"puppeteer-is-required-for-url-scanning-install-n","errorCode":null,"errorMessage":"puppeteer is required for URL scanning. Install: npm install puppeteer","messagePattern":"puppeteer is required for URL scanning\\. Install: npm install puppeteer","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/engine/engines/browser/detect-url.mjs","lineNumber":181,"sourceCode":"// ---------------------------------------------------------------------------\n\nasync function detectUrl(url, options = {}) {\n  const profile = options?.profile;\n  const waitUntil = options?.waitUntil || 'networkidle0';\n  const settleMs = Number.isFinite(options?.settleMs) ? options.settleMs : 0;\n  const viewport = options?.viewport || { width: 1280, height: 800 };\n  const externalBrowser = options?.browser || null;\n  let puppeteer;\n  if (!externalBrowser) {\n    try {\n      puppeteer = await profileStepAsync(profile, {\n        engine: 'browser',\n        phase: 'setup',\n        ruleId: 'import-puppeteer',\n        target: url,\n      }, () => import('puppeteer'));\n    } catch {\n      throw new Error('puppeteer is required for URL scanning. Install: npm install puppeteer');\n    }\n  }\n\n  // Read the browser detection script — reuse it instead of reimplementing\n  const browserScriptPath = path.resolve(\n    path.dirname(fileURLToPath(import.meta.url)),\n    '..',\n    '..',\n    'detect-antipatterns-browser.js'\n  );\n  let browserScript;\n  try {\n    browserScript = profileStep(profile, {\n      engine: 'browser',\n      phase: 'setup',\n      ruleId: 'read-browser-script',\n      target: url,\n    }, () => fs.readFileSync(browserScriptPath, 'utf-8'));","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/cli/engine/engines/browser/detect-url.mjs#L163-L199","documentation":"Thrown by detectUrl() when the optional 'puppeteer' dependency cannot be dynamically imported, and the caller did not supply a pre-launched browser via options.browser. Puppeteer is a peer/optional dependency for URL scanning because the CLI's file/HTML path is the primary mode and bundling Chromium would bloat every install. The error fires only for the URL (live-site) detection code path.","triggerScenarios":"Calling detectUrl(url) (or the CLI's URL-scan subcommand) without 'puppeteer' installed in node_modules AND without passing options.browser as an already-launched puppeteer Browser instance. The dynamic import('puppeteer') rejects, the catch re-throws this exact message.","commonSituations":"Running the impeccable CLI in a minimal install that excluded optional deps; CI where puppeteer was pruned to save space; a monorepo where puppeteer is hoisted to a parent node_modules the dynamic import cannot resolve; using a URL target for the first time after only testing local files.","solutions":["Run `npm install puppeteer` (or `bun add puppeteer`) in the project consuming the CLI so the dynamic import resolves.","If you cannot install Chromium in the environment, pass options.browser — a puppeteer Browser you launched yourself — so the import is skipped entirely.","Verify the install with `node -e \"import('puppeteer')\"` exiting cleanly before retrying the URL scan.","In monorepos, ensure puppeteer is resolvable from the cli/engine/engines/browser/ directory (install at the workspace root or in the package that runs the CLI)."],"exampleFix":"// before\nconst results = await detectUrl('https://example.com');\n\n// after — install puppeteer, OR inject an existing browser\nconst puppeteer = await import('puppeteer');\nconst browser = await puppeteer.launch({ headless: true });\nconst results = await detectUrl('https://example.com', { browser });","handlingStrategy":"validation","validationCode":"async function canScanUrls() {\n  try { await import('puppeteer'); return true; }\n  catch { return false; }\n}\n// or check for an externally provided browser:\nconst hasBrowser = Boolean(options.browser);","typeGuard":"/** True if the caller can run detectUrl without installing puppeteer. */\nfunction hasUrlScanRuntime(options = {}) {\n  return Boolean(options?.browser); // puppeteer availability is async-checked separately\n}","tryCatchPattern":"try {\n  const results = await detectUrl(url, options);\n} catch (err) {\n  if (/puppeteer is required/.test(err.message)) {\n    console.error('Install puppeteer or pass options.browser to scan URLs.');\n  } else throw err;\n}","preventionTips":["Gate URL-scan features behind a canScanUrls() probe before exposing them in a UI.","Document puppeteer as an optional peer dependency in your consuming package.","Accept an options.browser injection point so CI can reuse one browser across scans."],"tags":["dependency","puppeteer","browser-engine","optional-dep","dynamic-import"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}