World’s largest virtual agentic engineering & quality conference
Most Puppeteer screenshot and PDF problems come down to one thing: you are capturing before the page has finished rendering. Blank, partial, or low-resolution output is fixed by waiting for content with waitForSelector, networkidle0, or waitForNetworkIdle, then setting deviceScaleFactor for resolution. PDFs add two more rules: page.pdf() works only in headless mode, and you must pass printBackground: true and call emulateMediaType to control how CSS is applied. The sections below map each symptom to its exact fix with working code.
Puppeteer drives a real Chromium instance, so both page.screenshot() and page.pdf() capture whatever the browser has painted at the exact moment you call them. If JavaScript is still hydrating, fonts are still loading, images are lazy-loaded, or network requests are in flight, you capture an unfinished frame. On top of timing, PDFs introduce print-specific rendering rules and a hard headless-only requirement, and Linux or Docker environments add a dependency layer that, when incomplete, renders nothing at all. Once you understand these three buckets (timing, PDF rendering rules, and environment), every fix becomes predictable.
Work through these in order. The first two solve the overwhelming majority of blank and partial screenshots.
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// 1. High-resolution viewport (retina-quality output)
await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 2 });
// 2. Wait until the network is idle so the page is fully painted
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
// 3. Wait for a known element to guarantee content is present
await page.waitForSelector('#main-content');
// 4. Full-page, transparent-background screenshot
await page.screenshot({
path: 'page.png',
fullPage: true,
omitBackground: true,
});
await browser.close();To capture just one element or a fixed region instead of the whole page, use an element handle or the clip option:
// Capture a single element
const chart = await page.$('#chart');
await chart.screenshot({ path: 'chart.png' });
// Or capture an exact rectangular region
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 600, height: 400 },
});For pages with lazy-loaded media, scroll the full document so every image enters the viewport and paints before you capture:
// Scroll to the bottom so lazy images load, then wait for them
await page.evaluate(async () => {
await new Promise((resolve) => {
let total = 0;
const step = 200;
const timer = setInterval(() => {
window.scrollBy(0, step);
total += step;
if (total >= document.body.scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
});
});
await page.waitForNetworkIdle();
await page.screenshot({ path: 'lazy-page.png', fullPage: true });const browser = await puppeteer.launch({ headless: true }); // PDF needs headless
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
// Render the on-screen styling rather than the print stylesheet
await page.emulateMediaType('screen');
// Make sure web fonts are loaded before printing
await page.evaluateHandle('document.fonts.ready');
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true, // include background colors and images
preferCSSPageSize: true,
margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' },
displayHeaderFooter: true,
headerTemplate: '<span style="font-size:10px;width:100%;text-align:center;"></span>',
footerTemplate:
'<span style="font-size:10px;width:100%;text-align:center;">' +
'Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>',
});
await browser.close();A frequent surprise is that screenshots and PDFs work locally but render blank inside a container. Chromium needs a set of shared libraries and fonts that minimal Linux images do not ship with, and without them it paints nothing. Install the dependencies and launch with the sandbox flags that containers require:
# Debian/Ubuntu: install the libraries and fonts Chromium needs
apt-get update && apt-get install -y \
libnss3 libatk-bridge2.0-0 libdrm2 libgbm1 libasound2 \
libxkbcommon0 libpangocairo-1.0-0 fonts-liberation fonts-noto-color-emoji// In containers, run without the Chrome sandbox
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});Missing fonts are the usual cause of blank or box-filled text in container-generated PDFs, so always include a base font package such as fonts-liberation plus any custom fonts your pages use.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Blank or partial screenshot | Captured before render finished | networkidle0, waitForSelector, or waitForNetworkIdle |
| Only top of page captured | Viewport-only capture | fullPage: true |
| Low-resolution image | Default pixel density | deviceScaleFactor: 2 (or 3) |
| Empty image boxes | Lazy-loaded images | Scroll page, then waitForNetworkIdle |
| PDF call fails | Browser launched headful | Launch with headless enabled |
| PDF missing colors/images | Backgrounds off by default | printBackground: true |
| PDF unlike on-screen page | Print media type applied | emulateMediaType('screen') |
| Blank output in Docker | Missing libraries/fonts | Install deps + --no-sandbox |
When you need to scale these captures across many browser and OS combinations without maintaining your own Chromium fleet, running Puppeteer on the TestMu AI cloud lets you execute headless screenshot and PDF jobs in parallel and verify how pages render everywhere, not just on your local machine.
You are almost always capturing before the page finished rendering. Navigate with waitUntil: 'networkidle0', wait for a specific element with page.waitForSelector(), or call page.waitForNetworkIdle() right before page.screenshot(). For lazy-loaded images, scroll the full document first so they enter the viewport and paint.
Three common causes: the page was not fully loaded when page.pdf() ran, you forgot printBackground: true so colors and background images are missing, or you are on Linux/Docker without the required Chromium libraries and fonts. Wait for content, enable printBackground, and install the missing system dependencies.
No. page.pdf() generates a PDF only when Chromium runs in headless mode. If you launch the browser in headful mode the call will fail, so launch with headless enabled when you need PDF output.
By default page.pdf() renders with the print CSS media type, so any @media print rules apply and screen-only styling is dropped. Call await page.emulateMediaType('screen') before page.pdf() to render the on-screen appearance instead.
Increase the device pixel ratio with page.setViewport({ width, height, deviceScaleFactor: 2 }). A deviceScaleFactor of 2 doubles the pixel density and produces a retina-quality image; use 3 for even sharper output at the cost of a larger file.
Get the element handle and call screenshot on it, for example const el = await page.$('#chart'); await el.screenshot();. Alternatively, pass a clip option with x, y, width, and height to page.screenshot() to capture a specific rectangular region.
KaneAI - Testing Assistant
World’s first AI-Native E2E testing agent.

TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance