Short answer: Playwright’s full_page=True captures the entire scrollable document, not every pixel inside a nested scrolling panel. For a panel, table, chat window, or menu with its own scrollbar, use a locator and either (1) scroll and stitch overlapping screenshots, or (2) temporarily expand the element and capture it. The right method depends on lazy loading, virtualized rows, sticky children, and whether changing the layout is acceptable.
First identify what actually scrolls
“Full screenshot” can describe two different targets:
- The document: the browser page itself scrolls. Playwright can capture it directly with
page.screenshot(full_page=True). - A nested element: a
div, table wrapper, chat pane, drawer, or menu has its ownoverflow: autooroverflow: scroll. A normal locator screenshot captures only the element’s visible, currently scrolled area.
Check the page in DevTools: inspect the suspected element and look for a scrollable client area, then compare scrollHeight with clientHeight. If the document’s document.scrollingElement.scrollHeight is larger than the viewport, use the page method. If a child’s scrollHeight is larger than its clientHeight, use a nested-element strategy.
Capture a complete scrollable page
For a page-level scrollbar, Playwright’s documented full-page option is the simplest and most reliable approach:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com", wait_until="networkidle")
page.screenshot(path="full-page.png", full_page=True)
browser.close()
The asynchronous form is equivalent:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1440, "height": 900})
await page.goto("https://example.com", wait_until="networkidle")
await page.screenshot(path="full-page.png", full_page=True)
await browser.close()
asyncio.run(main())
full_page=True extends the capture to the page’s full scrollable height. It does not automatically expand a child element whose own scrollbar hides content.
#1 Best Overall
- CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
- INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
- THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
- WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
- A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents
Why a locator screenshot is not enough for a nested panel
This captures the element’s clipped bounds as currently rendered:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com/dashboard")
panel = page.locator(".results-panel")
panel.screenshot(path="panel-visible.png")
browser.close()
If .results-panel has a 400-pixel viewport and 4,000 pixels of content, the image normally contains the visible 400 pixels only. Locator screenshots support PNG, JPEG, and WebP; options such as scale change CSS-pixel versus device-pixel output, but do not reveal hidden scroll content.
Strategy 1: scroll, capture, and stitch
Scrolling preserves the page’s rendered layout and is usually the safer choice for complex interfaces. It also gives lazy-loaded content a chance to render. The example below captures overlapping strips, removes the duplicate overlap, and writes one PNG.
Rank #2
- CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
- 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
- SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
- INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
- THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
from io import BytesIO
from PIL import Image
from playwright.sync_api import sync_playwright
URL = "https://example.com/dashboard"
SELECTOR = ".results-panel"
OUTPUT = "results-panel-full.png"
OVERLAP = 40
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900}, device_scale_factor=1)
page.goto(URL, wait_until="domcontentloaded")
panel = page.locator(SELECTOR)
panel.wait_for(state="visible")
# Read dimensions in CSS pixels and record the original position.
metrics = panel.evaluate("""el => ({
scrollHeight: el.scrollHeight,
clientHeight: el.clientHeight,
clientWidth: el.clientWidth,
scrollTop: el.scrollTop
})""")
total = metrics["scrollHeight"]
viewport = metrics["clientHeight"]
step = max(1, viewport - OVERLAP)
positions = list(range(0, max(1, total - viewport + 1), step))
last = max(0, total - viewport)
if positions[-1] != last:
positions.append(last)
strips = []
for top in positions:
panel.evaluate("(el, y) => { el.scrollTop = y; }", top)
# Wait for scroll handlers, lazy content, and paint to settle.
page.wait_for_timeout(150)
panel.scroll_into_view_if_needed()
strips.append(Image.open(BytesIO(panel.screenshot(type="png"))).convert("RGB"))
width = max(image.width for image in strips)
output_height = strips[0].height + sum(image.height - OVERLAP for image in strips[1:])
result = Image.new("RGB", (width, output_height), "white")
y = 0
for index, image in enumerate(strips):
crop = image if index == 0 else image.crop((0, OVERLAP, image.width, image.height))
result.paste(crop, (0, y))
y += crop.height
result.save(OUTPUT)
# Restore the user’s original scroll position.
panel.evaluate("(el, y) => { el.scrollTop = y; }", metrics["scrollTop"])
browser.close()
Install the image library with pip install playwright pillow, and install browser binaries with playwright install chromium. The stitcher assumes the panel’s width and height remain stable. If a scrollbar changes the available width, capture after the first scroll and keep the same dimensions for every strip.
Make stitching robust
- Use overlap: a 20–80 pixel overlap helps avoid gaps caused by fractional scroll positions. The overlap must match the actual duplicate region; it is not a universal constant.
- Use the final position explicitly: stepping by a fixed amount often misses the exact bottom. Always add
scrollHeight - clientHeight. - Wait for content: after each scroll, wait for a known row or image, a short delay, or a page-specific loading indicator to disappear.
- Freeze animations: inject temporary CSS that disables transitions and animations if moving elements create seams.
- Watch sticky children: a sticky header may appear in every strip. Crop that repeated area or temporarily disable stickiness if visual fidelity permits.
- Do not assume one scroll container: nested panels can require selecting the innermost element and separately handling an outer container.
Strategy 2: temporarily expand the element
For a static panel, you can remove its height constraint, set overflow: visible, and capture the resulting element. This is simpler than stitching, but it can reflow the page, alter sticky positioning, trigger different responsive rules, or expose content that is normally virtualized.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com/dashboard", wait_until="networkidle")
panel = page.locator(".results-panel")
panel.wait_for(state="visible")
original = panel.evaluate("""el => ({
height: el.style.height,
maxHeight: el.style.maxHeight,
overflow: el.style.overflow,
overflowY: el.style.overflowY
})""")
panel.evaluate("""el => {
el.dataset.screenshotOriginalStyle = el.getAttribute('style') || '';
el.style.height = 'auto';
el.style.maxHeight = 'none';
el.style.overflow = 'visible';
el.style.overflowY = 'visible';
}""")
page.wait_for_timeout(100)
panel.screenshot(path="panel-expanded.png", type="png")
panel.evaluate("""el => {
if (el.dataset.screenshotOriginalStyle) {
el.setAttribute('style', el.dataset.screenshotOriginalStyle);
} else {
el.removeAttribute('style');
}
delete el.dataset.screenshotOriginalStyle;
}""")
browser.close()
Prefer the original application’s layout rules where possible. For example, changing only max-height may preserve width, while replacing the entire inline style can remove unrelated runtime styles. If the application uses a virtualized list, expansion may still render only the rows near the viewport; use scroll-and-stitch or load all records through the application’s own UI instead.
Lazy loading, virtualization, and dynamic content
Lazy-loaded images
Scroll through the element before the final capture so image observers fire. Wait for each image to report complete and a nonzero natural width, or wait for a page-specific loading state. Otherwise, the stitched image may contain placeholders.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Rank #3
- Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
- Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
- Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
- In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
- Ultra-thin bezels: Maximize your viewing experience with thin bezels.
Virtualized rows
Virtualized tables deliberately remove off-screen rows from the DOM. A single expanded screenshot cannot include nodes that do not exist. Scroll-and-stitch can capture each rendered window, but rows may be recycled and heights can change. A stable row identifier and a wait condition for the first and last visible row make validation easier.
Infinite scrolling
If scrolling loads more records, scrollHeight is not final at the first measurement. Continue scrolling until the loading indicator disappears and the height stops increasing for several checks, or until the UI reports an end-of-list state. Set a maximum number of passes to avoid an endless feed.
Fonts and animations
Use a deterministic browser context, wait for document.fonts.ready, and disable animations when pixel-level comparisons matter. Different font availability changes line wrapping and therefore every subsequent stitch boundary.
Rank #4
- CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
- SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
- MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
- KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
- INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient
Output format, scale, and dimensions
Use PNG for lossless text and UI, JPEG for smaller photographic captures, and WebP when your downstream system accepts it. Playwright’s type option controls the format when saving or returning bytes. scale="css" keeps one output pixel per CSS pixel; scale="device" uses device pixels and can produce larger images on high-DPI contexts. Neither setting captures additional scroll content.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Very tall images can exceed image-viewer or downstream API limits. For reports, save one image per panel or emit a PDF instead of creating a single enormous bitmap. Keep the browser viewport and device scale fixed across runs if you compare screenshots.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Only the visible panel appears | A locator screenshot clips to the element’s viewport. | Use scroll-and-stitch or temporary expansion; full_page=True is for the document. |
| Bottom rows are missing | The loop never captured the exact final scroll position. | Add scrollHeight - clientHeight as the last position. |
| Blank or half-loaded images | Lazy loading or network requests are still pending. | Scroll to trigger loading and wait for a selector, image completion, or loading indicator. |
| Visible seams or duplicated headers | Incorrect overlap, sticky content, or fractional scroll offsets. | Increase or measure overlap, crop repeated sticky regions, and use integer positions. |
| Expanded capture changes the design | Removing the height constraint caused reflow or changed sticky behavior. | Use stitching, or apply a narrower style override and restore the original style afterward. |
| Rows repeat or disappear | The list is virtualized. | Capture each rendered window while validating row identifiers; expansion alone cannot materialize removed rows. |
TimeoutError while locating the panel |
The selector is wrong, the element is inside an iframe, or it appears after a delayed action. | Use a role or test-id locator, wait for the iframe and select its frame, and wait for the panel’s visible state. |
| Image is unexpectedly large | Device-pixel scaling or a very tall scroll area. | Use scale="css", reduce the viewport width only if the layout allows it, or split output. |
Performance and reliability checklist
- Reuse one browser and context for a batch of pages, but create an isolated context when cookies or viewport settings differ.
- Prefer
wait_until="domcontentloaded"plus targeted waits over a global network-idle wait on applications with analytics or long-lived connections. - Measure
scrollHeightafter each pass when content can grow; stop only after the height and loading state stabilize. - Capture at a fixed viewport, device scale, timezone, locale, and authentication state.
- Restore
scrollTopand inline styles in afinally-style cleanup path if later test steps use the same page. - Validate the final image dimensions and, for data-heavy panels, inspect the first and last expected row or label.
- Keep screenshots out of memory when they are huge: save strips to temporary files or process them incrementally before assembling the final image.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request can return PNG, JPEG, WebP, or PDF. It can accept cookie and consent banners like a visitor, then remove more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether it was billed.
For a normal full-page website capture, see the ScreenshotNeo documentation and run:
Best Value
- 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
- 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
- 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same request in Python is:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
And in 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}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', bytes);
For a nested application panel, ScreenshotNeo can target an element by CSS selector and also supports full-page capture with lazy images loaded, custom CSS and JavaScript, clicks before capture, waits for a selector, delay or network idle, hidden selectors, blocked requests or resource types, cookies, headers, user agents, authorization, timezone and geolocation. It also offers dark mode, device presets or custom viewports, retina scale, transparent backgrounds, resizing, chosen-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage and OpenAPI APIs, PDF controls, and parameter names used by other screenshot APIs.
Free tools Windows power users keep installed
One-click scans. No signup required.
An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients, so an AI agent can perform captures without you wiring Playwright into the agent. Free accounts include 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.
Choosing the right approach
| Situation | Recommended method | Main trade-off |
|---|---|---|
| Document-level scrollbar | Playwright full_page=True |
Does not include hidden content inside nested scroll containers. |
| Static, non-virtualized panel | Temporary expansion | Fast and simple, but layout and sticky behavior can change. |
| Lazy-loaded or interactive panel | Scroll-and-stitch | Preserves rendered behavior, but needs waits, overlap handling, and validation. |
| Virtualized or infinite list | Scroll-and-stitch with row checks | More engineering; the DOM may not contain all rows at once. |
Frequently Asked Questions
Can I pass full_page=True to locator.screenshot()?
No. The documented full-page option belongs to the page screenshot operation. A locator screenshot is clipped to the element’s rendered bounds, so nested scroll content needs a custom strategy.
Will increasing the screenshot scale reveal more rows?
No. CSS/device scale changes pixel density only. It does not change the element’s scroll range or include hidden content.
Which strategy is best for a virtualized table?
Scroll-and-stitch is generally the practical option, with waits and checks for row identities. Temporarily expanding the container cannot capture rows that the application has removed from the DOM.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

