Fix Playwright Python Screenshots That Miss Content Loaded After Scrolling
Playwright’s full-page option captures the document, but it may not trigger scroll-loaded content. Scroll the right target, wait for it to render, then capture.
If a Playwright Python screenshot misses content that appears only after scrolling, first scroll the relevant target or nested container to trigger the page’s loading behavior, then wait for a meaningful, page-specific signal that the content has rendered. Use full_page=True when you want the final image to cover the whole document. That option changes the capture area; it does not, by itself, guarantee that scrolling-triggered content has loaded.
1. Identify what kind of screenshot you need
Playwright’s default page.screenshot() captures the current viewport. Choose the capture method based on whether you need the visible screen, the whole document, or one element.
| Capture | Use it for | Key detail |
|---|---|---|
| Viewport | The currently visible part of the page | page.screenshot(path="page.png") uses this by default. |
| Full page | A tall image of the full scrollable document | Pass full_page=True. Trigger scroll-dependent loading separately. |
| Element | A particular matched element | locator.screenshot() captures the element’s current state. A scrollable element shows its currently visible contents. |
These capture scopes are distinct from the loading trigger. A full-page screenshot is not a substitute for scrolling through an infinite list or waiting for an application to render the next batch. Playwright documents full-page and element screenshots separately from its scrolling actions (screenshots, element screenshots, and scrolling).
2. Scroll the content and wait for it to load
Use a locator for a meaningful lower-page target. Scrolling it into view can trigger an infinite list to load more content. Replace the example text and placeholder wait with a target and rendered-state condition from your application.
from pathlib import Path
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", wait_until="domcontentloaded")
# Scrolling this lower-page target can trigger lazy or infinite content.
page.get_by_text("Footer text").scroll_into_view_if_needed()
# Replace with a real condition, for example an expected item or result count.
page.get_by_text("Expected loaded item").wait_for(state="visible")
page.screenshot(path="page.png", full_page=True)
browser.close()
The wait is deliberately tied to expected content. A fixed delay may happen to work for one run, but it is not a general guarantee that a page finished loading. The right signal depends on the application.
Async Playwright
Use the async API when the surrounding program already uses asyncio or async Playwright. Do not mix the synchronous API into an active event loop.
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()
await page.goto("https://example.com", wait_until="domcontentloaded")
await page.get_by_text("Footer text").scroll_into_view_if_needed()
await page.get_by_text("Expected loaded item").wait_for(state="visible")
await page.screenshot(path="page.png", full_page=True)
await browser.close()
asyncio.run(main())
3. Scroll the correct thing
Pages can scroll at the document level or inside a nested panel. If the target is inside a scrollable container, make sure your locator identifies that content or the container itself.
Scroll a target into view
page.get_by_text("Footer text").scroll_into_view_if_needed()
This is useful when a lower target exists in the page and bringing it into view should trigger loading. Playwright also scrolls automatically before actions such as clicking; an explicit scroll is useful when the script needs to trigger loading or control the screenshot position.
Wheel-scroll a nested container
container = page.get_by_test_id("scrolling-container")
container.hover()
page.mouse.wheel(0, 500)
page.get_by_text("Expected loaded item").wait_for(state="visible")
Hovering the actual scrollable panel helps direct the wheel input to it. Adjust the distance as needed and wait for the application’s expected content before capturing.
Adjust a container’s scroll position
container = page.get_by_test_id("scrolling-container")
container.evaluate("e => e.scrollTop += 500")
page.get_by_text("Expected loaded item").wait_for(state="visible")
Use this when you need to change a specific element’s scroll position directly. In async code, await each Playwright call, including evaluate and wait_for.
4. Capture the intended state
After scrolling and waiting, pick a capture method that matches the desired output:
- Whole tall page:
page.screenshot(path="page.png", full_page=True). - Current viewport after scrolling:
page.screenshot(path="viewport.png"). - One element:
page.get_by_test_id("result-panel").screenshot(path="panel.png"). Scroll a scrollable element or its container to the desired state first.
For an infinite list, one scroll may load only one batch. Continue scrolling and waiting until the target content or stopping condition for your use case is reached. Full-page capture describes the output extent; it does not mean the application has fetched every possible item in an unbounded list.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Everything below the first screen is absent | The call captured only the viewport. | Use full_page=True for the whole document, or scroll and capture the viewport intentionally. |
| The screenshot is tall, but lazy content is still missing | Capture extent changed, but the page’s scroll-triggered loading was never activated or had not finished. | Scroll a lower target into view, wait for an application-specific rendered-state signal, then capture. |
| Scrolling the page does not move the list | The list is inside a nested scrollable container. | Locate the actual panel, hover and wheel-scroll it, or adjust its scrollTop. |
| Only part of a panel appears in an element screenshot | The locator screenshot captures the element’s currently visible scrolled content. | Scroll the panel to the needed position before taking the element screenshot. |
| The expected-item wait times out | The locator may not match, the item may not have loaded, or the wrong container was scrolled. | Check the locator against the rendered page, trigger another loading batch if needed, and verify the correct scroller. |
| Results vary between runs | The capture may occur before the relevant UI state is rendered, or the loading trigger may be inconsistent. | Wait for a meaningful application state rather than relying on an arbitrary delay; confirm that the same target and container are used each run. |
6. Reliability, runtime, and cost considerations
For reliable captures, make the loading condition specific to the content you need. A visible expected item or a known result count is more informative than assuming a fixed delay is enough. For incremental lists, define a stopping condition so the script does not scroll indefinitely.
Full-page images can contain substantially more pixels than viewport images, and repeated scrolling may cause the page to load more data. Choose the smallest capture scope that answers your task, and avoid loading every batch when only a particular section is needed. Playwright’s cited documentation describes capture and scrolling behavior but does not establish universal timing, performance, or cost figures.
7. 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. For a straightforward full-page capture of a URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does full_page=True scroll the page like a user?
It requests a screenshot of the full scrollable page. For content whose loading depends on scroll actions, trigger that behavior and wait for the content separately.
Should I use synchronous or asynchronous Playwright?
Use the API style that fits the rest of your program. Playwright documents both sync and async library usage (Python library).
Can I use full-page capture for an infinite list?
Only for the content that has been loaded and is present when the screenshot is taken. Scroll and wait through the batches you need, then capture.
How do I configure full-page failure screenshots in pytest?
The Playwright pytest plugin provides --full-page-screenshot for automatic failure screenshots, and screenshot capture must be enabled. This plugin setting is separate from passing full_page=True to a direct API call; see the pytest plugin reference.


