Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Playwright’s Python API and set full_page=True. That option captures the entire scrollable document—not only the pixels currently visible in the browser window. A reliable capture also needs a deterministic viewport, an explicit readiness policy, overlay handling, lazy-content handling, and a stable output format.
This guide shows a complete Playwright workflow, an asynchronous version, Selenium’s Firefox-specific alternative, a lower-level Chromium option, and the failure modes that make “full-page” images incomplete or inconsistent.
Playwright: the recommended Python method
Playwright’s documented definition of a full-page screenshot is a capture of the full scrollable page, as if the page fit on a very tall screen. The key argument is full_page=True.
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 →Install Playwright and its browser
- Install the Python package:
python -m pip install playwright. - Install the browser binaries:
python -m playwright install chromium. - Save the following script as
capture_full_page.py.
Complete synchronous example
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="page.png", full_page=True)
browser.close()
Run it with python capture_full_page.py. The resulting page.png contains the full document. The viewport controls the page’s responsive layout; it does not limit the captured height when full_page=True is enabled.
#1 Best Overall
Asynchronous Python
Use the async API when your application already uses asyncio or when several captures must be coordinated.
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="page.png", full_page=True)
await browser.close()
asyncio.run(main())
Make the capture deterministic
Choose a readiness policy
networkidle waits for a period with no network connections, but it is not universally the right definition of “ready.” Analytics, advertisements, WebSockets, and polling can keep a page busy indefinitely. For an application with a known ready marker, navigate normally and wait for that selector instead:
page.goto("https://example.com/dashboard", wait_until="domcontentloaded")
page.locator("[data-page-ready='true']").wait_for(state="visible")
page.screenshot(path="dashboard.png", full_page=True)
A fixed delay is useful only when the site has a predictable animation or delayed render that cannot be represented by a selector. Prefer an application-state condition when one exists.
Set the viewport and scale intentionally
Use the same viewport for every run so responsive breakpoints do not change between images. Playwright supports scale="css" for an image whose dimensions follow CSS pixels, or scale="device" for device-pixel output. CSS scale is often easier to compare in visual tests; device scale can preserve more physical pixels on high-density displays.
page.screenshot(
path="stable.webp",
full_page=True,
type="webp",
quality=85,
scale="css"
)
PNG is lossless and best for pixel comparisons. JPEG is usually smaller when photographic content dominates. WebP is useful when the rest of your pipeline accepts it. JPEG quality applies to JPEG output.
Remove motion and transient UI
Animations, blinking cursors, carousels, and delayed transitions can produce different images on every run. Playwright’s screenshot API supports animation handling and an optional stylesheet. You can also inject CSS that freezes motion and hides elements that should not appear in a test image:
Rank #2
page.screenshot(
path="comparison.png",
full_page=True,
animations="disabled",
style="""
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
"""
)
Use masking when a dynamic value must remain in place but should not affect comparisons. Hide selectors only when removing the element reflects the screenshot’s purpose; otherwise you may conceal a layout bug.
Handle consent banners, authentication and overlays
A cookie dialog can obscure the top of a page, while a sticky chat widget can cover content throughout a full-document capture. Automate the same decision a real visitor would make before taking the image:
page.goto("https://example.com", wait_until="domcontentloaded")
accept = page.get_by_role("button", name="Accept all")
if accept.is_visible():
accept.click()
page.locator("[data-page-ready='true']").wait_for(state="visible")
page.screenshot(path="clean.png", full_page=True)
For login-protected pages, create a Playwright storage state after authenticating and load it when creating the context. Keep credentials and session files out of source control. If a banner is inside an iframe, locate the correct frame before clicking.
Lazy-loaded images and infinite scrolling
Full-page capture does not guarantee that every lazy resource has already loaded. If images load only after entering the viewport, trigger the site’s loading behavior first. A simple bounded scroll can activate common lazy-loading implementations:
page.goto("https://example.com/articles", wait_until="domcontentloaded")
page.evaluate("""async () => {
await new Promise(resolve => {
let y = 0;
const step = 700;
const timer = setInterval(() => {
window.scrollBy(0, step);
y += step;
if (y >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 100);
});
}""")
page.screenshot(path="lazy-loaded.png", full_page=True)
This is not an infinite-scroll crawler. For feeds that append content as you scroll, define a stopping rule—such as a known item count or a “no more results” marker—then return to the top and capture. Otherwise the page may grow without bound.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallUseful screenshot options
Playwright’s screenshot API provides controls for:
- Format: PNG, JPEG, or WebP; JPEG quality can be selected.
- Scale: CSS pixels or device pixels.
- Timeout: a limit for the screenshot operation in milliseconds.
- Masking: cover selected locators in a consistent color.
- Animation handling: disable or allow animations.
- Background: omit the default background when producing an image with transparency-related behavior.
- Stylesheet: apply capture-only CSS without changing the application bundle.
Capture a single element when the document is not the unit you need:
page.locator("main article").screenshot(path="article.png")
Remember that element screenshots and full-page screenshots answer different questions. The former captures one layout box; the latter captures the document’s scrollable page.
Selenium’s Firefox full-document method
If your team already uses Selenium, Firefox WebDriver documents a dedicated full-page call. The generic Selenium methods such as get_screenshot_as_file() are viewport/current-window methods and should not be described as full-document capture unless the selected driver explicitly documents that behavior.
Recommended Free Tools
from selenium import webdriver
options = webdriver.FirefoxOptions()
options.add_argument("-headless")
driver = webdriver.Firefox(options=options)
driver.get("https://example.com")
driver.get_full_page_screenshot_as_file("page.png")
driver.quit()
Firefox WebDriver also exposes save_full_page_screenshot() and byte/base64 variants. Use a try/finally block in production so the driver is closed if navigation or capture raises an exception.
Chromium CDP: a lower-level option
Projects that already speak the Chrome DevTools Protocol can use the Page domain’s captureBeyondViewport boolean to capture beyond the viewport. CDP requires you to manage protocol commands, image data, and document dimensions yourself, so it is less convenient than Playwright’s Python API. Choose it when CDP is already a core part of your tooling, not merely to avoid one Playwright dependency.
Reliability and CI checklist
- Pin the browser engine and run the same viewport in local and CI environments.
- Wait for an application-specific ready signal where possible.
- Accept or reject consent dialogs and establish the intended login state.
- Trigger lazy loading and set a finite rule for infinite-scroll pages.
- Disable animation or inject capture-only CSS for visual comparisons.
- Set a screenshot timeout appropriate to the site and network environment.
- Choose PNG, JPEG, or WebP based on whether fidelity, size, or deployment compatibility matters.
- Verify that the output file exists and is non-empty before publishing it or uploading it.
- Always close the browser or driver, including on exceptions.
Troubleshooting common failures
The image stops at the viewport
Check that you called page.screenshot(..., full_page=True), not the default viewport screenshot. In Selenium, use Firefox’s documented full-page method rather than a generic WebDriver screenshot call.
Images or cards are missing below the fold
The site probably uses lazy loading. Scroll through the document or invoke the application’s own loading mechanism, wait for image/network completion, then capture. For infinite scroll, implement an explicit stopping condition.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →The script hangs while waiting for network idle
Polling, analytics, ads, or WebSockets may prevent an idle state. Use domcontentloaded followed by a selector, response, or application-ready marker instead.
A consent dialog or chat widget covers content
Locate and interact with the dialog before capture. If it is in an iframe, switch to that frame. For a widget that should not be part of the image, hide its selector only after confirming that removal is appropriate.
The screenshot changes on every run
Fix the viewport and browser version, disable animations, freeze time-dependent UI where your application permits it, and mask unpredictable data. Also ensure fonts and external assets are available in CI.
The browser cannot launch in CI
Install the matching Playwright browser binaries, use headless mode, and check the runner’s sandbox requirements. A package installation alone does not install Chromium.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The output is unexpectedly large
Use JPEG when lossless output is unnecessary, WebP where supported, or scale="css" instead of device-pixel scale. Reducing the viewport width can also reduce the document’s rendered width, but it may change responsive layout.
Or skip the browser setup
ScreenshotNeo is a hosted website screenshot API and MCP server. It removes cookie banners, newsletter popups and chat widgets before capture; bot checks, blank pages, failed loads and cache hits are not billed, and each response reports the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info and capture_pdf—let Claude, Cursor and other MCP clients request captures without you managing a browser.
One GET request returns PNG, JPEG, WebP, or a PDF. The API supports full-page capture with lazy images loaded, CSS-selector element capture, custom CSS and JavaScript, clicks, waits, blocking rules, headers, cookies, user agents, authorization, timezone, geolocation, transparency, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Existing integrations can use the parameter names common to other screenshot APIs.
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 documentation for response headers, options, PDF settings, and authentication details.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsBest Value
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
FAQ
Frequently Asked Questions
Does full_page=True include content outside the HTML document?
It captures the page’s full scrollable document. Browser chrome, operating-system windows, and separate tabs are not part of the page.
Which format should I use for visual regression tests?
Use PNG when pixel fidelity matters. JPEG or WebP can reduce storage and transfer size when small differences from compression are acceptable.
Can I capture a page that requires a login?
Yes. Authenticate in the browser context, preserve the intended session state securely, and capture only after the authenticated page reaches its ready condition.
Free tools Windows power users keep installed
One-click scans. No signup required.
Is Selenium’s full-page screenshot available in every browser?
The documented dedicated full-document methods cited here are Firefox WebDriver methods. Generic Selenium screenshot calls should be treated as viewport captures unless that driver documents otherwise.
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.

