Chrome Headless Screenshot Cuts Off the Bottom of a Long Web Page: Fix
A viewport screenshot shows only the visible browser area. Use Playwright full-page capture or Chrome DevTools Protocol beyond-viewport capture, then check page readiness and dimensions.
Quick fix: a normal Chrome screenshot captures the browser viewport, so it can cut off the rest of a long page. In Playwright, set fullPage: true. With Chrome DevTools Protocol (CDP), call Page.captureScreenshot with captureBeyondViewport: true and omit clip. Then make sure the content has loaded and the page height has stopped changing before capture.
Why the bottom is missing
A viewport screenshot and a full-page screenshot are different capture requests. A viewport capture records the currently visible browser area. A full-page capture asks the automation library or browser protocol to include the page beyond that viewport.
Chrome’s command-line --screenshot option is documented with --window-size, but not as a full-document capture switch. A larger window changes the viewport; it does not by itself request the full scrollable page. [Chrome Headless command-line documentation]
Fix it with Playwright
For a Playwright script, use fullPage: true in the screenshot call. This example is runnable with Node.js after installing Playwright and its Chromium browser.
import { chromium } from 'playwright';
const url = process.argv[2] ?? 'https://example.com/';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Install and run it with:
npm install playwright
npx playwright install chromium
node screenshot.mjs https://example.com/
networkidle is an example readiness condition, not a universal best choice. Pages with polling, analytics, streaming, or other persistent network activity may never become idle. Choose a condition that matches the page, such as waiting for a known content selector, or use an explicit delay only when the site’s rendering behavior requires it. Playwright documents fullPage as capturing the full scrollable page. [Playwright screenshots documentation]
Fix it with Chrome DevTools Protocol
If your code sends CDP commands directly, capture beyond the viewport and leave out clip. Chromium then measures the full page and uses those dimensions for the capture.
const result = await client.send('Page.captureScreenshot', {
format: 'png',
captureBeyondViewport: true
});
await fs.promises.writeFile('page.png', Buffer.from(result.data, 'base64'));
Here, client is an established CDP session and fs is Node’s file-system module. For example, with a CDP client library, connect to the Chrome debugging endpoint, create a session for the target page, and send the command on that session. The protocol parameters relevant to this fix are format, captureBeyondViewport, and clip; omit clip for the full-page behavior described here. [CDP Page.captureScreenshot reference] [Chromium implementation]
What the Chrome command line can and cannot do
Use Chrome’s command-line screenshot when you want a viewport image or need to change the viewport dimensions:
chrome --headless --screenshot --window-size=1440,900 --timeout=10000 https://example.com/
This sets a 1440 by 900 window and a maximum wait of 10 seconds. The timeout is not a guarantee that asynchronous content is ready: Chrome captures after that maximum even if the page is still loading. The documented CLI options above do not provide the same full-page switch as Playwright’s fullPage or CDP’s captureBeyondViewport. [Chrome Headless command-line documentation]
When full-page capture still misses content
- Check the output dimensions. If the image height equals the configured viewport height, the script probably still uses viewport capture. Confirm the actual screenshot call and the browser or framework version.
- Wait for the content you need. A navigation event or timeout may finish before client-side rendering, images, or fonts are ready. Wait for a meaningful selector or another page-specific readiness signal, then inspect the page before capturing.
- Handle lazy loading and infinite scroll. Full-page capture does not guarantee that every site will load content that appears only after scrolling. If the page loads more items as the reader scrolls, scroll through the required content, wait for each addition to settle, and then capture.
- Wait for layout to settle. Images, embedded content, expanding banners, or late script updates can change page height between measurement and capture. Wait for those elements or for the measured height to remain stable before taking the screenshot.
- Consider extreme dimensions. Current Chromium source rejects a full-page capture when either measured dimension is at least 128 × 1024 pixels. Very long pages may need to be captured as stable sections and stitched or reviewed separately. The same source notes that page size can change between measurement and capture. These are implementation details that can change, so check the Chromium version you run. [Chromium implementation]
Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| Image ends at the viewport height | The call still requests a viewport screenshot. | Set Playwright fullPage: true, or use CDP captureBeyondViewport: true without clip. |
| Lower sections are blank or missing | Content is asynchronous, lazy-loaded, or added on scroll. | Wait for the required content, scroll to trigger loading where needed, and capture after layout settles. |
| Navigation wait times out on an active site | The selected wait condition expects network activity to stop. | Use a page-specific selector or readiness condition instead of relying on network idle. |
| Screenshot is unexpectedly short after waiting | --timeout elapsed while the page continued loading. |
Wait on actual page state; treat the CLI timeout as a maximum delay, not a readiness check. |
| Full-page capture fails on a very long page | The page may exceed Chromium’s current dimension limits, or its size may change during capture. | Wait for stable dimensions and capture the page in sections if necessary; verify behavior against your Chromium version. |
| The image is clipped despite the CDP flag | A clip parameter or a different capture path may constrain the output. |
Omit clip for full-page capture and verify the command is sent to the intended page target. |
Performance, reliability, and cost considerations
Full-page images can be much larger than viewport images. Long pages take more memory to rasterize and can produce large PNG files; use JPEG or WebP where your workflow permits lossy or modern image output, or capture stable sections when a single image is impractical. Keep the viewport width consistent so responsive layouts do not shift between runs.
Reliability depends on capturing a stable page state. Prefer a selector or application-specific ready signal over an arbitrary delay, and record the final document dimensions if a capture pipeline needs to diagnose intermittent truncation. For pages with endless scrolling or frequently changing content, define a stopping condition such as a known item count or target section.
Local Chrome and Playwright have no per-screenshot API charge, but your job still consumes compute, memory, storage, and maintenance time for browser setup. If you need a managed screenshot endpoint, ScreenshotNeo provides one-call screenshots and bills only clean shots; its response identifies the page verdict and billing status.
Or skip the browser setup
ScreenshotNeo can capture a page with one request. The options and API details are in the ScreenshotNeo API documentation.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An 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 1,000 free screenshots a month, with no card required.
FAQ
Does increasing --window-size capture the whole document?
No. It changes the viewport dimensions. Use an automation library’s full-page option or the CDP beyond-viewport parameter to request the full scrollable page.
Does a full-page screenshot always trigger lazy-loaded images?
Do not assume so. Scroll-triggered loading is site-specific; drive the page through the required scroll and loading steps before capture.
Is networkidle required?
No. It is one possible readiness condition. Sites with ongoing network requests often need a more specific condition tied to the content you want.
Can Chrome CLI’s timeout prove the page is ready?
No. It is a maximum wait before capture, and capture can happen even while the page is still loading.


