Written By Hanzala Saleem
Updated At June 15, 2026 | 7 min read
If you have ever tried to screenshot a landing page and ended up with just the hero section cut off at the fold, you already know the frustration. Playwright's fullPage option solves that by capturing everything from the top of the document to the very bottom in a single image, no manual scrolling or stitching required.
Below is everything you need to set up, common failure modes, and the fixes that actually work.
A full page screenshot in Playwright captures the entire scrollable content of a webpage, not just what is visible in the browser window. By default, Playwright's page.screenshot() only captures the visible viewport. Setting fullPage: true overrides this behavior and renders everything from the top of the document down to the last pixel of content.
Internally, Playwright calculates the total scroll height of the document and expands the rendering surface to match before taking the capture. The result is a single PNG (or JPEG) that represents the complete page layout.
import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
// Captures only the visible viewport (default)
await page.screenshot({ path: "viewport.png" });
// Captures the entire page from top to bottom
await page.screenshot({ path: "fullpage.png", fullPage: true });
await browser.close();
The difference is one property. The output file size, however, can vary dramatically depending on how long the page is.
| Factor | Viewport Screenshot | Full Page Screenshot |
|---|---|---|
| Captured area | Visible viewport only | Entire scrollable document |
| Affected by viewport size | Yes | Only for layout width |
| Output file size | Small | Large (varies by page length) |
| Render time | Fast | Slower on long pages |
| Lazy-loaded content | Often missed | Can be missed without extra steps |
| Best use cases | Above-the-fold previews, UI component testing | Documentation, archiving, design reviews, compliance |
| Sticky headers/footers | Appear once | Can appear multiple times |
Use a viewport screenshot when you need a quick visual snapshot of the above-the-fold experience, for example checking how a hero section renders on mobile. Use full page when you need the complete picture: landing page audits, archiving legal pages, documenting competitor layouts, or generating PDFs.

Here is the full setup from scratch.
npm init -y
npm install playwright
npx playwright install chromium
You only need the Chromium browser for screenshot work in most cases, which keeps the download small.
import { chromium } from "playwright";
import * as fs from "fs";
async function captureFullPage(url: string, outputPath: string) {
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
});
const page = await context.newPage();
// Wait until network is mostly idle before capturing
await page.goto(url, { waitUntil: "networkidle" });
await page.screenshot({
path: outputPath,
fullPage: true,
});
await browser.close();
console.log(`Screenshot saved to ${outputPath}`);
}
captureFullPage("https://example.com", "screenshot.png");
npx ts-node screenshot.ts
Or if you are using plain JavaScript:
node screenshot.js
The output file lands in your working directory. For very long pages, expect the file size to be anywhere from a few hundred kilobytes to several megabytes.

