{"record":{"id":"23b887f0a547d4e7","repo":"lissy93/web-check","slug":"no-body-element-found-on-the-page","errorCode":null,"errorMessage":"No body element found on the page","messagePattern":"No body element found on the page","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"api/screenshot.js","lineNumber":62,"sourceCode":"// Fallback to puppeteer when the direct Chromium binary call fails\nconst puppeteerScreenshot = async (targetUrl) => {\n  let browser = null;\n  try {\n    browser = await puppeteer.launch({\n      args: [...chromium.args, '--no-sandbox'],\n      defaultViewport: { width: 800, height: 600 },\n      executablePath: process.env.CHROME_PATH || (await chromium.executablePath()),\n      headless: true,\n      acceptInsecureCerts: true,\n      ignoreDefaultArgs: ['--disable-extensions'],\n    });\n    const page = await browser.newPage();\n    await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);\n    page.setDefaultNavigationTimeout(8000);\n    await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });\n    await page.evaluate(() => {\n      if (!document.querySelector('body')) {\n        throw new Error('No body element found on the page');\n      }\n    });\n    const buffer = await page.screenshot();\n    return buffer.toString('base64');\n  } finally {\n    if (browser) await browser.close().catch(() => {});\n  }\n};\n\nconst screenshotHandler = async (targetUrl) => {\n  if (!targetUrl) throw new Error('URL is missing from queryStringParameters');\n  try {\n    new URL(targetUrl);\n  } catch {\n    throw new Error('URL provided is invalid');\n  }\n\n  log.debug(`request received: ${targetUrl}`);","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/lissy93/web-check/blob/af1a97759fc8bcc43c876c94f2ccb018ce215f90/api/screenshot.js#L44-L80","documentation":"During a Puppeteer screenshot run (dark-mode emulation, domcontentloaded wait), the page is evaluated and document.querySelector('body') returns null, so the in-page script throws. HTML documents always have a body node synthesised by the parser, so this only fires when the navigated document is not HTML (e.g. XML, plain text in some contexts) or the page frame is in an unexpected state after navigation.","triggerScenarios":"Screenshotting a URL that serves application/xml, an RSS/Atom feed, or a non-HTML resource; a redirect chain landing on a non-HTML document; about:blank-ish or prematurely aborted loads where DOMContentloaded fired on a skeleton document.","commonSituations":"Feeds and API endpoints passed to a screenshot API, XML sitemap URLs, endpoints that sniff content-type differently for headless Chromium's User-Agent.","solutions":["Verify the target actually serves text/html (curl -I <url>) and fix the URL","Have the caller check Content-Type before requesting a screenshot","If you control this code, treat a missing body as a skip/soft-failure rather than a hard error, as the handler already does for Chromium-missing errors"],"exampleFix":"// before\nawait page.goto(url, { waitUntil: 'domcontentloaded' });\nawait page.screenshot(); // throws 'No body element found on the page' for XML docs\n\n// after\nconst res = await page.goto(url, { waitUntil: 'domcontentloaded' });\nif (!(res.headers()['content-type'] || '').includes('html')) {\n  return { skipped: `non-HTML content-type: ${res.headers()['content-type']}` };\n}\nawait page.screenshot();","handlingStrategy":"fallback","validationCode":"const res = await fetch(target, { method: 'HEAD', redirect: 'follow' });\nconst ct = res.headers.get('content-type') || '';\nif (!ct.includes('html')) skipScreenshot('non-HTML target');","typeGuard":"null","tryCatchPattern":"try { return { image: await screenshot(url) }; }\ncatch (e) {\n  if (e.message === 'No body element found on the page') return { skipped: 'target is not an HTML page' };\n  throw e;\n}","preventionTips":["Content-Type check before handing URLs to a screenshot renderer","Treat renderer anomalies as skips, mirroring the Chromium-not-found handling already present","Log the final response URL and content-type when screenshots fail to triage redirects"],"tags":["puppeteer","screenshot","content-type","dom"],"backgroundTag":"non-html-document-navigation","analyzedSha":"af1a97759fc8bcc43c876c94f2ccb018ce215f90","analyzedAt":"2026-08-27T11:44:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}