ScreenshotNeo

BlogHow-to

India Website Screenshot Bottom Cut Off? Puppeteer Fix

Fix a Puppeteer screenshot that cuts off the bottom of an India website. Check full-page capture, scrolling, rendering, clipping, and viewport behavior.

By the ScreenshotNeo team4 October 20267 min read

If a Puppeteer screenshot cuts off the bottom of a website, first confirm you are capturing the full document: await page.screenshot({ path: 'page.png', fullPage: true }). The fullPage option is the documented control for a full-page screenshot. If that still misses content, check whether the content rendered before capture, whether it lives in an inner scrolling element, and whether a clip or element screenshot limits the captured area. These causes need page-specific diagnosis; no single resize or screenshot flag fixes every site.

1. Use fullPage for the whole document

A normal screenshot captures the viewport. For the complete document, set fullPage: true:

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

Here is a complete runnable example. It opens a URL, waits for navigation, and saves a full-page PNG:

import puppeteer from 'puppeteer';

const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1365, height: 900, deviceScaleFactor: 1 });
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
  await page.screenshot({ path: 'page.png', fullPage: true });
  console.log('Saved page.png');
} finally {
  await browser.close();
}

Run it with:

npm install puppeteer
node capture.mjs https://example.com

Use a real target URL in place of example.com. Choose a viewport that matches the layout you mean to capture: desktop and mobile widths can produce different content and page heights.

2. Follow a diagnosis that matches the missing content

  1. Confirm the option and output. Make sure the call that writes the file actually includes fullPage: true. Open the output image and compare its height with the document’s scroll height.
  2. Remove accidental clipping. If the call also passes clip, inspect its x, y, width, and height. A clip deliberately restricts the output region. Remove it when you need the full document.
  3. Check which element scrolls. If the browser scrollbar belongs to a container such as a panel with overflow: auto, the missing content may not extend the document. A document full-page screenshot does not automatically expand every nested scroll area. Inspect the DOM and capture or scroll the actual container as the page requires.
  4. Wait for the content to exist. Client-rendered sections, lazy images, infinite-scroll lists, and delayed widgets may appear only after a selector is present or after scrolling. Wait for a meaningful selector, and if necessary scroll the document or the relevant container to trigger rendering before capture.
  5. Record the environment. When a minimal full-page call still differs, record Puppeteer and Chromium versions, operating system, viewport width and height, device scale factor, screenshot options, and which element scrolls. Compare the same URL and viewport when reproducing.

You can inspect document dimensions and scrollable elements in the page:

const dimensions = await page.evaluate(() => ({
  viewport: { width: innerWidth, height: innerHeight },
  document: {
    width: document.documentElement.scrollWidth,
    height: document.documentElement.scrollHeight
  },
  scrollables: [...document.querySelectorAll('*')]
    .filter(el => el.scrollHeight > el.clientHeight + 1)
    .slice(0, 20)
    .map(el => ({
      tag: el.tagName,
      id: el.id,
      className: typeof el.className === 'string' ? el.className : '',
      clientHeight: el.clientHeight,
      scrollHeight: el.scrollHeight,
      overflowY: getComputedStyle(el).overflowY
    }))
}));
console.log(dimensions);

This reports candidates, not a definitive diagnosis: some elements scroll horizontally, and scripts can change the DOM after measurement.

3. Handle lazy-loaded and delayed content

A full-page capture can only include content the page has rendered. For pages that load sections as they enter the viewport, a simple diagnostic is to scroll in increments and allow the page to update. This is a starting point, not a universal recipe; it may trigger infinite loading or page-specific behavior.

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });

await page.evaluate(async () => {
  const step = Math.max(300, Math.floor(innerHeight * 0.8));
  for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
    window.scrollTo(0, y);
    await new Promise(resolve => setTimeout(resolve, 150));
  }
  window.scrollTo(0, 0);
});

await page.waitForSelector('footer', { timeout: 10000 }).catch(() => {});
await page.screenshot({ path: 'page.png', fullPage: true });

Replace footer with a selector that indicates the specific content you need. If the content appears inside a nested scroll container, scroll that container instead of window. For an infinite list, decide on a stopping condition such as a known item count; otherwise the page may never finish loading.

4. Understand clips, elements, and viewport resizing

