ScreenshotNeo

BlogHow-to

How to Access the Session for a Puppeteer Connection

Connect to a running Puppeteer browser, find its pages and contexts, and keep it alive when you disconnect. Learn how this differs from a page-level CDP session.

By the ScreenshotNeo team4 October 20267 min read

To access a running browser session with Puppeteer, connect with its browser WebSocket endpoint, then enumerate pages and browser contexts:

import puppeteer from 'puppeteer';

const browser = await puppeteer.connect({
  browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/<id>',
});

const pages = await browser.pages();
const contexts = browser.browserContexts();

console.log(`Open pages: ${pages.length}`);
console.log(`Browser contexts: ${contexts.length}`);

// Detach Puppeteer while leaving the browser running.
await browser.disconnect();

Replace the sample endpoint with the endpoint supplied by your running browser. Use browser.disconnect() to detach without shutting it down; browser.close() closes the browser and its pages. This guide covers both common meanings of “session”: the connected browser and a page-level Chrome DevTools Protocol (CDP) session.

1. Connect Puppeteer to the running browser

puppeteer.connect() attaches to an existing browser. It does not launch a new one. The browser must expose a WebSocket endpoint that is reachable from the process running your Node.js code. See Puppeteer’s browser management guide.

import puppeteer from 'puppeteer';

const browserWSEndpoint = process.env.PUPPETEER_WS_ENDPOINT;
if (!browserWSEndpoint) {
  throw new Error('Set PUPPETEER_WS_ENDPOINT to the running browser WebSocket URL');
}

const browser = await puppeteer.connect({ browserWSEndpoint });
try {
  console.log('Connected:', browser.isConnected());
  console.log('Pages:', (await browser.pages()).length);
} finally {
  await browser.disconnect();
}

Install Puppeteer in your Node.js project with npm install puppeteer. This snippet uses ES modules; save it in a project configured with "type": "module" or use a .mjs file. The endpoint is browser-specific: do not copy the placeholder and expect it to work.

Find the WebSocket endpoint

When the browser’s debugging interface is available, its version endpoint at http://HOST:PORT/json/version returns a webSocketDebuggerUrl. Puppeteer’s Browser.wsEndpoint() also returns a URL intended for puppeteer.connect(). The documented endpoint has the shape ws://HOST:PORT/devtools/browser/<id>; use the actual URL provided by your browser setup. See Puppeteer’s Browser.wsEndpoint() API.

Keep the endpoint private. Anyone who can reach an exposed browser debugging endpoint may be able to control that browser. Make it reachable only by trusted processes and networks.

2. Get open pages and inspect browser contexts

After connecting, call browser.pages() to list open pages across the browser’s contexts. Call browser.browserContexts() to inspect the contexts themselves. To list pages in one specific context, use context.pages(); the default context is available through browser.defaultBrowserContext().

const allPages = await browser.pages();

for (const [index, page] of allPages.entries()) {
  console.log(index, await page.url());
}

for (const [index, context] of browser.browserContexts().entries()) {
  const contextPages = await context.pages();
  console.log(`Context ${index}: ${contextPages.length} page(s)`);
}

const defaultContext = browser.defaultBrowserContext();
const defaultPages = await defaultContext.pages();
console.log('Pages in default context:', defaultPages.length);

To work with a particular existing page, select it from the appropriate list. Check that the list is non-empty before indexing it:

const pages = await browser.pages();
const page = pages.find(async candidate => await candidate.url().includes('example.com'));

if (!page) {
  throw new Error('No matching page found');
}

Because Array.find() does not await asynchronous predicates, use a loop for URL matching instead:

let targetPage;
for (const candidate of await browser.pages()) {
  if ((await candidate.url()).includes('example.com')) {
    targetPage = candidate;
    break;
  }
}
if (!targetPage) throw new Error('No matching page found');

A page opened with window.open belongs to its parent page’s browser context. Puppeteer contexts isolate cookies and local storage, so a page in a different context will not have the same login state. Confirm that you have selected the context containing the page you need. See the BrowserContext documentation and Browser API.

3. Create a page-level CDP session when you need protocol commands

If by “session” you mean a Chrome DevTools Protocol connection attached to a particular page, create it from that page with page.createCDPSession(). This is a separate connection layer from attaching Puppeteer to a browser. It does not retrieve an existing page, restore login state, or replace puppeteer.connect().

const pages = await browser.pages();
if (pages.length === 0) throw new Error('The browser has no open pages');

const page = pages[0];
const cdpSession = await page.createCDPSession();
try {
  // Send a CDP command when your use case requires it.
  const version = await cdpSession.send('Browser.getVersion');
  console.log(version.product);
} finally {
  await cdpSession.detach();
}

