ScreenshotNeo

BlogHow-to

How to Fix a Website Screenshot That Has the Wrong Page Height

A screenshot that is too short or too tall often points to the capture mode, scroll container, CSS sizing, or viewport. Diagnose the cause and fix it in Playwright.

By the ScreenshotNeo team4 October 20268 min read

A screenshot with the wrong page height usually comes from one of four things: the capture mode does not match the intended output, the page scrolls inside a nested container, CSS limits the content to the viewport, or the layout changes with viewport size. First decide whether you need the visible viewport, one element, or the full scrollable document. In Playwright, use fullPage: true for the full scrollable page; inspect the scroll root and computed heights if the result is still wrong. Playwright defines full-page and element screenshots as separate capture choices.

1. Identify the height you actually want

“Page height” can refer to different things. Pick the target before changing CSS or capture settings:

Desired output Playwright method What it includes
Visible screen page.screenshot() The current viewport only.
Full document page.screenshot({ fullPage: true }) The full scrollable page as if it fit on a tall screen.
One component or panel page.locator(selector).screenshot() The selected element’s bounds.
Specific rectangle page.screenshot({ clip: { x, y, width, height } }) A chosen region in the page’s screenshot coordinate space.

A viewport capture that stops at the fold is behaving as requested. Conversely, a full-page capture may be much taller than the visible screen by design. The Playwright screenshot guide documents viewport, full-page, and element captures.

2. A reproducible Playwright diagnostic

This Node.js script reports the document and body dimensions, the largest likely nested scroll containers, and saves a full-page screenshot. It uses a fixed viewport before navigation so the page’s initial responsive layout is predictable.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });

  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.locator('body').waitFor();

  const dimensions = await page.evaluate(() => {
    const roots = [...document.querySelectorAll('html, body, *')]
      .map((el) => {
        const style = getComputedStyle(el);
        return {
          tag: el.tagName.toLowerCase(),
          id: el.id,
          className: typeof el.className === 'string' ? el.className : '',
          clientHeight: el.clientHeight,
          scrollHeight: el.scrollHeight,
          overflowY: style.overflowY,
          height: style.height,
          minHeight: style.minHeight,
        };
      })
      .filter((el) => el.scrollHeight > el.clientHeight + 1)
      .sort((a, b) => b.scrollHeight - a.scrollHeight)
      .slice(0, 12);

    return {
      viewport: { width: innerWidth, height: innerHeight },
      document: {
        htmlClientHeight: document.documentElement.clientHeight,
        htmlScrollHeight: document.documentElement.scrollHeight,
        bodyClientHeight: document.body.clientHeight,
        bodyScrollHeight: document.body.scrollHeight,
      },
      scrollContainers: roots,
    };
  });

  console.dir(dimensions, { depth: null });
  await page.screenshot({ path: 'full-page.png', fullPage: true });
  await browser.close();
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run it after installing Playwright and its Chromium browser. Replace the example URL with the page that has the problem. The element scan is a clue, not a definitive scroll-root detector: a page can use shadow DOM, frames, or application-specific scrolling behavior that needs separate inspection.

3. Diagnose and fix the cause

Step 1: Set the capture mode explicitly

For a full document, specify fullPage: true. For a viewport shot, omit it. For a card or panel, capture its locator. Do not infer that the screenshot API will expand a viewport capture just because more content exists below the fold.

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

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

// One element, including an independently scrolling panel if its element box is the target
await page.locator('.report-panel').screenshot({ path: 'panel.png' });

Step 2: Find the actual scroll container

Scroll the page manually or in browser devtools and observe what moves. If the document itself moves, full-page capture is the relevant mode. If only a nested panel moves, the content may not belong to the document’s scroll height. Capture the panel when that is the intended artifact. If you need every item in a virtualized list, scrolling and capturing each portion may be necessary because off-screen rows may not exist in the DOM at the same time.

For a nested panel whose visible box is fixed but whose contents extend beyond it, a locator screenshot normally captures the element’s box, not an automatically expanded representation of every possible scroll position. You can temporarily adjust the panel for a diagnostic capture, or script incremental scrolling and stitch output if the exact complete content is required. Preserve the normal layout for production unless the fixed panel height is itself a bug.

Step 3: Inspect CSS height constraints

Inspect html, body, the main wrapper, and the suspected scroll container. Look for height: 100vh, fixed pixel heights, max-height, and overflow: hidden or overflow: auto. A viewport-height rule can make the rendered area stop at the viewport, while overflow rules can move scrolling into a child element.

/* Example only: use content-driven height if the page is meant to grow. */
.page-shell {
  min-height: 100vh;
  height: auto;
  overflow: visible;
}

