ScreenshotNeo

BlogGuides

Puppeteer Page API: Take Screenshots, Generate PDFs, and Control Pages

Use Puppeteer’s Page API to navigate, interact with pages, capture screenshots, generate PDFs, and handle common automation edge cases.

By the ScreenshotNeo team4 October 20269 min read

Puppeteer’s Page API controls one browser tab: navigate to a URL, inspect or interact with the rendered page, wait for conditions, and save screenshots or PDFs. For a full-page screenshot, call page.screenshot({ path: 'page.png', fullPage: true }). For a PDF, call page.pdf({ path: 'page.pdf' }); PDFs use print CSS by default. This guide uses Node.js and Puppeteer, then shows equivalent ScreenshotNeo calls for cases where you do not want to run a browser yourself.

Install Puppeteer and capture a page

Install Puppeteer in a Node.js project. The standard package downloads a compatible browser during installation. If your environment manages Chrome separately, see the browser configuration notes below.

npm install puppeteer

Save this as capture.mjs and run it with node capture.mjs https://example.com. It writes a viewport screenshot and a PDF, checks the main document’s HTTP status, and closes the browser even if navigation or capture fails.

import puppeteer from 'puppeteer';

const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });

  const response = await page.goto(url, {
    waitUntil: 'networkidle2',
    timeout: 60_000,
  });

  if (response && !response.ok()) {
    throw new Error(`Main document returned HTTP ${response.status()}`);
  }

  await page.screenshot({ path: 'page.png', type: 'png' });
  await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
} finally {
  await browser.close();
}

networkidle2 is one possible navigation condition, not a guarantee that every application has finished rendering. For pages with delayed content, wait for a meaningful selector or application-specific signal before capturing. The [Puppeteer Page API reference](https://pptr.dev/api/puppeteer.page) documents the page lifecycle and its methods.

page.goto(url) resolves to the main resource response. Inspect its status when a 404 or 500 should fail your job: valid HTTP error responses do not necessarily make navigation throw. The response can be null for cases such as about:blank or same-URL navigation that only changes the hash.

Choose a wait condition based on what the page needs:

  • domcontentloaded: the initial HTML has been parsed; useful when you will wait for a specific element next.
  • load: the load event has fired, including load-blocking resources.
  • networkidle0 or networkidle2: network activity has quieted under Puppeteer’s respective connection thresholds. Persistent polling or analytics can make network-idle waits unsuitable.

When an action triggers navigation, start waiting for the navigation at the same time as the action. This avoids missing a fast navigation:

const [response] = await Promise.all([
  page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
  page.locator('a.some-link').click(),
]);
console.log('Navigated to:', response?.url());

If the click updates a single-page app without a document navigation, wait for the resulting URL, selector, or application state instead of waitForNavigation().

Find elements, interact, and evaluate page code

Use locators for user-like actions. Puppeteer recommends locators because they wait for an element to exist and be in a suitable state for the action. This helps avoid races where a click runs before the target is ready.

await page.locator('input[name="email"]').fill('dev@example.com');
await page.locator('button[type="submit"]').click();
await page.locator('[data-testid="account-home"]').wait();

For custom inspection, page.evaluate() runs a function in the page’s JavaScript context and returns its serializable result. Arguments are passed explicitly:

const title = await page.evaluate(() => document.title);
const heading = await page.evaluate((selector) => {
  return document.querySelector(selector)?.textContent?.trim() ?? null;
}, 'h1');
console.log({ title, heading });

If the callback returns a promise, Puppeteer waits for it. Use evaluateHandle() when you need to retain a reference to an object in the page context; dispose the handle when finished. page.$eval(selector, callback) runs a callback on the first matching element and throws if there is no match, so use a locator wait or check for presence if absence is expected.

See the [Puppeteer interactions guide](https://pptr.dev/guides/page-interactions) for locators and the [Page API](https://pptr.dev/api/puppeteer.page) for evaluation methods.

Take viewport, full-page, and clipped screenshots

page.screenshot() returns image bytes by default. Providing path writes a file; when type is omitted, Puppeteer can infer the format from the filename extension. Full-page capture is opt-in. A clip captures a rectangular area in page coordinates.

// Visible viewport
await page.screenshot({ path: 'viewport.png', type: 'png' });

// Entire document, beyond the viewport
await page.screenshot({ path: 'full-page.png', fullPage: true });

// A specific rectangle in CSS pixels
await page.screenshot({
  path: 'chart.png',
  clip: { x: 120, y: 180, width: 800, height: 450 },
});

PNG is lossless and has no quality setting. JPEG and WebP are lossy formats; their quality option controls output quality. Use omitBackground: true when you need a transparent background, where supported by the chosen format. Set viewport dimensions and device scale factor before navigation or capture when output dimensions matter:

await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 2 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'retina.png', fullPage: true });

Full-page screenshots can be much taller and larger than viewport captures. Pages that lazy-load images may need scrolling before capture so the assets are requested and rendered. A clip must fit the page’s rendered coordinate space; wait for layout changes to settle before calculating it.

The [Puppeteer screenshot API](https://pptr.dev/api/puppeteer.page.screenshot) describes screenshot parameters and return values. During a screenshot, creating or closing pages in the same BrowserContext waits for the capture to finish; this can affect parallel capture orchestration.

Generate PDFs with print or screen styles

page.pdf() uses the print CSS media type. For screen styling, set the media type before generating the PDF. Print output can alter colors; add -webkit-print-color-adjust: exact in the page’s CSS when exact colors are required.

// Print CSS (the default)
await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  printBackground: true,
  margin: { top: '12mm', right: '12mm', bottom: '16mm', left: '12mm' },
  preferCSSPageSize: true,
});

