Puppeteer Browser API: Launch and Control Chrome
Learn to launch or connect Puppeteer to Chrome, choose the right package, create isolated contexts, automate pages, and capture screenshots or PDFs.
Puppeteer launches Chrome or connects to an existing browser, then lets JavaScript create isolated browser contexts and pages to navigate and interact with websites. For the fewest browser compatibility surprises, install puppeteer, which downloads a compatible Chrome for Testing. Use puppeteer-core when you want to select and manage the browser executable yourself.
1. Install Puppeteer and launch Chrome
The puppeteer package includes Puppeteer and downloads Chrome for Testing by default. The following complete example launches Chrome, creates a context and page, navigates to a site, changes the viewport, takes a screenshot, and closes the browser.
npm install puppeteer
// screenshot.mjs
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
timeout: 30_000,
});
try {
const context = await browser.createBrowserContext();
try {
const page = await context.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await context.close();
}
} finally {
await browser.close();
}
Run it with node screenshot.mjs. The try/finally blocks ensure the context and browser are closed even when navigation or capture fails. Replace the navigation wait condition when the target site needs a different readiness signal.
2. Choose between puppeteer and puppeteer-core
| Package | Browser selection | Use it when |
|---|---|---|
puppeteer |
Downloads a compatible Chrome for Testing by default. | You want the simplest local setup and the documented compatibility path. |
puppeteer-core |
Does not provision a browser; provide executablePath or channel when launching. |
Your environment manages Chrome, or you need to choose its location or channel. |
Puppeteer documentation says the bundled Chrome for Testing works best and does not guarantee compatibility with arbitrary browser versions. A system Chrome installation can be useful, but its version becomes part of your deployment configuration. When choosing Google Chrome, the launch reference suggests Canary or Dev Channel. See launch documentation.
Launch puppeteer-core with an explicit executable
npm install puppeteer-core
// core.mjs
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({
executablePath: '/usr/bin/google-chrome',
headless: true,
timeout: 30_000,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
Use the actual browser path for your operating system and deployment image. Alternatively, specify channel to select a Chrome installation from a known channel location. Do not set both without a reason; choose the selection mechanism that matches your environment.
3. Configure launch options
puppeteer.launch(options) returns a Browser. Common options include:
| Option | What it controls | Practical note |
|---|---|---|
browser |
Browser type; defaults to Chrome in the current reference. | Keep this aligned with the browser you intend to run. |
headless |
Whether Chrome runs without a visible window; current default is true. |
Use visible mode when diagnosing UI behavior locally. |
channel |
Selects a system Chrome channel from a known location. | Changes which browser build Puppeteer controls. |
executablePath |
Path to a specific browser executable. | Pin and maintain the browser version alongside your deployment. |
args |
Additional command-line arguments passed to Chrome. | Only add flags your environment requires; flags can change browser behavior. |
env |
Environment variables passed to the browser process. | Useful for controlled process configuration. |
userDataDir |
Directory for persistent browser profile data. | Use separate directories when persistent profiles must not overlap. |
timeout |
Launch timeout in milliseconds; current default is 30,000. | Increase only when browser startup in your environment needs longer. |
waitForInitialPage |
Whether launch waits for the first page. | Consult the installed version’s API reference before changing this behavior. |
Option names and defaults can change by Puppeteer release. The values above reflect the v25.12.0 documentation in the research dossier. Check the live LaunchOptions reference for your installed version.
4. Use browser contexts and pages
A browser can manage multiple pages. A BrowserContext represents an individual user context and isolates storage such as cookies and localStorage. Pages created in a context share that context’s storage. Closing a non-default context closes its pages; the default context cannot be closed.
const browser = await puppeteer.launch();
try {
const firstUser = await browser.createBrowserContext();
const secondUser = await browser.createBrowserContext();
const firstPage = await firstUser.newPage();
const secondPage = await secondUser.newPage();
await Promise.all([
firstPage.goto('https://example.com'),
secondPage.goto('https://example.org'),
]);
// Cookies and localStorage are isolated between these contexts.
console.log(await firstPage.title(), await secondPage.title());
await firstUser.close();
await secondUser.close();
} finally {
await browser.close();
}
Use separate contexts for independent sessions that can share one browser process. This separates browser storage while avoiding the need to start a browser for every page. Use separate processes when your application needs process-level separation or independent browser lifecycles. See the BrowserContext reference.
5. Navigate and interact with a page
A Page is Puppeteer’s API surface for a tab. You can set its viewport, navigate, query and interact with elements, evaluate page JavaScript, and create screenshots or PDFs. Locator-based interaction is shown in the official Getting started guide.
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const title = await page.title();
console.log(title);
// Locator actions wait for the element as needed.
const heading = page.locator('h1');
console.log(await heading.waitHandle().then(async (element) => {
try { return await element.evaluate((node) => node.textContent); }
finally { await element.dispose(); }
}));
For application flows, prefer explicit waits tied to the element or state you need over arbitrary long sleeps. Page behavior and available methods are documented in the Page API.
6. Capture screenshots and PDFs
Puppeteer can capture a viewport or full page, and can capture a specific element by obtaining its element handle and calling screenshot on that handle. PDF generation uses print CSS by default. Switch to screen media before creating the PDF if you need screen styling.
// Viewport screenshot
await page.screenshot({ path: 'viewport.png' });
// Full-page screenshot
await page.screenshot({ path: 'full-page.png', fullPage: true });
// Element screenshot
const element = await page.$('main');
if (!element) throw new Error('Could not find main element');
await element.screenshot({ path: 'main.png' });
// PDF using screen styles
await page.emulateMediaType('screen');
await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
Choose the capture type that matches your output: viewport screenshots preserve the current viewport, full-page screenshots include the page’s full vertical content, and element screenshots focus on one selected region. See the official Page API and screenshot guide.
7. Connect Puppeteer to a remote browser
When a browser process already exists, Puppeteer can connect over a WebSocket endpoint instead of launching Chrome locally. The endpoint must come from the browser environment you control.
// remote.mjs
import puppeteer from 'puppeteer-core';
const wsEndpoint = process.env.PUPPETEER_WS_ENDPOINT;
if (!wsEndpoint) throw new Error('Set PUPPETEER_WS_ENDPOINT');
const browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });
try {
const pages = await browser.pages();
const page = pages[0] ?? await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'remote.png' });
} finally {
// Disconnect Puppeteer from the remote browser. This does not close its process.
await browser.disconnect();
}
Keep the WebSocket endpoint private: possession of it grants control of that browser session. A remote browser is useful when another service manages the browser process. In browser-side Puppeteer, connecting is supported, but launching or downloading a browser is not, because those operations rely on Node.js APIs. The browser-side guide describes the supported workflow.
8. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Launch reports no executable or cannot find Chrome | puppeteer-core was installed without selecting a browser, or the path is wrong. |
Use puppeteer to download Chrome for Testing, or set a valid executablePath or channel. |
| Browser launches locally but fails in deployment | The deployed environment lacks the expected browser binary or its supporting environment. | Provision the browser in the deployment image and verify the path and version there. |
| Unexpected behavior with system Chrome | The Chrome version may not match Puppeteer’s supported build. | Prefer bundled Chrome for Testing or align the installed browser with the Puppeteer release. |
| Launch times out | Browser startup takes longer than the configured launch timeout. | Check whether the process can start, then adjust timeout to fit the environment. |
| Page appears blank or navigation never resolves | The chosen navigation wait condition may not match the site’s activity, or the site failed to load. | Check the URL and page errors; wait for a specific selector or use a less strict navigation condition where appropriate. |
| PDF differs from the visible page | page.pdf() uses print CSS by default. |
Call page.emulateMediaType('screen') before PDF generation when screen styles are needed. |
| Cookies appear shared or missing | Pages were created in the same context, or a context was closed and its pages ended. | Use a separate browser context for each isolated session and keep it open for the full workflow. |
| Remote connect fails | The endpoint is missing, unreachable, or not a valid browser WebSocket endpoint. | Verify the endpoint supplied by the browser host and network access from the Node.js process. |
9. Performance, reliability, and cost
Launching a browser process has setup and resource costs; when handling multiple independent sessions, contexts can isolate storage while sharing a browser. Close contexts and browser processes in cleanup paths to avoid accumulating pages and processes. For stable automation, keep Puppeteer and its expected browser version aligned, use explicit readiness conditions, and distinguish a navigation timeout from a page-level failure.
The research dossier contains no benchmark or cost figure for running Puppeteer, so actual resource use and infrastructure cost depend on your workload and environment. Remote browser infrastructure moves browser provisioning and lifecycle management to another service; the WebSocket connection capability itself does not prescribe a provider.
Or skip the browser setup
If your job is to capture a website rather than automate a full browser workflow, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF; see the 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)
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}`);
ScreenshotNeo accepts cookie and consent banners before capture, then removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free account and get 1,000 screenshots a month with no card.
Frequently asked questions
Can Puppeteer launch Chrome?
Yes. In Node.js, call puppeteer.launch(). With puppeteer-core, choose an executable path or channel.
Can Puppeteer control a browser it did not start?
Yes. Connect to an existing browser with puppeteer.connect({ browserWSEndpoint }) and disconnect when your session is finished.
Does closing a page close the browser?
No. Pages, contexts, and the browser have separate lifecycles. Close a context to close its pages, or close the browser process when its work is complete.
Can Puppeteer run in a browser environment?
Browser-side Puppeteer supports connecting to a remote browser. It cannot launch or download Chrome in that environment because those operations use Node.js APIs.


