ScreenshotNeo

BlogHow-to

Puppeteer Screenshot Has a White Strip at the Bottom: How to Fix It

Diagnose a white strip in a Puppeteer screenshot by checking capture size, page height, loading, device scale, and browser compatibility.

By the ScreenshotNeo team4 October 20267 min read

A white strip at the bottom of a Puppeteer screenshot is a symptom, not a single known Puppeteer error. First confirm whether you intend to capture the visible viewport or the whole page. Then compare the screenshot dimensions with the document’s scroll height, make sure the content you need has rendered, and run a controlled test at deviceScaleFactor: 1. If the symptom remains, check that your Puppeteer and Chromium versions are a supported pair. The cause depends on the page, versions, viewport, and screenshot options.

1. Confirm the capture region

By default, page.screenshot() captures the viewport. Set fullPage: true when you intend to capture the full page. A mismatch between the expected region and the selected mode can make the output appear to have extra space.

// Viewport only
await page.screenshot({ path: 'viewport.png' });

// Full document
await page.screenshot({ path: 'full-page.png', fullPage: true });

The current Puppeteer screenshot options document fullPage as defaulting to false. See the ScreenshotOptions reference and the screenshots guide.

2. Measure the rendered page and screenshot

Before changing CSS or capture options, record the viewport and document heights. If the document itself extends below the visible content, a full-page capture can faithfully include that empty area. Inspect the layout for an oversized footer or container, bottom padding or margin, positioned elements, and responsive styles. These are things to investigate, not proven causes for every white strip.

const dimensions = await page.evaluate(() => ({
  viewport: {
    width: window.innerWidth,
    height: window.innerHeight,
  },
  document: {
    htmlScrollHeight: document.documentElement.scrollHeight,
    bodyScrollHeight: document.body.scrollHeight,
    htmlClientHeight: document.documentElement.clientHeight,
  },
}));
console.log(dimensions);

const screenshot = await page.screenshot({
  path: 'capture.png',
  fullPage: true,
});
console.log('PNG bytes:', screenshot.length);

For a PNG, inspect its pixel width and height with an image viewer or image metadata tool. Compare those dimensions to your configured viewport and the measured document height, allowing for device scale. This helps separate an unexpectedly tall document from a screenshot capture issue.

3. Use a repeatable diagnostic script

