{"record":{"id":"a3ce124229a33401","repo":"santifer/career-ops","slug":"fetch-playwright-error-e-message-falling-b","errorCode":null,"errorMessage":"[fetch] Playwright error: ${e.message} — falling back to plain fetch.","messagePattern":"\\[fetch\\] Playwright error: (.+?) — falling back to plain fetch\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"openrouter-runner.mjs","lineNumber":443,"sourceCode":"    ({ chromium } = await import('playwright'));\n  } catch {\n    console.warn('[fetch] Playwright unavailable — falling back to plain fetch.');\n  }\n\n  if (chromium) {\n    let browser;\n    try {\n      browser = await chromium.launch({ headless: true });\n      const page = await browser.newPage();\n      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });\n      await page.waitForTimeout(2000); // wait for SPA render\n      const text = await page.evaluate(() => {\n        document.querySelectorAll('script,style,nav,footer,header').forEach(el => el.remove());\n        return (document.body?.innerText || document.body?.textContent || '').replace(/\\s+/g, ' ').trim();\n      });\n      return text.slice(0, 16_000);\n    } catch (e) {\n      console.warn(`[fetch] Playwright error: ${e.message} — falling back to plain fetch.`);\n    } finally {\n      if (browser) await browser.close().catch(() => {});\n    }\n  }\n\n  // Plain HTTP fallback\n  try {\n    const r = await fetch(url, {\n      headers: { 'User-Agent': DEFAULT_USER_AGENT }\n    });\n    if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);\n    const html = await r.text();\n    return html.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim().slice(0, 16_000);\n  } catch (e) {\n    throw new Error(`Could not fetch job page: ${e.message}`);\n  }\n}\n","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/openrouter-runner.mjs#L425-L461","documentation":"Fallback warning in openrouter-runner.mjs's fetchJobPage: the Playwright headless-browser extraction of a job page threw (launch failure, navigation timeout, page.evaluate error, or browser crashed), and the runner logs this message and falls through to a plain HTTP fetch of the same URL.","triggerScenarios":"page.goto times out (30s-ish, heavy JS pages), Playwright browser binary not installed, evaluate throws because document.body is null after removing script/nav elements, or browser.close fails after a crash.","commonSituations":"CI/container without `npx playwright install` browsers; JS-rendered SPA job boards that block headless Chrome; sites with anti-bot measures; slow network causing goto timeout.","solutions":["Install browser binaries: npx playwright install chromium","Read err.message: timeout → increase goto timeout or use waitUntil:'domcontentloaded'; missing body → guard `document.body?.innerText`","If Playwright is unavailable, ensure the plain-fetch fallback yields usable text and expect degraded extraction for JS-heavy pages","For blocked pages, set a realistic user agent or run headed mode"],"exampleFix":"// before\nconst text = await page.evaluate(() => {\n  document.querySelectorAll('script,style,nav,footer,header').forEach(el => el.remove());\n  return (document.body?.innerText || document.body?.textContent || '').replace(/\\s+/g, ' ').trim();\n});\n// after\nconst text = await page.evaluate(() => {\n  document.querySelectorAll('script,style,nav,footer,header').forEach(el => el.remove());\n  return ((document.body?.innerText ?? document.body?.textContent) || '').replace(/\\s+/g, ' ').trim();\n}); // plus try/catch around page.evaluate to return '' instead of throwing","handlingStrategy":"fallback","validationCode":"import { chromium } from 'playwright';\ntry { const b = await chromium.launch(); await b.close(); } catch { console.warn('Playwright browsers not installed; run: npx playwright install chromium'); }","typeGuard":"function browserAvailable(mod) { try { mod.chromium.executablePath(); return true; } catch { return false; } }","tryCatchPattern":"let text; try { text = await extractWithPlaywright(url); } catch (e) { console.warn(`[fetch] Playwright error: ${e.message} — falling back`); text = await plainFetch(url); }","preventionTips":["Run `npx playwright install` in every environment/container image","Prefer waitUntil:'domcontentloaded' plus a settle delay over 'networkidle'","Guard optional DOM access (document.body?.innerText) inside page.evaluate","Set a realistic user agent for bot-protected job boards"],"tags":["playwright","scraping","fallback","browser"],"backgroundTag":"browser-automation-failed","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}