This is not a universal patch. A dashboard, modal, or app shell may intentionally be viewport-bound. Change the CSS only if the page is supposed to grow with its content. A Playwright maintainer described a case where height: 100vh left no content rendered below the viewport; full-page capture cannot reveal content the page does not render. See Playwright issue 28027.

Step 4: Test whether viewport size drives the layout

Set the viewport before navigation, capture, then compare with a taller diagnostic viewport. A Playwright maintainer suggested 1280 × 3000 as an example diagnostic size; it is not a general target resolution. If the page’s reported scroll height or rendered content changes, viewport-dependent CSS or JavaScript is involved. Restore the intended dimensions and check the responsive result before deciding on a fix. Playwright’s viewport sizing API describes resizing a page; its docs recommend setting viewport before navigation when a site reacts to dimensions.

await page.setViewportSize({ width: 1280, height: 3000 });
await page.goto('https://example.com');
await page.screenshot({ path: 'tall-viewport.png' });

A taller viewport is an experiment, not necessarily the final solution: responsive breakpoints, sticky elements, and scripts may produce a different layout at that height.

Step 5: Compare browsers only after checking layout

A historical 2020 Playwright issue discussed a Chromium full-page mismatch involving viewport units. Treat that report as a lead for reproducing a browser-specific issue, not evidence of a current universal Chromium defect. First keep the page state, width, browser version, and screenshot options constant. Then compare current browser engines if the discrepancy remains.

4. Verify dimensions and make comparisons repeatable

For a reliable comparison, keep these inputs fixed:

  • URL, authentication state, and page data
  • Browser engine and version
  • Viewport width and height, set before navigation
  • Capture mode and any clip or locator
  • Wait condition and the point at which dynamic content settles

Compare image dimensions with the intended document or element dimensions. In the browser, document.documentElement.scrollHeight and document.body.scrollHeight provide useful document-level readings; scrollHeight on the actual scroll container is the relevant measurement for a nested panel. CSS pixel dimensions can differ from physical image pixels when device scale factor is applied, so do not mistake a scale-factor difference for a page-height bug.

5. Common errors and fixes

Symptom Likely cause What to do
Image stops at the fold Viewport screenshot mode Use fullPage: true if the full document is desired.
Full-page image is still short CSS constrains height, content is not rendered, or scrolling is nested Inspect computed height, overflow, and document versus panel scroll height.
Some rows or cards are missing Virtualized list or lazy content has not rendered Scroll the relevant container to trigger content, wait for it, or capture chunks. Do not assume one document screenshot can include DOM nodes that were never created.
Height changes when viewport changes Viewport media queries, vh sizing, or layout scripts Set the intended viewport before navigation and inspect responsive rules.
Only a panel’s top portion appears The panel has its own scroll height Capture the correct locator and determine whether the target is the panel box or all scrolled content.
Different engines produce different output Engine-specific layout or rendering behavior Reproduce on current versions with identical inputs, then reduce to a small test case.
Screenshot call times out or page never settles Navigation or dynamic app state is still pending Choose an explicit wait condition appropriate to the app, wait for a stable selector, and distinguish application loading failures from capture height.

6. Performance, reliability, and cost considerations

Very tall captures require more rendering work and create larger image files. Capture only the needed element or region when that is the actual deliverable. For long pages, reduce unnecessary image scale during diagnosis and avoid repeatedly comparing captures while changing several variables at once. Lazy loading and virtualized content can make output depend on scroll behavior and wait timing, so use a repeatable page state and wait for the content that matters.

For production capture pipelines, record the browser version, viewport, URL, and capture options alongside each output so height regressions can be reproduced. A successful screenshot call does not establish that the entire intended content was rendered: compare the image dimensions and page scroll measurements, and check for blank or missing sections.

Or skip the browser setup

ScreenshotNeo is a website screenshot API. One GET request returns an image, including full-page captures with lazy images loaded; the API documentation lists the available options.

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}`);
  • Cookie banners are accepted like a visitor; 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot. Each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers identify the page verdict and billing status.
  • An MCP server gives AI agents tools to take screenshots, inspect page info, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.

FAQ

Does full-page mode change my page’s CSS?

No. It captures the page as rendered; it does not rewrite the page’s layout to create missing content.

Should I set a very tall viewport as the permanent fix?

Usually not without checking the responsive consequences. Use it first as a diagnostic, then capture at the viewport your output is meant to represent.

Why does the image have more pixels than the reported CSS height?

Device scale factor can map CSS pixels to a different number of image pixels. Compare like units and account for the capture scale.

Is a Chromium full-page height mismatch always a browser bug?

No. A historical issue is not proof of a current general defect. Confirm the page layout and reproduce with current versions before attributing the cause to the browser.

Sources