Choose the layer that matches the task: browser connection for existing browser pages and contexts; page CDP session for protocol-level work on a selected page. See the Puppeteer Page API.

4. Disconnect or close the browser

Call Effect Use it when
browser.disconnect() Detaches Puppeteer and leaves the browser process running. You are finished with this Puppeteer connection but want the browser and its pages to remain available.
browser.close() Closes the browser and its associated pages. You intend to end the browser session.

For a browser you do not own or must leave running, disconnect. Calling close() ends that browser session. Puppeteer documents this lifecycle distinction in its browser management guide and Browser API.

5. Complete connection example with safe cleanup

This example connects, prints each open page’s URL, and disconnects even if a page lookup fails:

import puppeteer from 'puppeteer';

const endpoint = process.env.PUPPETEER_WS_ENDPOINT;
if (!endpoint) throw new Error('Missing PUPPETEER_WS_ENDPOINT');

const browser = await puppeteer.connect({ browserWSEndpoint: endpoint });
try {
  const contexts = browser.browserContexts();
  console.log(`Connected to ${contexts.length} context(s)`);

  const pages = await browser.pages();
  if (pages.length === 0) {
    console.log('No open pages');
  } else {
    for (const [index, page] of pages.entries()) {
      console.log(`${index}: ${await page.url()}`);
    }
  }
} finally {
  await browser.disconnect();
}

If your application needs to keep using the connected browser after this block, move the disconnect to the point where the application is actually done. Cleanup should match ownership: disconnect from a shared browser; close a browser your process is responsible for ending.

6. Common errors and fixes

Symptom Likely cause What to check
Connection refused or WebSocket handshake failure The host or port is unreachable, the browser is not exposing debugging, or the endpoint is wrong. Verify the browser is running, check network reachability from the Node.js process, and use the exact webSocketDebuggerUrl supplied by that browser.
Connection closes soon after attaching The browser process exited, the endpoint belongs to another browser instance, or an intermediary dropped the WebSocket connection. Check the browser process and endpoint source; ensure the network path permits a persistent WebSocket connection.
browser.pages() returns no pages The browser is open but has no pages available to enumerate. Handle the empty array and create a page only if your workflow is meant to add one. Do not index pages[0] without checking.
The expected login is missing You selected a page in a different browser context. Cookies and local storage are isolated by context. Enumerate contexts and inspect pages within the context that owns the authenticated page.
The browser disappears after cleanup browser.close() was called instead of browser.disconnect(). Use disconnect() when you only mean to detach Puppeteer.
createCDPSession is unavailable or fails No page was selected, or the code is treating the browser connection itself as a page CDP session. Get a page from browser.pages() first, then call page.createCDPSession().
Endpoint works locally but not in a container 127.0.0.1 refers to the container itself, not necessarily the host running the browser. Use the browser’s reachable host address and configure network access between the processes.

7. Performance, reliability, and cost notes

  • Reuse a connection when appropriate. If one process performs several operations against the same running browser, a single connection avoids repeatedly establishing connections. Keep lifecycle ownership clear so one worker does not close a browser shared by others.
  • Handle disconnections. A WebSocket connection depends on the browser process and network path remaining available. Check browser.isConnected() before relying on an old connection, and reconnect with a current endpoint after an actual disconnect.
  • Bound page work. Enumerating pages is straightforward, but work across many pages can consume browser resources. Process only the pages needed and avoid opening duplicate tabs unnecessarily.
  • Cost depends on how you run the browser. Puppeteer itself is the browser automation library; infrastructure and browser hosting costs depend on your deployment. The research sources do not specify prices or performance benchmarks.

8. Or skip the browser setup

If your actual goal is a website screenshot, ScreenshotNeo provides a screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. 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

Python:

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)

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}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

Use a Node.js runtime with a file-writing API to save the response body; the example uses Bun’s Bun.write. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month.

9. FAQ

How do I get pages from an existing Puppeteer connection?

Connect using the browser WebSocket endpoint, then call await browser.pages(). Use context.pages() when you need pages from a specific context.

Does Puppeteer connect restore a logged-in session?

It attaches to the running browser and exposes its pages and contexts. Whether the expected login is present depends on selecting the context containing that page; contexts isolate cookies and local storage.

Does browser.disconnect() close Chrome?

No. It detaches Puppeteer and leaves the browser process running. browser.close() closes the browser and its pages.

Is a CDP session the same as a Puppeteer browser connection?

No. A CDP session created with page.createCDPSession() is attached to a page for protocol commands. A browser connection attaches Puppeteer to the running browser and lets you enumerate its pages and contexts.