Many modern pages only load images when they scroll into the viewport. Playwright's full page capture does not scroll the page before taking the screenshot by default, so off-screen images are often blank or missing.
Fix: Scroll the page before capturing.
await page.goto(url, { waitUntil: "networkidle" });
// Scroll to trigger lazy loading
await page.evaluate(async () => {
await new Promise<void>((resolve) => {
let total = 0;
const step = 300;
const timer = setInterval(() => {
window.scrollBy(0, step);
total += step;
if (total >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 100);
});
});
await page.screenshot({ path: "fullpage.png", fullPage: true });
Pages that load more content as you scroll down will never have a fixed bottom, which means full page screenshots on these sites either hang or capture an incomplete portion.
Fix: Disable infinite scroll before capturing by injecting CSS or intercepting the XHR calls that trigger new content. Alternatively, set a maxScrolls limit in your scroll loop and capture what loads within that boundary.
Fixed-position headers, cookie banners, and chat widgets are positioned relative to the viewport, not the document. During a full page capture, Chromium adjusts the rendering surface to the full document height, and fixed elements can end up mispositioned, stretched, or duplicated depending on how the page handles the resize.
Fix: neutralize fixed positioning before capturing. Inline-style selectors miss most real-world cases, since sticky headers are usually styled through stylesheets, so check the computed style in JavaScript:
await page.evaluate(() => {
for (const el of Array.from(document.querySelectorAll("*"))) {
const pos = getComputedStyle(el).position;
if (pos === "fixed" || pos === "sticky") {
(el as HTMLElement).style.position = "absolute";
}
}
});
await page.screenshot({ path: "fullpage.png", fullPage: true });Setting absolute keeps the element in the flow at its current spot instead of pinning it to every screen. If you would rather drop these elements entirely, set display: none in the same loop.
Animations freeze mid-frame during capture, which makes screenshots look unpolished, particularly on hero sections with moving elements.
Fix: Disable animations globally.
await page.addStyleTag({
content: `
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
`,
});
This is the one that catches most people in production. Headless Chromium's software rendering backend has a maximum texture size of 16384 pixels, and that becomes a hard ceiling on screenshot height. Pages taller than that tile or truncate: the image repeats or cuts off every 16384 pixels instead of capturing cleanly. The exact ceiling depends on the GPU backend, but 16384 is the common headless value.
On top of the texture limit, very tall pages drive memory up during compositing and can crash the render process outright on long captures.
Fix: check the page height before capturing, and fall back when it exceeds the ceiling.
const height = await page.evaluate(() => document.documentElement.scrollHeight);
const MAX = 16384;
if (height > MAX) {
// Option A: capture in clipped sections and stitch them yourself
// Option B: switch to JPEG to cut compositing memory
await page.screenshot({
path: "fullpage.jpg",
type: "jpeg",
quality: 80,
clip: { x: 0, y: 0, width: 1280, height: MAX },
});
} else {
await page.screenshot({ path: "fullpage.png", fullPage: true });
}JPEG output uses less memory during encoding than PNG, which alone resolves some crashes on borderline pages. For pages well past the limit, capturing in clipped sections and stitching is the reliable route.
If you're capturing more than ~50 URLs a day, or need reliable full-page output across sites you don't control, managing Playwright yourself becomes the bottleneck, not the code.
ScreenshotAPI.net is a managed screenshot API built on a real Chromium rendering infrastructure. You send one HTTP request, and it handles the rest, including full page capture, lazy-load scrolling, ad blocking, and cookie banner removal.
curl "https://shot.screenshotapi.net/screenshot?token=YOUR_API_KEY&url=https://example.com&full_page=true&output=image"
The full_page=true parameter tells the API to capture the entire scrollable content of the page, the same behavior as Playwright's fullPage: true.
const axios = require("axios");
const response = await axios.get("https://shot.screenshotapi.net/screenshot", {
params: {
token: "YOUR_API_KEY",
url: "https://example.com",
full_page: true,
lazy_load: true,
block_ads: true,
no_cookie_banners: true,
output: "image",
},
responseType: "arraybuffer",
});
require("fs").writeFileSync("screenshot.png", response.data);
Notice lazy_load: true, which automatically handles the scroll-to-trigger problem described earlier. You do not need to write custom scroll logic.
| Task | DIY Playwright | ScreenshotAPI |
|---|---|---|
| Full page capture | fullPage: true |
full_page=true |
| Lazy-load images | Custom scroll loop | lazy_load=true |
| Ad blocking | Custom request interception | block_ads=true |
| Cookie banner removal | Custom CSS selectors | no_cookie_banners=true |
| Retina / high-res output | Custom deviceScaleFactor |
retina=true |
| Viewport control | newContext({ viewport }) |
width and height params |
| Scheduling | Cron + your own server | Built-in scheduled captures |
For large-scale use cases like visual monitoring, competitor archiving, or bulk documentation generation, the API approach removes the infrastructure layer entirely. See the full parameter reference for all available options, viewports, output formats (PNG, JPG, WebP, PDF), and cloud storage integration. You can also check the Node.js SDK quickstart to skip the raw HTTP calls entirely.
It works on most standard pages. Sites with infinite scroll, heavy JavaScript frameworks that render content only on interaction, or server-side pagination may produce incomplete results. The scroll-before-capture technique described above improves coverage for lazy-loaded content, but infinite scroll pages require additional handling.
fullPage: true is a single, stitched capture of the entire document. Taking multiple viewport screenshots and merging them is a manual workaround that produces the same result but with more complexity, potential gaps between images, and inconsistent results on pages where content shifts during scrolling.
Yes. You can authenticate with Playwright by filling login forms programmatically, injecting session cookies via context.addCookies(), or storing authentication state with context.storageState(). Once authenticated, page.screenshot({ fullPage: true }) works the same way on protected pages.
Yes. Use page.pdf({ path: 'page.pdf', fullPage: true }) instead of page.screenshot(). PDF output respects the full page layout and is useful for archiving or compliance. Note that PDF rendering uses print media queries, so the visual output may differ slightly from the screenshot version.
Yes. ScreenshotAPI.net lets you capture full page screenshots with a single HTTP request. Pass full_page=true along with your URL and API key, and the API returns a complete screenshot without any browser management on your end. There is a free plan that includes 100 screenshots to get started.