ScreenshotNeo

BlogHow-to

How to Capture a Full-Page Website Screenshot with Playwright in JavaScript

Capture an entire page with Playwright’s fullPage option, handle dynamic content and long pages, and save or return the screenshot in Node.js.

By the ScreenshotNeo team4 October 20268 min read

Use Playwright’s page.screenshot() with fullPage: true to capture the full scrollable page instead of only the visible viewport. The example below launches Chromium, loads a URL, and saves the result as a PNG.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle' });
    await page.screenshot({ path: 'screenshot.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Install Playwright and its Chromium browser with npm install playwright followed by npx playwright install chromium. The screenshot API’s fullPage option defaults to false; set it explicitly to include content beyond the viewport. See the Playwright Page screenshot API.

1. Set up and run the JavaScript example

Save the example as capture.js, install the package and browser, then run it with Node.js:

npm init -y
npm install playwright
npx playwright install chromium
node capture.js

For a script that accepts a target URL and output filename, use command-line arguments:

const { chromium } = require('playwright');

(async () => {
  const url = process.argv[2] || 'https://example.com';
  const output = process.argv[3] || 'screenshot.png';
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
    await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
    await page.screenshot({ path: output, fullPage: true });
    console.log(`Saved ${output}`);
  } finally {
    await browser.close();
  }
})();

Run it as node capture.js https://example.com out.png. Use URLs you are authorized to access. Pages behind authentication may need a logged-in browser context, cookies, or an authorization flow.

2. Wait for the page content you need

A page can finish its initial navigation before client-side rendering, images, or lazy-loaded sections are ready. Choose a navigation condition that matches the site, then wait for a meaningful element when possible:

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.locator('main article').waitFor({ state: 'visible', timeout: 15000 });
await page.screenshot({ path: 'article.png', fullPage: true });

Navigation options include load, domcontentloaded, networkidle, and commit. networkidle can be unsuitable for sites with persistent connections or continuous background requests. A selector wait gives you a more direct signal that the content you care about is present. If a page uses lazy loading triggered by scrolling, see the dedicated section below.

3. Screenshot options that matter

page.screenshot() accepts options for output, image format, and capture behavior. The most useful choices for full-page work are:

Option What it does When to use it
fullPage: true Captures the full scrollable page rather than just the viewport. Long articles, landing pages, and page archives.
path Saves the screenshot to a file. Without it, the call returns an image buffer. Use a path for a straightforward local artifact; use the buffer for upload or further processing.
type Selects png, jpeg, or webp. When omitted, the file extension determines the format. Choose a format deliberately when returning a buffer or when output naming may vary.
quality Sets lossy image quality for JPEG or WebP; it does not apply to PNG. Reduce output size when some compression artifacts are acceptable.
scale Uses CSS pixel dimensions or device pixel ratio for the output scale. Use CSS scaling for smaller output; use device scaling when sharper high-density output is needed.
omitBackground Leaves the default background transparent where supported. Useful for compositing, though transparent output is most practical in PNG.
animations Controls animation handling during capture. Disable or finish animations when stable visual comparisons matter.
timeout Sets the maximum time for the screenshot operation. Increase for unusually long pages, while keeping an upper bound in automation.

Consult the API reference for the complete option list and current behavior. Full-page dimensions can be much larger than the browser viewport, so output size and memory use can grow quickly.

4. Capture lazy-loaded content

fullPage: true makes the captured image cover the scrollable page, but a site may load images or sections only after they approach the viewport. Scroll through the document before taking the final screenshot so those resources have a chance to load:

async function scrollThroughPage(page) {
  await page.evaluate(async () => {
    const step = Math.max(400, window.innerHeight);
    for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
      window.scrollTo(0, y);
      await new Promise(resolve => setTimeout(resolve, 150));
    }
    window.scrollTo(0, 0);
  });
}

await page.goto(url, { waitUntil: 'domcontentloaded' });
await scrollThroughPage(page);
await page.screenshot({ path: 'full-page.png', fullPage: true });

The short pause is an example, not a guarantee: some sites need a longer wait or a selector-specific check after scrolling. If the page appends new content as you scroll, the document height can change. In that case, repeat until the height stops increasing or until a known end-of-content marker appears, with a maximum number of passes to prevent an endless loop.

5. Return the image buffer instead of saving a file

Omit path to receive a buffer, which you can write yourself, send to object storage, or pass to an image-processing library:

const image = await page.screenshot({ fullPage: true, type: 'png' });
await require('node:fs/promises').writeFile('page.png', image);
// Or pass `image` to an upload or image-processing function.

For a compressed WebP file, specify the format and a quality value:

const image = await page.screenshot({
  fullPage: true,
  type: 'webp',
  quality: 82
});

6. Control viewport, device scale, and page state

The viewport affects responsive layout and therefore the screenshot’s content and width. Set it when consistency matters. Use a browser context for a specific viewport and device scale factor:

const context = await browser.newContext({
  viewport: { width: 1280, height: 800 },
  deviceScaleFactor: 1
});
const page = await context.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
await page.screenshot({ path: 'desktop.png', fullPage: true, scale: 'css' });

You can also set color scheme, locale, or other context properties to reproduce the page state your workflow needs. For visual regression work, keep viewport, browser version, fonts, locale, color scheme, and page data consistent between captures. A full-page screenshot captures the rendered page; it does not automatically dismiss consent dialogs, authenticate to a site, or turn a dynamic page into a fixed snapshot.

7. Capture a full page with cURL, Python, or Node.js using ScreenshotNeo

For a managed screenshot endpoint, ScreenshotNeo accepts one GET request with the target URL and returns a PNG, JPEG, WebP, or PDF. Its API supports full-page capture, including lazy images. The parameter names used by other screenshot APIs also work, which can make switching easier. See the ScreenshotNeo website and API documentation.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);

Or skip the browser setup

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot tools, and 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get started.

8. Troubleshooting

Symptom Likely cause What to do
Screenshot contains only the visible viewport fullPage was omitted or set to false. Set fullPage: true in the screenshot options.
Images or sections are missing Lazy loading or client rendering had not completed. Scroll through the page, wait for a meaningful selector, then capture.
Navigation times out The site keeps network activity open, is slow, or cannot be reached. Try domcontentloaded and wait for the content selector you need; check the URL and network access.
Screenshot operation times out The page is exceptionally long or resource-heavy. Set a suitable screenshot timeout, reduce unnecessary work, and consider capturing a specific element or viewport if a full document is not required.
Output is unexpectedly large A long document, high device scale, or lossless format creates many pixels. Use scale: 'css', WebP or JPEG where appropriate, or resize after capture.
Layout differs between runs Responsive dimensions, animations, fonts, content, or page state changed. Fix the viewport and context settings, wait for fonts/content, and disable animations for stable comparison.
Browser executable is missing The Playwright package is installed but its browser binaries are not. Run npx playwright install chromium in the environment used to run the script.
Page is blank or blocked The site may require authentication, deny automation, or have a transient load failure. Check access in a normal browser, provide the required authorized session state, and inspect navigation errors and page content.

9. Performance, reliability, and cost

Playwright runs a real browser process, so capture time includes browser startup, navigation, page rendering, and image encoding. Reuse a browser across multiple captures in a controlled worker rather than launching one for every URL. Close pages and contexts when finished, and always close the browser in a finally block so a failed navigation does not leave processes behind.

Full-page captures can consume substantial memory because the browser must render and encode a tall image. Keep concurrency bounded, use CSS scale when device-pixel output is unnecessary, and apply an overall timeout. For a page that grows indefinitely or contains very large embedded content, use a content boundary or capture the relevant section instead of allowing unbounded work.

Playwright itself is an open-source automation library; the operational cost of a self-managed workflow comes from the machine resources, browser installation, maintenance, and any storage or processing you add. ScreenshotNeo pricing is Free for 1,000 shots/month with no card, 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, and every feature is on every plan.

10. FAQ

Does fullPage: true scroll the page while capturing?

It captures the full scrollable page rather than the current viewport. If content loads only when scrolled into view, scroll through the page first so that content has an opportunity to load.

Can Playwright save a full-page screenshot as WebP?

Yes. Set type: 'webp' and optionally set quality. The documented screenshot formats are PNG, JPEG, and WebP.

Can I capture only one part of a long page?

Yes. Use a locator’s screenshot method for a specific element, or keep fullPage false for a viewport capture. Full-page mode is for the complete scrollable document.

What should I use for repeatable visual checks?

Fix the browser environment, viewport, device scale, locale, color scheme, page data, and wait condition. Dynamic ads, timestamps, and other changing content can still cause differences unless your workflow controls them.