How to Fix Cut-Off Content in a Scrolling Webpage Screenshot
A screenshot that stops before the page ends usually has the wrong capture scope or missed scroll-triggered content. Diagnose both and capture the full page with Playwright.
If a webpage screenshot ends at the bottom of the visible browser window, it was probably captured in viewport mode. In Playwright, capture the scrollable document with fullPage: true in JavaScript or full_page=True in Python. If the result is still incomplete, check whether the content loads only after scrolling, whether you captured the correct scroll container, and whether an element or clip restriction narrowed the capture.
1. Check the capture scope first
A viewport screenshot records what is visible in the browser view. A full-page screenshot is a different mode intended to include the scrollable document. Playwright documents both full-page and element screenshots, as well as screenshot options such as clipping. See the Playwright screenshot guide and Page API.
- Viewport: the visible browser area.
- Full page: the scrollable document, as if the page fit on a very tall screen.
- Element: only the selected element and its bounds.
- Clipped region: a rectangular portion of the page.
Before changing page code, check the screenshot call for an element locator or clip rectangle. Those can intentionally restrict what appears. Also identify the actual scrolling region: some pages scroll the document, while others scroll an inner panel.
2. Capture the full page with Playwright JavaScript
Install Playwright in a Node project, then run this complete script. The fullPage option is the key change from a viewport capture.
npm install playwright
npx playwright install chromium
// screenshot.js
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'load', timeout: 60000 });
await page.screenshot({ path: 'page-full.png', fullPage: true });
} finally {
await browser.close();
}
})();
Run it with node screenshot.js. Use a real target URL in place of https://example.com. The documented minimal capture is await page.screenshot({ path: 'screenshot.png', fullPage: true }).
3. Capture the full page with Playwright Python
Install the Python package and Chromium browser, then run the script. Python uses the snake-case option name full_page.
python -m pip install playwright
python -m playwright install chromium
# screenshot.py
from pathlib import Path
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
try:
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com", wait_until="load", timeout=60_000)
page.screenshot(path="page-full.png", full_page=True)
finally:
browser.close()
Run python screenshot.py. Playwright’s full-page setting expands the capture area; it does not guarantee that every site’s asynchronous or scroll-triggered content has appeared.
4. Load content that appears only while scrolling
Some websites defer images or sections until they approach the viewport or a scroll event occurs. A full-page screenshot may not itself cause those page scripts to load everything. A Playwright feature request describes this as a limitation in some capture paths; treat it as a possible cause to check in your own browser and site, not a universal behavior. See Playwright issue 40941.
When content is missing:
- Open the page in a normal browser and scroll through the missing area.
- Wait for the content to appear and images to load.
- In automation, scroll the document in steps and allow it to settle after each step.
- Capture the full page after the relevant content has appeared, then reopen the image and inspect its bottom edge.
async function revealByScrolling(page) {
await page.evaluate(async () => {
const step = Math.max(300, Math.floor(window.innerHeight * 0.8));
for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
window.scrollTo(0, y);
await new Promise(resolve => setTimeout(resolve, 250));
}
window.scrollTo(0, 0);
});
}
await page.goto('https://example.com', { waitUntil: 'load', timeout: 60000 });
await revealByScrolling(page);
await page.screenshot({ path: 'page-full.png', fullPage: true });
This example scrolls the document, so adapt it if a nested panel is the real scroll container. The delay is a settling opportunity, not a guarantee that every network request or animation is complete. For a known lazy section, waiting for a specific selector is more reliable than relying on a fixed delay.
5. Fix nested scroll areas, element capture, and clipping
If the page itself does not scroll but a panel does, window.scrollTo will not reveal that panel’s contents. Scroll the intended element instead, then capture the panel or the whole page according to the output you need.
const panel = page.locator('.results-panel');
await panel.evaluate(async element => {
const step = Math.max(200, Math.floor(element.clientHeight * 0.8));
for (let y = 0; y < element.scrollHeight; y += step) {
element.scrollTop = y;
await new Promise(resolve => setTimeout(resolve, 200));
}
element.scrollTop = 0;
});
await page.screenshot({ path: 'whole-document.png', fullPage: true });
To capture only a component, use an element screenshot; it intentionally excludes surrounding content:
await page.locator('.results-panel').screenshot({ path: 'panel.png' });
If you set clip, its rectangle limits the captured region. Remove or resize the clip if you intend to capture beyond it. For a long panel that is shorter than its full content because of CSS overflow, an element screenshot may only show the visible box; scroll and capture sections, or change the page layout for the capture if you control it.
6. Confirm content is ready, then inspect the output
The screenshot call does not establish that site-specific asynchronous content has finished loading. Choose a readiness condition that matches the page: wait for a known selector, an application state, or a short delay after a scroll-triggered section appears. Reopen the saved file and check the intended bottom, blank areas, repeated sections, and missing images. Compare it with the rendered page after scrolling.
For repeatable captures, keep the URL, viewport, browser engine, and readiness condition consistent. A taller viewport can alter responsive layout and the amount of lazy content initially visible; it does not replace a full-page capture when the goal is the entire document.
7. cURL option for a hosted screenshot
cURL cannot control your local Playwright browser. It can call a screenshot service that renders the URL remotely and returns an image. Check the service’s documentation for the relevant full-page parameter; ScreenshotNeo supports full-page capture with lazy images loaded. Its API documentation describes the request options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-d full_page=true \
-o page-full.webp
Replace YOUR_API_KEY with your key. The code uses ScreenshotNeo’s full-page option; consult the docs for the current parameter names and other capture settings.
8. Troubleshooting checklist
| Symptom | Likely cause | What to change |
|---|---|---|
| Image ends at the viewport bottom | Viewport mode is being used. | Set fullPage: true in JavaScript or full_page=True in Python. |
| Bottom sections are blank or absent | Content may load only after scrolling, or the page was captured before it appeared. | Scroll through the document, wait for the content, then recapture. |
| A panel is cut off but the page is otherwise complete | The panel has its own scroll container. | Scroll the panel itself; capture the panel or document as appropriate. |
| Only a small rectangle or component appears | An element screenshot or clip rectangle limits scope. | Remove the clip or use a page screenshot. |
| Capture fails on a very long page | The resulting image may be large or exceed limits in the environment or tool. | Capture meaningful sections separately, lower the output scale if available, or export a PDF when appropriate. |
| Screenshot is missing after the script exits | The output path may be relative to a different working directory. | Use an absolute path or check the process working directory and write permissions. |
| Page content differs between runs | Dynamic content, animations, viewport changes, or inconsistent readiness timing. | Fix the viewport and wait for a page-specific ready condition before capture. |
9. Performance, reliability, and output size
Full-page screenshots can create much larger images than viewport screenshots, especially on tall pages or high device scale settings. Playwright’s screenshot API supports CSS-pixel and device-pixel scales; higher device scale can increase output dimensions and file size. Use the lowest scale that preserves the detail you need. For very long pages, section captures can be easier to review and handle, though stitching sections adds work and may produce seams or inconsistent states.
Reliability depends on rendering readiness, not merely on requesting a full-page image. Wait for meaningful content, scroll if the site uses scroll-triggered loading, and inspect the saved output. In scheduled capture workflows, record the URL and capture settings with each result so a later mismatch can be diagnosed. No capture method can guarantee that an inaccessible, blocked, or failed page will render correctly.
10. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF. Full-page capture loads lazy images; other relevant controls include CSS-selector element capture, viewport and device presets, retina scale, custom wait conditions, and output resizing. See the ScreenshotNeo API docs for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
- Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Responses include
X-Page-VerdictandX-Billedheaders. - An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
11. Frequently asked questions
Does full-page capture mean the browser scrolls all the way down?
It requests a capture of the scrollable page; it does not necessarily trigger the same scroll events as a person. Scroll first when the site reveals content only on scroll.
Should I use a screenshot or PDF for a long page?
Use an image when you need a visual asset. A PDF is often easier to share or print across many pages; check the tool’s page sizing and range options.
Why does my image look different from the page in my browser?
Compare viewport dimensions and page state, then check whether dynamic sections, animations, or consent overlays differed when the capture ran.
Can a full-page screenshot include an inner panel’s hidden content automatically?
Not necessarily. The document and the panel can have separate scroll areas. Scroll the specific panel and choose a capture scope that includes its content.