This runnable example logs the browser version and page dimensions, waits for navigation, and captures the full page. Replace the URL and adjust the viewport to match the case you are debugging.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    console.log('Browser:', await browser.version());
    const page = await browser.newPage();
    await page.setViewport({
      width: 1440,
      height: 900,
      deviceScaleFactor: 1,
    });

    await page.goto('https://example.com', {
      waitUntil: 'networkidle2',
    });

    const dimensions = await page.evaluate(() => ({
      viewportHeight: window.innerHeight,
      htmlScrollHeight: document.documentElement.scrollHeight,
      bodyScrollHeight: document.body.scrollHeight,
    }));
    console.log('Dimensions:', dimensions);

    await page.screenshot({ path: 'capture.png', fullPage: true });
  } finally {
    await browser.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

networkidle2 is a navigation condition, not proof that every lazy image, iframe, animation, or client-rendered component is ready. Wait for the specific content that matters before taking the screenshot.

4. Wait for the content you need

Prefer a meaningful readiness condition over an arbitrary delay. If a particular component must exist, wait for its selector. If the image itself matters, check that it loaded and has nonzero natural dimensions. A fixed delay can be useful for diagnosis, but it cannot guarantee rendering on a slow or variable page.

// Wait for a page component to appear
await page.waitForSelector('[data-testid="page-content"]', {
  visible: true,
  timeout: 15000,
});

// Optionally verify images have completed loading
await page.waitForFunction(() => {
  const images = [...document.images];
  return images.every((image) => image.complete && image.naturalWidth > 0);
}, { timeout: 15000 });

await page.screenshot({ path: 'ready.png', fullPage: true });

Use the image check only when every image is expected to load; a deliberately broken or blocked image will otherwise cause the wait to time out. For a site with lazy loading, scroll through the page or otherwise trigger the relevant content before checking readiness. An older Puppeteer issue reported missing lazy-loaded images and iframe content despite a full-page capture, so do not assume that navigation idle alone settles every page (issue #3202).

5. Compare device scale factor

Run two captures with identical URL, viewport, browser, navigation, and screenshot options, changing only deviceScaleFactor. Compare the current value with 1. This is a controlled diagnostic, not a universal fix: one older report associated a site-specific white capture with scale factor 2, but it does not establish that scale is generally responsible (issue #3169).

await page.setViewport({
  width: 1440,
  height: 900,
  deviceScaleFactor: 1,
});
await page.screenshot({ path: 'scale-1.png', fullPage: true });

await page.setViewport({
  width: 1440,
  height: 900,
  deviceScaleFactor: 2,
});
await page.screenshot({ path: 'scale-2.png', fullPage: true });

Keep all other inputs fixed. Otherwise, a changed result will not tell you which setting mattered.

6. Verify Puppeteer and Chromium compatibility

Log await browser.version() and note the installed Puppeteer version. Check the Puppeteer release documentation for the browser version it supports. A 2023 issue was diagnosed as Puppeteer 18.1.0 being paired with Chromium 119, which that Puppeteer release did not support (issue #11514). Keep the Puppeteer package and its managed browser installation aligned where possible; if you provide a system browser executable yourself, verify that pairing explicitly.

7. Understand what transparency can tell you

Puppeteer uses a white background by default. omitBackground: true removes that default background so transparent pixels can show through. It is useful as a diagnostic for whether the apparent white is simply the page background. It does not crop the image, shorten the document, or remove empty layout space.

await page.screenshot({
  path: 'transparent.png',
  fullPage: true,
  omitBackground: true,
});

See the official screenshot option reference for the documented option behavior.

8. Troubleshoot by symptom

Symptom or check Likely explanation to investigate Next step
The strip appears only with fullPage: true The document may have real extra height, or full-page capture is not the region you meant. Log both scroll heights, inspect bottom layout and compare with a viewport-only capture.
The strip appears in viewport and full-page captures The page may paint a white background or the issue may be specific to the page/browser combination. Try omitBackground: true diagnostically, then compare scale and browser versions.
Content is missing or the bottom is blank Lazy content, an iframe, or client rendering may not be ready at capture time. Wait for the required selector or asset and trigger lazy loading before capture.
Changing scale factor changes the output The behavior may be scale-sensitive on this page or browser setup. Repeat with one variable changed and keep the reproduction details.
Only one environment produces the result Browser/Puppeteer versions or operating environment may differ. Record browser.version(), Puppeteer version, OS, viewport, and exact options.
A very tall page fails or produces an unexpected capture Browser and page-size constraints may be involved. A 2017 report described a 16,384-pixel cap in that historical environment; it is not a current general limit. Reduce the captured region or capture sections and inspect current browser behavior. See issue #359.

To get a useful diagnosis, preserve a minimal reproduction and report the Puppeteer version, await browser.version() output, operating system, target URL or local HTML, viewport including deviceScaleFactor, screenshot options, and output image dimensions. Without these, there is not enough information to identify one root cause reliably.

9. Keep captures reliable and efficient

  • Wait for the right condition. Use a selector or asset readiness check for required content. Avoid relying on a long fixed sleep for correctness.
  • Bound waits. Give navigation and readiness waits a timeout, and log which stage failed so a slow target is distinguishable from a screenshot failure.
  • Use a consistent browser build. Record Puppeteer and browser versions when comparing results across machines or CI runs.
  • Capture only what you need. Viewport screenshots are generally smaller and faster than full-page captures. Extremely tall captures consume more memory and can encounter browser or image-size constraints.
  • Do not retry blindly. A retry can help with transient loading, but first preserve the failed output and logs; repeated retries can hide a deterministic layout or compatibility problem.

There is no single cost figure for running Puppeteer: it depends on where Chromium runs, the resources allocated, capture dimensions, and workload. For a self-hosted workflow, budget for browser process memory and CPU, especially for large full-page captures and concurrent jobs.

10. Or skip the browser setup

If you need a screenshot without managing Chromium and Puppeteer, ScreenshotNeo is a website screenshot API and MCP server. Its API accepts one GET request with a URL and returns an image or PDF. See the ScreenshotNeo API documentation for options and setup.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for free and get 1,000 screenshots a month with no card.

FAQ

Does omitBackground remove the strip?

Only if the white pixels come from the default background and transparency is acceptable. It does not crop excess height or fix page layout.

Does networkidle2 guarantee a complete screenshot?

No. A page can render lazy content, iframe content, or client-side updates after navigation reaches that condition. Wait for the specific content required.

Is there a universal maximum height for a full-page screenshot?

Do not treat the 16,384-pixel figure from a 2017 issue as a current universal limit. Limits and behavior depend on the browser and environment; test the versions you run.