Shotkiln Get a free key
← Website screenshot guidesGuide

How to take a full-page screenshot in Playwright

One option, fullPage in Node or full_page in Python, captures the whole page. What it misses: images that wait for a scroll, cookie bars and content inside scrolling elements. Code for both languages, tested, with the fixes.

· Tested with Playwright 1.64.1 for Node and 1.63.0 for Python · 7 min read

The short version

Pass fullPage: true in Node or full_page=True in Python to page.screenshot. Playwright captures the page's full scroll height at the viewport width you set. If the page loads images on scroll, scroll it with the mouse wheel first, or those images come out empty.

full-page.mjsnode full-page.mjs
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto('https://example.com');
await page.screenshot({ path: 'full-page.png', fullPage: true });
await browser.close();
full_page.pypython full_page.py
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 800})
    page.goto("https://example.com")
    page.screenshot(path="full-page.png", full_page=True)
    browser.close()

Install for Node with npm i playwright, or for Python with pip install playwright. Then fetch the browser once with npx playwright install chromium or playwright install chromium. Playwright for Node needs Node 20 or later. The same option is setFullPage(true) in Java and FullPage = true in .NET; we tested only Node and Python.

What we found in testing

We captured test pages at 1280 × 800 with the plain call above, then with the fixes on this page.

Test pagePlain full-page callWith the fixes
8 images with loading="lazy"8 of 8 loaded8 of 8
8 images loaded by IntersectionObserver0 of 88 of 8, after the wheel scroll
Cookie bar fixed to the bottomPainted once, at the bottom of the first screenHidden with the style option
Rows scrolling inside <main>1280 × 800; locator('main').screenshot() gave 1080 × 8001280 × 5640, all 40 rows
Page 30,000 px tallOne PNG, correct top to bottomNo fix needed

Playwright did better than Puppeteer on native lazy images (Puppeteer loaded 4 of 8 in the same test), but images loaded by JavaScript as they come into view stayed empty in both.

A script for real sites

This scrolls the page 400 px at a time with the mouse wheel, waits for the network to go quiet, returns to the top and captures with animations stopped.

full-page.mjsnode full-page.mjs https://example.com
import { chromium } from 'playwright';

const url = process.argv[2] ?? 'https://example.com';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto(url, { waitUntil: 'networkidle' });

// Scroll with the mouse wheel, as a visitor would, so scroll listeners and
// IntersectionObserver-based lazy loading both fire. Then go back to the top.
const height = await page.evaluate(() => document.documentElement.scrollHeight);
for (let y = 0; y < height; y += 400) {
  await page.mouse.wheel(0, 400);
  await page.waitForTimeout(150);
}
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForLoadState('networkidle');

await page.screenshot({ path: 'full-page.png', fullPage: true, animations: 'disabled' });
await browser.close();
full_page.pypython full_page.py https://example.com
import sys
from playwright.sync_api import sync_playwright

url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 800})
    page.goto(url, wait_until="networkidle")

    # Scroll with the mouse wheel so lazy images load, then go back to the top.
    height = page.evaluate("document.documentElement.scrollHeight")
    for _ in range(0, height, 400):
        page.mouse.wheel(0, 400)
        page.wait_for_timeout(150)
    page.evaluate("window.scrollTo(0, 0)")
    page.wait_for_load_state("networkidle")

    page.screenshot(path="full-page.png", full_page=True, animations="disabled")
    browser.close()

Both versions turned the Wikipedia article on screenshots into a 1280 × 4487 PNG of 860 KB with every image loaded, in 3.5 seconds for Node and 3.7 for Python, browser start included.

animations: 'disabled' fast-forwards finite CSS animations and transitions to their end state and resets infinite ones, so a carousel or fade-in doesn't get caught halfway.

Hide cookie bars for the shot only

Elements with position: fixed are painted once where they sit on the first screen. A bottom cookie bar becomes a stripe across the image. Playwright's style option injects CSS for the capture only:

node
await page.screenshot({
  path: 'full-page.png',
  fullPage: true,
  style: '.cookie-bar, #chat-widget { display: none !important; }',
});
python
page.screenshot(path="full-page.png", full_page=True,
                style=".cookie-bar, #chat-widget { display: none !important; }")

On our test page the bar was gone from the image, and afterwards it was still on the page. That makes this safer than editing the DOM when the same page goes on to run a test. To blank out content instead of removing it, such as a live clock, use mask with a list of locators.

Scrollable elements

When an app scrolls a <main> element instead of the page, the document is one screen tall and a full-page shot returns 1280 × 800. A locator screenshot doesn't help either: it captures the box you see. Let the container grow, then capture the page:

node
await page.addStyleTag({
  content: 'html, body { height: auto !important; } main { overflow: visible !important; height: auto !important; }',
});
await page.screenshot({ path: 'panel.png', fullPage: true });
python
page.add_style_tag(content="html, body { height: auto !important; } main { overflow: visible !important; height: auto !important; }")
page.screenshot(path="panel.png", full_page=True)

Our test page went from 1280 × 800 to 1280 × 5640 in both languages. Swap main for the element that has the scroll bar.

One region of a long page

Unlike Puppeteer, Playwright accepts a clip together with fullPage, and measures the clip from the top of the whole page:

node
await page.screenshot({ path: 'region.png', fullPage: true, clip: { x: 0, y: 2000, width: 1280, height: 600 } });

WebP, and no browser to run

Playwright can't write WebP, and a long PNG is heavy: 860 KB for that Wikipedia page. The Shotkiln API returns WebP and runs the browser for you:

pythonimage/webp
import os, requests

r = requests.get("https://shotkiln.com/v1/screenshot",
    params={"url": "https://en.wikipedia.org/wiki/Screenshot", "full_page": "true", "format": "webp"},
    headers={"x-api-key": os.environ["SHOTKILN_KEY"]}, timeout=60)
r.raise_for_status()
open("full-page.webp", "wb").write(r.content)
print(r.headers["x-cache"], r.headers.get("x-render-ms"), len(r.content))

Our run printed MISS 5058 407642: 5.1 seconds of render time and a 408 KB WebP at the same 1280 × 4487, with the footer logos that only load on scroll. Like the script above, the API scrolls the page before a full-page capture; pass "scroll": "false" to skip that. Within the cache time, one hour by default, a repeat request prints HIT None with the same bytes, straight from cache. Keep Playwright for anything behind a login.

Frequently asked questions

How do I take a full-page screenshot in Playwright Python?
Call page.screenshot(path="page.png", full_page=True). The Python API uses snake_case, so the option is full_page rather than fullPage. Scroll through the page first if it lazy-loads images.
Can Playwright save screenshots as WebP?
No. Playwright writes PNG or JPEG, with a quality setting for JPEG. Convert afterwards with an image library such as sharp or Pillow, or use a screenshot API that returns WebP directly.
Why does locator.screenshot() cut off my scrollable element?
It captures the element as laid out on screen, so a scroll container comes out at its visible size: 1080 × 800 in our test. Lift its height and overflow limits first, then take a full-page shot.
How do I hide elements only in the screenshot?
Pass a style string to page.screenshot, such as ".cookie-bar { display: none !important; }". Playwright applies it for the capture and removes it afterwards, so the page itself is unchanged.
Can I capture one region of a long page?
Yes. Combine fullPage with a clip. The clip is measured from the top of the whole page, so a clip starting at 2,000 px captures content well below the first screen.

Full-page screenshots as WebP, one request

Send a URL and get the whole page back as PNG, JPEG or WebP. 100 free screenshots a month, no card.

Related