Capture mode Use it for What to check
Viewport screenshot What is currently visible in the browser window It does not mean the entire document
fullPage: true The full document page Content must be rendered; inner scroll regions may need their own handling
clip A specific rectangular region Coordinates and dimensions can intentionally cut off the bottom
elementHandle.screenshot() One DOM element Element bounds and viewport clipping behavior

Puppeteer’s screenshot options document captureBeyondViewport separately from fullPage. Its default depends on whether a clip is supplied; do not treat it as a replacement for fullPage. See the [official ScreenshotOptions reference](https://pptr.dev/api/puppeteer.screenshotoptions).

There are historical reports of viewport clipping changes and oversized element screenshots. In one 2019 discussion about Puppeteer v2.0.0 behavior, a maintainer suggested resizing the viewport for scripts that relied on the old behavior. A 2018 element screenshot report also describes resizing to the element bounds as a workaround. Resizing can trigger media queries and JavaScript resize handlers, so compare the layout before and after and restore the original viewport if you use this diagnostic. These reports describe historical cases, not a general fix for current versions: [v2.0.0 clipping discussion](https://github.com/puppeteer/puppeteer/issues/5080) and [oversized element discussion](https://github.com/puppeteer/puppeteer/issues/1779).

5. Check viewport units and device scale independently

If a resize appears to change the layout, inspect the rendered dimensions and CSS rather than assuming the screenshot image is simply cropped. An old report described unexpected vw/vh behavior during full-page capture; it dates to 2017 and does not establish a current universal cause. Likewise, a 2023 issue about deviceScaleFactor concerned output quality on specific historical Linux versions, not a general way to reveal content below the fold. Treat both as diagnostic leads: [viewport-unit report](https://github.com/puppeteer/puppeteer/issues/703) and [device-scale report](https://github.com/puppeteer/puppeteer/issues/11514).

6. Troubleshoot common failures

Symptom Likely cause What to try
Only the visible viewport is saved fullPage is missing or false Set fullPage: true on the screenshot call that writes the image.
The bottom ends at an exact rectangle A clip limits the capture Remove the clip for a document capture or correct its dimensions for a deliberate crop.
The document height looks correct but a panel is truncated The panel scrolls internally Identify the element with overflow and capture or scroll that element with page-specific logic.
Footer or lower images are absent Lazy loading or delayed rendering Wait for a relevant selector or scroll through the actual content before capture.
Layout changes after a resize workaround Responsive CSS or resize handlers ran Use the intended viewport and avoid resizing, or verify and restore the original viewport.
Results differ between machines Different browser, Puppeteer, OS, viewport, or scale settings Record and align those values before comparing screenshots.
Capture hangs on network idle Long-lived requests can prevent the chosen idle condition Use domcontentloaded plus an explicit selector or bounded delay suited to the page.

A historical mobile-view issue reported partial full-page output, but it did not establish a general cause or present-day fix. Reproduce with the current installed versions and a minimal script before attributing a mobile-only failure to Puppeteer itself: [partial mobile capture report](https://github.com/puppeteer/puppeteer/issues/1273).

7. Performance, reliability, and cost

  • Performance: Full-page output can be much taller than a viewport image. Large pages use more time and memory, especially at a high device scale factor. Use the smallest viewport and output scale that meet your requirement; avoid repeated scrolling when the page does not need it.
  • Reliability: Make capture conditions explicit: target selector, navigation timeout, viewport, and browser version. Avoid relying only on a fixed sleep when a selector can signal readiness. For pages that keep network requests open, choose a different readiness condition and wait for the content you need.
  • Cost: A self-hosted Puppeteer script has no per-screenshot API charge, but it uses your compute, storage, and maintenance time. Large captures can increase resource use. If using a service, check its billing rules and what happens for failed loads and cached responses.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its clean-shot flow accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. AI agents can use its MCP tools, including take_screenshot, get_page_info, and capture_pdf.

Here is the one-call cURL example; see the ScreenshotNeo API documentation for options:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to get started.

FAQ

Does fullPage: true capture content inside every scrollable panel?

No. It requests a full-document capture. A panel with its own scrolling may need page-specific scrolling or element capture.

Should I always set captureBeyondViewport: true?

No. It is a separate option with behavior tied to the presence of clip; use fullPage for a full-document screenshot.

Will increasing deviceScaleFactor show more of the page?

No. It affects output scale, not the document content available to capture.

What details help diagnose a bug report?

Include Puppeteer and Chromium versions, OS, viewport, device scale factor, screenshot options, a minimal script, and whether the document or an inner element scrolls.