Puppeteer Examples for Browser Automation and Screenshots
Copyable Puppeteer examples for navigating, interacting with pages, capturing screenshots, isolating sessions, and connecting to an existing browser.
Puppeteer automates a browser from JavaScript: launch a browser or connect to one, create a page, navigate to a URL, wait for the state you need, interact, capture, and clean up. Use page.screenshot() for a page image and an element handle’s screenshot() method for one element. The examples below cover those workflows, separate sessions, remote browsers, and common failure cases.
Install Puppeteer in a Node.js project with npm install puppeteer. The package includes a compatible browser installation; if you choose puppeteer-core or a custom browser executable, you are responsible for providing a compatible browser. See the official getting started guide and launch options.
1. Minimal runnable screenshot
Save this as screenshot.mjs and run node screenshot.mjs. It launches a browser, sets a viewport, loads a page, writes a PNG, and closes the browser even if a step fails.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
page.goto() waits according to its navigation option, while screenshotting records the page’s current rendered state. Choose the viewport before navigation if the page responds to viewport size during startup. The official Page API documents navigation and saving screenshots to a path.
2. Choose what and how to capture
Viewport or full-page image
By default, a page screenshot captures the visible viewport. Set fullPage: true to capture the full scrollable page:
await page.screenshot({ path: 'full-page.png', fullPage: true });
A long page may create a very tall image and consume substantial memory. For repeatable captures, set a deliberate viewport and account for content that loads only after scrolling. Full-page capture does not mean every lazy-loaded asset is guaranteed to have loaded; trigger the site’s intended loading behavior and wait for the content you need.
One element
Use a locator to find the element, then capture its handle. Element screenshots attempt to scroll a hidden element into view before capturing it. The resulting image covers that element, not the whole page. See the official screenshots guide.
const card = await page.locator('[data-testid="product-card"]').waitHandle();
try {
await card.screenshot({ path: 'product-card.png' });
} finally {
await card.dispose();
}
If the element is not unique, refine the selector or use a locator that identifies it unambiguously. If its contents are still changing, wait for the specific visible state before capturing.
Format, quality, transparency, and clipping
The screenshot options include type (png, jpeg, or webp where supported by the browser), quality for lossy formats, omitBackground for transparency, and clip for a rectangular capture region. When saving to path, the file extension can determine the image type. Quality is not applicable to PNG. Consult the current ScreenshotOptions reference for option details.
await page.screenshot({
path: 'region.webp',
type: 'webp',
quality: 82,
clip: { x: 0, y: 0, width: 640, height: 480 },
});
await page.screenshot({ path: 'transparent.png', omitBackground: true });
Clipping coordinates are page coordinates; ensure the requested region exists and that your capture settings permit capturing beyond the viewport when needed. Transparent output depends on the page and browser rendering; page styles may still paint their own backgrounds.
3. Wait for the right state before interacting or capturing
Navigation completion and application readiness are different. A page can finish loading while client-side data, fonts, images, or animations are still changing. Prefer a locator wait for the actual element or state required by your next action. Puppeteer recommends Locators because they wait for element presence and a suitable action state. See Page interactions.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com/search', { waitUntil: 'domcontentloaded' });
await page.locator('input[name="q"]').fill('browser automation');
await page.locator('button[type="submit"]').click();
await page.locator('[data-testid="search-results"]').wait();
await page.screenshot({ path: 'search-results.png', fullPage: true });
} finally {
await browser.close();
}
When a click causes navigation, start waiting for navigation and click together so the navigation event is not missed:
await Promise.all([
page.waitForNavigation(),
page.locator('a.next-page').click(),
]);
For lower-level control, waitForSelector() returns an element handle. Dispose of handles you no longer need, especially in loops. A Locator is usually simpler for one-off interactions.
Navigation waits support lifecycle choices such as domcontentloaded, load, and networkidle. Network-idle conditions can be unsuitable for pages that keep polling or streaming requests. Use a meaningful element or application-ready signal when available rather than waiting indefinitely for every network request to stop.
4. Browser automation example: fill, submit, and capture
This complete example illustrates the normal launch-to-close sequence and locator-based interaction. Replace the URL and selectors with ones from the page you control or are authorized to automate.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1365, height: 900 });
await page.goto('https://example.com/form', { waitUntil: 'domcontentloaded' });
await page.locator('input[name="email"]').fill('reader@example.com');
await page.locator('button[type="submit"]').click();
await page.locator('[role="status"]').wait();
await page.screenshot({ path: 'confirmation.png' });
} finally {
await browser.close();
}
Use a test account and non-destructive form when adapting this pattern. Browser automation performs real page actions; it does not make a site’s actions reversible.
5. Isolate cookies and local storage with BrowserContexts
Pages in separate BrowserContexts have isolated cookies and local storage. This makes contexts useful when running independent test users or sessions in one browser process. Close the context after its work to release its pages and associated state. See Browser management.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const context = await browser.createBrowserContext();
try {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'isolated-session.png' });
} finally {
await context.close();
}
} finally {
await browser.close();
}
Use one context per independent session, not one new browser process per page by default. This keeps session data separated while avoiding unnecessary browser launches.
6. Launch a browser or connect to an existing one
Launch locally
puppeteer.launch() starts a browser managed by Puppeteer. This is the simplest local setup and is shown in the examples above. Puppeteer’s bundled browser is the documented guaranteed configuration; a custom executablePath is your responsibility to keep compatible with Puppeteer.
Connect to a running browser
For a browser started separately, connect using its WebSocket endpoint, then disconnect the client when done. Do not close a browser process you do not own unless that is part of your service’s lifecycle contract.
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'remote.png' });
} finally {
await browser.disconnect();
}
Set BROWSER_WS_ENDPOINT to the actual endpoint provided by your browser process or hosting environment; do not publish credentials embedded in a remote endpoint. Browser management documents puppeteer.connect() and WebSocket endpoints. In the browser-client setup, Puppeteer connects to a separate browser with an open debugging port; that client setup cannot launch or download a browser. See Running Puppeteer in the browser.
7. Set viewport and screen configuration
page.setViewport({ width, height }) controls the page’s viewport, which affects responsive layouts and the visible screenshot dimensions. For screen configurations beyond an ordinary viewport, Puppeteer documents the --screen-info argument and dynamic screen methods in headless mode. The --screen-info switch and dynamic screen changes are headless-only; Browser.screens() is available in both headful and headless modes. See Screen configuration.
const browser = await puppeteer.launch({
args: ['--screen-info={800x600 label=primary}{600x800 label=secondary}'],
});
Most screenshot jobs need only a viewport. Use screen configuration when the page or test specifically depends on multiple displays or screen topology.
8. Or skip the browser setup
For a one-off website capture, ScreenshotNeo provides a screenshot API and an MCP server. One GET request returns an image or PDF. The API accepts the URL and an access key; see the ScreenshotNeo API documentation.
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,
)
r.raise_for_status()
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}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month, no card required.
9. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser executable not found | Browser was not installed, or puppeteer-core is used without an executable. |
Install the browser using Puppeteer’s supported setup or provide a compatible executable path. Review the browser installation tools and your launch configuration. |
| Custom browser fails to launch | The binary may not be compatible with the Puppeteer version or its system dependencies. | Try Puppeteer’s bundled browser first. Check the operating system requirements and launch error details before changing launch flags. |
| Screenshot is blank or incomplete | The page may not have reached its useful state, content may load after navigation, or a selector may match the wrong element. | Wait for a specific locator or readiness condition, check the URL and page state, and then capture. |
| Navigation wait times out | The target is slow, unreachable, or never reaches the chosen lifecycle event; persistent requests can also make network-idle waits unsuitable. | Use a realistic timeout, choose an earlier navigation event where appropriate, and wait for the content your task actually needs. |
| Click appears to work but next page is missed | The click and navigation wait were started in the wrong order. | Use Promise.all([page.waitForNavigation(), locator.click()]) as shown above. |
| Element handle is detached or stale | The page replaced the node after the handle was obtained. | Re-query with a Locator after the page update; dispose of handles when finished. |
| Image dimensions differ from expectation | Viewport, full-page mode, device scale, or page layout changed the output dimensions. | Set the viewport explicitly and decide between viewport and full-page capture. Check the screenshot options and current page layout. |
| Remote connection is refused | The browser is not listening at the endpoint, the WebSocket endpoint is wrong, or the debugging port is inaccessible. | Start the remote browser with its connection endpoint available and pass the exact WebSocket endpoint to puppeteer.connect(). |
| Browser processes remain after an error | Cleanup was skipped on an exception. | Put browser or context shutdown in a finally block. Use disconnect() for a browser process managed elsewhere. |
10. Performance, reliability, and cost
- Reuse sensibly: launching a browser has overhead. For batches, keep a browser process open and use isolated contexts for independent sessions; close contexts and browser processes at the end of their lifecycle.
- Wait narrowly: waiting for the exact result element can avoid delays from unrelated background traffic. Avoid arbitrary long sleeps when an observable selector or state is available.
- Control image size: a larger viewport and a very tall full-page image require more image data and memory. Capture only the region or element required when the task allows it.
- Make jobs recoverable: set practical navigation and job timeouts, log the target URL and failure stage, and retry transient failures selectively. A retry cannot fix a bad selector, invalid URL, or incompatible browser.
- Plan for environment differences: fonts, browser version, viewport, locale, timezone, and application data can change rendered output. Fix the variables relevant to your use case when visual consistency matters.
- Account for compute: self-hosted Puppeteer uses your machine or server resources for browser processes, memory, and captured image data. The official documentation provides no general speed or cost benchmark; measure your own workload and hosting environment.
11. Frequently asked questions
Can Puppeteer capture a PDF instead of an image?
Yes. Puppeteer has page PDF functionality for browser-rendered documents. Use it when the deliverable is a printable document rather than a raster screenshot, and consult the current Page API for the relevant options.
Does an element screenshot include the rest of the page?
No. An element handle’s screenshot is scoped to that element. Use page.screenshot({ fullPage: true }) for the full page.
Can I connect Puppeteer to a browser running on another machine?
Yes, if that browser exposes a reachable debugging WebSocket endpoint and the network allows the connection. Connecting does not itself start or install the remote browser.
Are cookies shared between separate BrowserContexts?
No. BrowserContexts isolate cookies and local storage, which is why they work for separate automation sessions.
Can I use Puppeteer to capture a specific CSS region?
Yes. Use a locator to find an element and call its screenshot method, or use the page screenshot’s clipping options when you need a coordinate rectangle.


