How to Capture a Full Browser Screenshot With Puppeteer or Playwright
Capture an entire scrollable page with Puppeteer or Playwright, save it to a file or buffer, and troubleshoot common full-page issues.
Direct answer: pass fullPage: true to page.screenshot() in Puppeteer or Playwright. This captures the page’s full scrollable content instead of only the visible viewport. Give the call a path to write an image file, or omit it to receive image bytes for further processing.
Full-page capture includes web content inside the page. It does not include browser tabs, the address bar, window borders, or other operating-system chrome.
1. The shortest working examples
Puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
await browser.close();
Playwright
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
await browser.close();
Puppeteer’s fullPage option is documented as taking a screenshot of the full page. Playwright defines it as the full scrollable page, as if the page fit on a very tall screen. See the Puppeteer ScreenshotOptions reference and Playwright screenshots guide.
2. Puppeteer: complete patterns
Save a PNG, JPEG, or WebP
When you provide a path, Puppeteer infers the image type from the extension. PNG is the documented default when no type is otherwise selected.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 60_000
});
await page.screenshot({
path: 'page.webp',
type: 'webp',
quality: 82,
fullPage: true
});
} finally {
await browser.close();
}
Keep the screenshot in memory
const bytes = await page.screenshot({ fullPage: true });
// bytes is a Uint8Array when binary output is used.
Use the in-memory result when you need to upload the image, hash it, run image processing, or return it from an API without creating a temporary file.
Capture a rectangle with clip
await page.screenshot({
path: 'region.png',
clip: { x: 120, y: 300, width: 800, height: 500 }
});
clip limits the output to an x/y rectangle. It is useful for a component or a known region; it is different from fullPage, which targets the complete scrollable document. Puppeteer’s captureBeyondViewport controls capture beyond the viewport when clipping; its documented default depends on whether a clip is supplied.
3. Playwright: complete patterns
Save a full-page image
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', {
waitUntil: 'networkidle',
timeout: 60_000
});
await page.screenshot({
path: 'full-page.png',
fullPage: true,
animations: 'disabled',
caret: 'hide'
});
} finally {
await browser.close();
}
Return bytes for post-processing
const buffer = await page.screenshot({ fullPage: true });
// buffer contains the encoded screenshot bytes.
Control output scale
await page.screenshot({
path: 'css-pixels.png',
fullPage: true,
scale: 'css'
});
Playwright’s scale: 'css' produces one output pixel per CSS pixel. scale: 'device' produces device-pixel output and is the documented default.
Mask dynamic or private elements
await page.screenshot({
path: 'masked.png',
fullPage: true,
mask: [page.locator('[data-private]'), page.getByText('Loading')]
});
mask covers matching locators during capture. Playwright documents a pink default mask color; set a mask color when your visual tests require a different appearance.
4. Playwright’s command-line workflow
If you only need a direct command, Playwright’s CLI supports --full-page and --filename:
playwright-cli screenshot --full-page --filename=full-page.png https://example.com
Use the CLI for one-off captures or shell pipelines. Use the API when you need authentication, readiness checks, masking, custom headers, or post-processing.
5. Options that affect the result
| Need | Puppeteer | Playwright |
|---|---|---|
| Entire scrollable page | fullPage: true |
fullPage: true |
| Output file | path |
path |
| Bytes in memory | Omit path |
Omit path |
| Rectangle | clip |
clip |
| Pixel density | Viewport deviceScaleFactor |
scale: 'css' or 'device' |
| Hide animations/caret | Handle in page CSS or script | animations, caret |
| Cover selected elements | Inject CSS or overlay elements | mask |
Set the viewport before navigation when responsive layout matters. A full-page screenshot extends vertically, but the page still lays itself out using the viewport width and device scale you choose.
6. Dynamic pages and lazy-loaded content
fullPage: true does not guarantee that every lazy-loaded image, API response, animation frame, or client-side route has finished. Wait for a condition that represents readiness for the site you are capturing.
Wait for a known element
await page.goto('https://example.com/catalog', { waitUntil: 'domcontentloaded' });
await page.locator('[data-catalog-ready]').waitFor({ state: 'visible' });
await page.screenshot({ path: 'catalog.png', fullPage: true });
Wait for a specific application state
await page.goto('https://example.com/dashboard');
await page.waitForFunction(() => window.appReady === true);
await page.screenshot({ path: 'dashboard.png', fullPage: true });
Handle lazy images deliberately
await page.evaluate(async () => {
for (const image of document.images) image.loading = 'eager';
const pending = Array.from(document.images)
.filter(image => !image.complete)
.map(image => new Promise(resolve => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', resolve, { once: true });
}));
await Promise.all(pending);
});
await page.screenshot({ path: 'images-ready.png', fullPage: true });
The exact readiness strategy depends on the application. A fixed sleep can help with a known transition, but a selector or application state is usually more deterministic.
7. Full page versus the full browser window
These APIs capture web content rendered by the page. They do not capture the address bar, tabs, browser menus, desktop, or window frame. For an image of the operating-system window or desktop, use an operating-system or desktop capture tool instead.
8. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Only the visible viewport is captured | fullPage is omitted or false. |
Pass fullPage: true. |
| Address bar is missing | Page screenshots target web content, not browser chrome. | Use an OS-level window capture tool. |
| Images are blank | Lazy loading or network requests are unfinished. | Wait for a readiness selector/state and verify image completion. |
| Capture times out | Navigation or a readiness condition never completes. | Raise the timeout only when appropriate; inspect the failing request and use a condition that can actually become true. |
| Sticky header appears repeatedly | The site intentionally keeps a fixed element visible while the page is stitched. | Hide it with page CSS or capture a specific region; Playwright masking can cover it. |
| Fonts change between runs | Web fonts have not loaded before capture. | Wait for document.fonts.ready or a site-specific font-ready signal. |
| Animations cause differences | Transitions or carousels are at different frames. | Disable animations; in Playwright use animations: 'disabled'. |
| Screenshot is too large | The document is very tall or device scale is high. | Use scale: 'css', reduce viewport width or capture sections with clip. |
| Authenticated content is absent | The new page has no session or required headers. | Reuse a browser context with storage state, or set the required cookies/headers before navigation. |
9. Performance and reliability
- Reuse a browser process for batches of URLs, while creating an isolated page or context per capture.
- Choose a readiness signal instead of an unnecessarily long global delay.
- Disable animations and hide the caret when output must be repeatable.
- Use CSS-pixel output when device-pixel density creates files larger than your downstream system needs.
- For very tall pages, consider section captures and stitching if one image exceeds your storage or image-processing limits.
- Close pages and browsers in
finallyblocks so failures do not leak processes. - Record the URL, viewport, browser version, wait condition, and timestamp with each artifact so visual differences can be diagnosed.
Neither library’s full-page option promises that every third-party resource will load or that a page will be visually stable without application-specific waits. Treat navigation, readiness, and screenshot as separate failure points and log each one.
10. Cost and operational choices
Self-hosting Puppeteer or Playwright means operating browsers, fonts, sandbox settings, concurrency, storage, and timeouts. Browser startup and rendering consume CPU and memory, while very tall pages produce larger artifacts. A managed screenshot API can be simpler when you need a repeatable HTTP request, queueing, caching, or many unrelated target sites.
11. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns PNG, JPEG, WebP, or PDF output. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Responses identify the page verdict and billing result with X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for all options. A basic capture:
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(`HTTP ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
ScreenshotNeo also supports full-page capture with lazy images loaded, CSS-element capture, device presets and custom viewports, retina scale, custom CSS and JavaScript, clicks, selector waits, delay or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL-based caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.
12. FAQ
Does fullPage include the browser address bar?
No. It captures the full scrollable document inside the page.
Can I get bytes instead of a file?
Yes. Omit path; Puppeteer returns binary bytes and Playwright returns a buffer.
Should I use Puppeteer or Playwright?
Choose the runtime already used by your project. Puppeteer is a focused Chrome automation API; Playwright adds cross-browser automation, masking, scaling controls, and a screenshot CLI.
Why is a page still incomplete after networkidle?
Network idle only describes observed network activity. The application may still be rendering, waiting for an interaction, or lazy-loading content. Wait for a site-specific selector or state.
Can a full-page screenshot be a PDF?
The Puppeteer and Playwright calls above produce image screenshots. Use each library’s PDF workflow when you need a PDF, or use ScreenshotNeo’s PDF capture endpoint.


