How to Capture a Full Screenshot of a Scrolling Page in Playwright
Capture and save a scrolling page in Playwright with JavaScript, TypeScript, or Python. Learn when to use full-page versus element screenshots and how to troubleshoot common issues.
To capture the whole scrollable document in Playwright, set fullPage: true in a page screenshot call. In JavaScript or TypeScript, use await page.screenshot({ path: 'full-page.png', fullPage: true }). In Python, use page.screenshot(path='full-page.png', full_page=True) (or await it with the async API). The default is a screenshot of only the visible viewport, so set the option explicitly.
1. Capture a full page with JavaScript or TypeScript
Install Playwright, save this as screenshot.js, and run it with Node.js. The example opens a page, captures the entire scrollable document, saves a PNG, and closes the browser even if capture fails.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
} finally {
await browser.close();
}
})();
Install the package and browser binaries using the official Playwright installation guide. If the project uses TypeScript, the same call and option apply:
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
} finally {
await browser.close();
}
The screenshot option is named fullPage in JavaScript and TypeScript. With no path, Playwright returns image bytes instead of saving the file; use await and write those bytes yourself if you need a custom destination or upload flow. See the Page API screenshot options.
2. Capture a full page with Python
Install the Python package and its browser binaries with the commands in the Playwright Python installation guide. This synchronous script saves the complete document:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
try:
page = browser.new_page()
page.goto("https://example.com", wait_until="load")
page.screenshot(path="full-page.png", full_page=True)
finally:
browser.close()
In async Python, the method is awaited and the option remains full_page=True:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
try:
page = await browser.new_page()
await page.goto("https://example.com", wait_until="load")
await page.screenshot(path="full-page.png", full_page=True)
finally:
await browser.close()
asyncio.run(main())
Python uses snake_case option names. The Python guide shows both synchronous and asynchronous screenshot calls: Playwright screenshots in Python.
3. Choose the right screenshot scope
| Need | Use | What it captures |
|---|---|---|
| The complete scrollable document | page.screenshot({ fullPage: true }) / full_page=True |
The page-level full scrollable screenshot. |
| One component or region | page.locator('selector').screenshot() |
The selected element’s region. |
| Automated visual regression check | Playwright Test expect(page).toHaveScreenshot() |
A test assertion that waits for stable consecutive screenshots and compares against a baseline. |
An element screenshot is not the same as a page screenshot. For a scrollable element, the locator screenshot shows the content visible at that element’s current scroll position; do not assume it captures every item inside the nested scroller. Consult the Locator API for locator screenshot behavior.
For a visual test, install and configure Playwright Test, then add an assertion such as:
import { test, expect } from '@playwright/test';
test('page visual baseline', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('full-page.png', { fullPage: true });
});
This is a test-runner workflow, not a replacement for saving a screenshot in a standalone script. The assertion behavior is documented in PageAssertions.
4. Handle loading and page behavior
A full-page option defines the screenshot scope; it does not decide when your application has finished rendering. Choose a navigation condition that fits the page, then wait for an application-specific readiness signal when necessary. For example, if the content is known to appear under a selector, wait for it before capture:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('main').waitFor({ state: 'visible' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
For Python, the equivalent is:
page.goto("https://example.com", wait_until="domcontentloaded")
page.locator("main").wait_for(state="visible")
page.screenshot(path="full-page.png", full_page=True)
Do not treat a fixed delay as a universal readiness check: it may waste time on a fast page and still be too short on a slow one. Also avoid assuming that full-page capture automatically loads every lazy image or expands every nested scroll container. The documented guarantee is the page’s full scrollable screenshot; behavior of page-specific lazy content, fixed-position elements, and nested scrollers can depend on the page.
5. cURL, Python, and Node.js with a screenshot API
For a direct HTTP screenshot request, ScreenshotNeo accepts a URL and returns an image or PDF. Its parameter names are compatible with those used by other screenshot APIs. See the ScreenshotNeo API documentation for options and response details.
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,
)
r.raise_for_status()
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}`);
const fs = require('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Or skip the browser setup
ScreenshotNeo captures a URL with one request and offers an API and MCP server for AI agents. The call below saves a screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Cookie banners, popups, and chat widgets are removed before capture, and each cleanup step can be disabled. Bot checks, blank pages, timeouts, and failed loads are never billed; cache hits also cost nothing, and response headers report the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients 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 shots. Full-page capture, element capture, PDF, custom waits, headers, cookies, caching, bulk jobs, and other options are available; every feature is on every plan. Read the API docs, then sign up for 1,000 free screenshots a month, with no card.
6. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Only the visible screen is in the file | The full-page option was omitted, misspelled, or set false. | Set fullPage: true in JavaScript/TypeScript or full_page=True in Python on the page screenshot call. |
| Python reports an unexpected keyword argument | Python option spelling is being mixed with JavaScript spelling. | Use full_page in Python, not fullPage. |
| The capture ends before expected content | Content may not have rendered when capture began, or the content may live in a nested scroller. | Wait for a meaningful page or application readiness condition. For nested scrolling, inspect the element separately and choose a capture approach appropriate to the page; full-page documentation does not promise to combine every nested scroller. |
| An element shot omits its off-screen items | A locator screenshot targets the element and a scrollable element shows its current visible content. | Use a page-level full-page screenshot if the goal is the document, or handle the component’s scrolling explicitly if only that component is needed. |
| Visual comparison differs between runs or machines | OS, browser version, browser settings, hardware, power source, or headless mode can affect rendering. | Generate and compare baselines in the same environment. See Playwright’s visual comparison guidance. |
| Script exits with the browser still open after an error | Browser cleanup was skipped on an exception. | Use finally or Python’s async context manager as in the examples. |
7. Performance, reliability, and cost
A full-page image covers more pixels than a viewport image, so tall pages can take longer to capture and produce larger files. Capture only the needed scope, choose an output format and image handling strategy that suits the destination, and close browser instances reliably. If generating many screenshots, reuse a browser process where appropriate while creating isolated pages for separate jobs; handle navigation and screenshot failures explicitly and apply bounded timeouts in the surrounding workflow.
For screenshot tests, keep browser, operating system, settings, and execution mode consistent between baseline generation and comparison. Playwright cautions that rendering can vary across these conditions, so mismatched environments create noisy visual diffs. A browser-based workflow has infrastructure and execution costs that depend on where and how it runs; Playwright itself does not impose a per-screenshot API charge. ScreenshotNeo’s free tier is 1,000 shots per month with no card; paid tiers are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free. Only clean shots are billed.
8. FAQ
Does full-page mean the browser scrolls down and saves separate images?
The API describes the result as a screenshot of the full scrollable page. You generally do not need to write a manual scroll-and-stitch loop for this documented page-level use.
Can I save the returned screenshot somewhere other than a local file?
Yes. Omit path to receive image bytes from Playwright, then pass those bytes to your own storage or response code.
Can I use the screenshot as a visual regression baseline?
Yes. Playwright Test’s toHaveScreenshot assertion supports full-page capture. Keep the baseline and comparison environment consistent.
Is a full-page screenshot the same as a PDF?
No. A screenshot is a raster image of the rendered page. Playwright also provides PDF generation in supported browser workflows; use the appropriate PDF API when you need a document rather than an image.


