The full_page=True parameter in Playwright is one of its best features. Unlike Selenium, which requires complicated scrolling and stitching, Playwright handles it natively. But there are nuances you need to understand.
The Basics
Here’s the simplest full page screenshot:
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') page.screenshot(path='fullpage.png', full_page=True) browser.close()That’s it for static pages. But most modern websites aren’t static.
Understanding Viewport vs Full Page
It’s important to understand the difference:
- Viewport: The visible area of the browser window (e.g., 1920x1080)
- Full Page: The entire scrollable content (could be 1920x5000 or more)
# Viewport only (default)page.screenshot(path='viewport.png')
# Full scrollable pagepage.screenshot(path='fullpage.png', full_page=True)The viewport setting still matters for full page screenshots—it determines the width:
context = browser.new_context( viewport={'width': 1440, 'height': 900})page = context.new_page()page.goto('https://example.com')# Screenshot will be 1440px wide, but full heightpage.screenshot(path='fullpage.png', full_page=True)Handling Lazy-Loaded Images
Modern websites lazy-load images to improve performance. If you take a screenshot immediately, you’ll see placeholder images or blank spaces.
Solution: Scroll First, Screenshot Later
from playwright.sync_api import sync_playwright
def scroll_page(page): """Scroll through the page to trigger lazy loading.""" page.evaluate(''' async () => { await new Promise((resolve) => { let totalHeight = 0; const distance = 300; const timer = setInterval(() => { const scrollHeight = document.body.scrollHeight; window.scrollBy(0, distance); totalHeight += distance;
if (totalHeight >= scrollHeight) { clearInterval(timer); window.scrollTo(0, 0); // Scroll back to top resolve(); } }, 100); }); } ''')
with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto('https://example.com')
# Scroll to trigger lazy loading scroll_page(page)
# Wait for images to load page.wait_for_load_state('networkidle')
# Now take the screenshot page.screenshot(path='fullpage.png', full_page=True) browser.close()Handling Infinite Scroll Pages
Some pages load more content as you scroll (like social media feeds). Here’s how to handle them:
from playwright.sync_api import sync_playwright
def scroll_infinite_page(page, max_scrolls=10): """Scroll an infinite page until no new content loads.""" prev_height = -1 scroll_count = 0
while scroll_count < max_scrolls: # Scroll to bottom page.evaluate('window.scrollTo(0, document.body.scrollHeight)') page.wait_for_timeout(1000) # Wait for content to load
# Check if new content was loaded new_height = page.evaluate('document.body.scrollHeight') if new_height == prev_height: break # No new content
prev_height = new_height scroll_count += 1
# Scroll back to top page.evaluate('window.scrollTo(0, 0)')
with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto('https://news.ycombinator.com')
scroll_infinite_page(page, max_scrolls=5) page.wait_for_timeout(500)
page.screenshot(path='fullpage.png', full_page=True) browser.close()Fixed Headers and Footers
Fixed (sticky) elements can cause issues in full page screenshots—they appear multiple times as the page renders. Here’s how to handle them:
page.goto('https://example.com')
# Hide fixed elements before screenshotpage.add_style_tag(content=''' * { position: static !important; } .sticky-header, .fixed-footer, [style*="position: fixed"], [style*="position: sticky"] { position: relative !important; }''')
page.screenshot(path='fullpage.png', full_page=True)Or more targeted:
# Hide specific fixed elementspage.evaluate(''' document.querySelectorAll('.sticky-header, .fixed-nav').forEach(el => { el.style.position = 'relative'; });''')Maximum Size Considerations
Very long pages can cause issues:
- Memory: Large screenshots consume significant memory
- File Size: PNG files can become huge
- Rendering: Some systems limit image dimensions
Solution: Use JPEG for Large Screenshots
page.screenshot( path='fullpage.jpg', full_page=True, type='jpeg', quality=85)Solution: Capture in Sections
For extremely long pages, capture in sections:
from playwright.sync_api import sync_playwrightfrom PIL import Imageimport io
def capture_in_sections(page, section_height=2000): """Capture a long page in sections and stitch together.""" total_height = page.evaluate('document.body.scrollHeight') viewport_width = page.evaluate('window.innerWidth')
images = [] offset = 0
while offset < total_height: # Scroll to position page.evaluate(f'window.scrollTo(0, {offset})') page.wait_for_timeout(100)
# Capture viewport screenshot_bytes = page.screenshot() images.append(Image.open(io.BytesIO(screenshot_bytes)))
offset += section_height
# Stitch images together total_height = sum(img.height for img in images) result = Image.new('RGB', (viewport_width, total_height))
y_offset = 0 for img in images: result.paste(img, (0, y_offset)) y_offset += img.height
return resultAsync Version for Better Performance
import asynciofrom playwright.async_api import async_playwright
async def full_page_screenshot(url, output_path): async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page() await page.goto(url)
# Scroll to load lazy content await page.evaluate(''' async () => { await new Promise(resolve => { let totalHeight = 0; const distance = 300; const timer = setInterval(() => { window.scrollBy(0, distance); totalHeight += distance; if (totalHeight >= document.body.scrollHeight) { clearInterval(timer); window.scrollTo(0, 0); resolve(); } }, 100); }); } ''')
await page.wait_for_load_state('networkidle') await page.screenshot(path=output_path, full_page=True) await browser.close()
asyncio.run(full_page_screenshot('https://example.com', 'fullpage.png'))Comparison: Playwright vs Selenium Full Page
| Feature | Playwright | Selenium |
|---|---|---|
| Native full page | full_page=True | Not supported |
| Workaround needed | No | Yes (scroll + stitch) |
| Quality | Excellent | Depends on implementation |
| Speed | Fast | Slow |
This is one of the main reasons I recommend Playwright over Selenium for screenshot tasks.
When Screenshots Aren’t Enough
For production systems handling thousands of screenshots, consider:
- Memory management becomes critical
- Browser crashes can lose progress
- Rate limiting and retries add complexity
A screenshot API like ScreenshotOne handles these challenges. See the main Python screenshot guide for when to use each approach.
Summary
Full page screenshots with Playwright are straightforward, but remember:
- Use
full_page=Truefor complete captures - Scroll first to trigger lazy-loaded content
- Handle infinite scroll with max scroll limits
- Remove fixed positioning for clean results
- Use JPEG for very long pages to manage file size
Frequently Asked Questions
If you read the article, but still have questions. Please, check the most frequently asked. And if you still have questions, feel free reach out at support@screenshotone.com.
How to take full page screenshot with Playwright Python?
Use page.screenshot(path='screenshot.png', full_page=True). The full_page parameter captures the entire scrollable page height, not just the visible viewport.
What is the maximum screenshot size in Playwright?
Playwright doesn't have a hard limit, but very long pages (over 16,384 pixels) may cause memory issues. For extremely long pages, consider capturing in sections or using a screenshot API.
How to handle lazy-loaded images in full page screenshots?
Scroll through the page first to trigger lazy loading, then wait for images to load before taking the screenshot. Use page.evaluate() to scroll and page.wait_for_load_state('networkidle') to wait.