// Render with screen media rules instead
await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-layout.pdf', format: 'A4', printBackground: true });

Useful PDF options include paper format (such as A4 or Letter), explicit width and height, landscape, margin, printBackground, scale, preferCSSPageSize, and page ranges where supported by the installed version. CSS @page rules can define page size and margins. Avoid setting contradictory CSS page dimensions and API options without deciding which should take precedence.

Generating a PDF from the rendered page is different from navigating to a URL whose main resource is itself a PDF. The Page reference notes that headless shell mode does not support navigation to PDF documents. For an existing PDF, download or process the file with a PDF-specific tool rather than assuming browser navigation will render it as an ordinary page.

See the [Puppeteer PDF API](https://pptr.dev/next/api/puppeteer.page.pdf) for version-specific options.

Control the browser context and page

A Page represents one tab (or extension background page). A practical capture job generally configures its page before navigation, waits for the actual content it needs, then captures and closes resources.

  • Viewport and device scale: page.setViewport({ width, height, deviceScaleFactor }) controls responsive layout and pixel density.
  • Emulated media: page.emulateMediaType('screen') switches CSS media behavior, especially useful before PDF generation.
  • Waits: use locators or waitForSelector for content readiness; use a fixed delay only when the page offers no observable readiness signal.
  • Page-context code: evaluate returns data; evaluateHandle retains a page-side reference.
  • Frames: content inside an iframe belongs to that frame’s document. Locate the frame and interact with its frame context rather than querying the top-level document.
  • Resource lifetime: close pages and browsers in finally blocks so failed jobs do not leave browser processes running.

For session-specific state such as cookies or authentication, configure a browser context and page before navigation. Keep credentials out of source code and logs. Pages may also use service workers, cross-origin frames, or client-side routing, each of which can change what is visible to page-level queries.

Or skip the browser setup

For a one-off or server-side screenshot, [ScreenshotNeo](https://screenshotneo.com) accepts a URL and returns an image or PDF through one API request. See the [ScreenshotNeo API docs](https://screenshotneo.com/docs/) for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
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}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed, and response headers report the page verdict and billing status. An MCP server lets AI agents such as Claude, Cursor, and other MCP clients take screenshots. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. [Create a free ScreenshotNeo account](https://screenshotneo.com/account/sign-up/).

Troubleshooting

Symptom Likely cause Fix
goto times out The chosen lifecycle event never occurs, often because the page keeps network connections open. Use domcontentloaded and then wait for a selector that represents readiness. Increase the timeout only when the page genuinely needs more time.
Capture is blank or content is missing Capture ran before client rendering, an iframe load, or lazy content completed. Wait for a meaningful locator or application state. For lazy images, scroll through the page and allow images to load before full-page capture.
Navigation returns status 404 or 500 without throwing An HTTP error response is still a completed navigation. Check response?.status() or response?.ok() and handle the status explicitly.
Click times out or hits the wrong element The target is absent, covered, moving, or not actionable. Use a locator, wait for the correct state, and target a stable selector. If navigation follows, pair the action with waitForNavigation().
$eval throws No element matched the selector. Wait for the selector or check for existence before invoking $eval; use a locator when interacting.
PDF colors differ from the web page PDF uses print CSS and print color adjustment can change colors. Use screen media if appropriate and set -webkit-print-color-adjust: exact for required exact colors.
Cannot navigate to a PDF URL Headless shell mode does not support PDF document navigation. Download the PDF or use a PDF processing workflow; use page.pdf() to create a PDF from HTML.
Browser launch fails in a container Browser dependencies, executable configuration, or sandbox policy differ from a local desktop. Install the dependencies required by the browser image, use a compatible browser installation, and follow the security guidance for your deployment environment. Do not disable sandboxing without understanding the isolation consequences.

Performance, reliability, and cost

  • Reduce work per capture: use a viewport screenshot when a full document is unnecessary; full-page output takes more rendering and produces larger files.
  • Wait for the required state: a targeted selector is often more reliable than waiting for all network activity to stop on a page with polling or long-lived connections.
  • Bound resources: set navigation and selector timeouts, close pages and browsers reliably, and limit concurrent browser work according to the memory available to the process.
  • Make jobs repeatable: fix viewport, device scale, media type, and wait condition. Dynamic content, fonts, ads, timestamps, and animation can change captures between runs.
  • Check outcomes: inspect navigation status and whether the expected content exists before treating a file as a successful capture. A written image file alone does not prove the page was correct.
  • Account for operations: self-hosted Puppeteer uses your compute, browser maintenance, and operational time. A screenshot API trades browser management for per-plan usage; ScreenshotNeo offers 1,000 monthly shots free, with paid tiers from $5 for 3,000.

FAQ

Does page.screenshot() return a filename?

Without a path, it returns screenshot data (bytes by default). With a path, it writes the capture to disk.

Why does my PDF look different from the browser window?

PDF generation uses print media by default. Switch to screen media before generating if that is the desired layout.

Can Puppeteer capture an element only?

Yes. Locate the element and use its bounding box as the clip rectangle, or use element screenshot functionality supported by your installed Puppeteer version. Ensure the element is visible and its layout is stable before capturing.

Is a 404 a Puppeteer exception?

Usually not. Navigation can complete with an HTTP error response, so inspect the returned response status.