World’s largest virtual agentic engineering & quality conference

WHENAUG 19-21
WHEREVirtual · Global
Register Now

How to handle Issues with taking screenshots or generating PDFs in Puppeteer?

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.

Why Screenshots and PDFs Fail in Puppeteer

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.

Fixing Screenshot Issues

Work through these in order. The first two solve the overwhelming majority of blank and partial screenshots.

  • Capture the full page, not just the viewport: by default page.screenshot() grabs only what fits the current viewport. Pass fullPage: true to capture the entire scrollable height.
  • Wait for content before capturing: navigate with waitUntil: 'networkidle0', wait for a known element with page.waitForSelector(), or call page.waitForNetworkIdle() immediately before the screenshot. This is the single most common fix for blank output.
  • Raise the resolution: set a viewport with deviceScaleFactor: 2 to double pixel density for retina-quality images. Use 3 for even sharper output.
  • Capture a single element: get an element handle and call screenshot() on it, or pass a clip region to grab a specific rectangle.
  • Force lazy images to load: with fullPage: true, lazy-loaded images stay empty until scrolled into view. Scroll the document to the bottom, then wait for the network to idle before capturing.
  • Transparent vs white background: Chromium paints a default white backdrop. Pass omitBackground: true to make it transparent in PNG or WebP output.
  • Fonts not rendering: custom and web fonts may not be ready when you capture. Wait for document.fonts.ready, and on Linux/Docker install the actual font packages in the image.
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 });

Fixing PDF Issues

  • Headless only: page.pdf() generates a PDF only when Chromium runs in headless mode. If you launched in headful mode, the call fails, so launch with headless enabled whenever you need PDF output.
  • Missing colors and backgrounds: set printBackground: true (it defaults to false) so background colors and images are included. The CSS -webkit-print-color-adjust: exact declaration forces exact color rendering.
  • PDF looks unlike the on-screen page: by default page.pdf() renders with the print media type. Call await page.emulateMediaType('screen') first to render the on-screen appearance, or pass 'print' explicitly to honor your print stylesheet.
  • Wrong size or cramped margins: set format (for example 'A4' or 'Letter') and a margin object with top, right, bottom, and left values.
  • Headers and footers: enable displayHeaderFooter: true and supply headerTemplate and footerTemplate HTML using classes such as pageNumber, totalPages, and date. Set a font size in the template, as the default is tiny.
  • Respect CSS page size: pass preferCSSPageSize: true so any CSS @page size rule wins over the format option.
  • Page breaks: control them in CSS with break-before, break-after, and break-inside: avoid so tables and cards are not split across pages.
  • Blank or incomplete PDF: as with screenshots, wait for fonts and images before calling page.pdf(), otherwise the output is empty, especially for SPAs and lazy content.
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();

Fixing Blank Output on Linux and Docker

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.

Quick Issue and Fix Reference

SymptomLikely CauseFix
Blank or partial screenshotCaptured before render finishednetworkidle0, waitForSelector, or waitForNetworkIdle
Only top of page capturedViewport-only capturefullPage: true
Low-resolution imageDefault pixel densitydeviceScaleFactor: 2 (or 3)
Empty image boxesLazy-loaded imagesScroll page, then waitForNetworkIdle
PDF call failsBrowser launched headfulLaunch with headless enabled
PDF missing colors/imagesBackgrounds off by defaultprintBackground: true
PDF unlike on-screen pagePrint media type appliedemulateMediaType('screen')
Blank output in DockerMissing libraries/fontsInstall deps + --no-sandbox

Best Practices

  • Always wait deliberately. Prefer explicit waits (a selector, a font ready promise, or network idle) over fixed timeouts, which are both slower and flakier.
  • Pin a viewport. Set width, height, and deviceScaleFactor before capturing so output is reproducible across machines and CI runs.
  • Keep the latest Puppeteer. Screenshot and PDF behavior changes between Chromium versions, so stay current and re-baseline visual snapshots after upgrades.
  • Stabilize the page for visual diffs. Disable animations and transitions with injected CSS so repeated captures are pixel-stable.
  • Validate across real browsers. Local headless Chromium is one engine. Run the same flows on a cloud grid such as Puppeteer Testing to confirm rendering holds across browser and OS combinations.

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.

Frequently Asked Questions

Why is my Puppeteer screenshot blank or only partially rendered?

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.

Why does page.pdf() return an empty or blank PDF?

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.

Does page.pdf() work in headful (non-headless) mode?

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.

Why does my PDF look different from the page in the browser?

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.

How do I take a high-resolution screenshot with Puppeteer?

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.

How do I capture only one element instead of the whole page?

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.

Related Questions

Test Your Website on 3000+ Browsers

Get 100 minutes of automation test minutes FREE!!

Test Now...

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

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests