ScreenshotNeo

BlogHow-to

How to Use a Chrome DevTools Protocol Session with Puppeteer

Create a Puppeteer CDP session, send protocol commands, listen for events, and detach safely—with page and target examples, troubleshooting, and a screenshot API alternative.

By the ScreenshotNeo team4 October 20269 min read

A Puppeteer Chrome DevTools Protocol (CDP) session gives your code a direct interface to the browser’s debugging protocol. For a page workflow, create one with await page.createCDPSession(), send protocol methods with session.send(), subscribe to protocol events with session.on(), and call session.detach() when you are done.

This guide uses JavaScript and Puppeteer. CDP methods and events depend on the protocol supported by the browser you connect to, so check the documentation for your installed Puppeteer release and target browser before relying on a particular method.

1. Create a page CDP session and use it

For a session attached to a page, use page.createCDPSession(). The example below launches Puppeteer’s browser, opens a page, creates a session, enables the Animation domain, listens for an animation event, and sends a second command to read the playback rate.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();
  await page.goto('https://example.com');

  const session = await page.createCDPSession();

  try {
    session.on('Animation.animationCreated', event => {
      console.log('Animation created:', event);
    });

    await session.send('Animation.enable');

    const result = await session.send('Animation.getPlaybackRate');
    console.log('Playback rate:', result.playbackRate);
  } finally {
    await session.detach();
  }
} finally {
  await browser.close();
}

This follows the pattern in Puppeteer’s CDPSession documentation. The protocol method names in the example are browser protocol methods, not arbitrary Puppeteer methods. Review the target browser’s supported protocol before using a command or event in production.

What happens in the example

  1. page.createCDPSession() creates a session attached to the page.
  2. session.on(eventName, handler) subscribes to a protocol event.
  3. session.send(methodName, params) sends a protocol command and resolves with its result.
  4. session.detach() ends the session. After detaching, it cannot send messages or emit events.
  5. The outer finally closes the browser even if navigation or a CDP operation fails.

Register an event handler before issuing the command that enables or triggers that event. Some events can arrive quickly after activation, and registering first avoids missing an early notification.

2. Send CDP commands and handle results

Use send() with the protocol method name and, when required, a parameter object. The command name and parameter schema come from the browser protocol domain you are using. A command with no parameters can omit the second argument.

const session = await page.createCDPSession();

try {
  await session.send('Animation.enable');

  const { playbackRate } = await session.send(
    'Animation.getPlaybackRate'
  );

  console.log(playbackRate);

  await session.send('Animation.setPlaybackRate', {
    playbackRate: 1.5,
  });
} finally {
  await session.detach();
}

The returned value is the protocol command’s result object. Destructure only fields that the relevant protocol method documents. A method can fail because its name or parameters are invalid, the browser does not support it, or the connection has ended.

Catch errors with command context

Include the method name in logs so protocol failures are easier to locate. Preserve the original error when reporting or rethrowing it.

async function sendCdp(session, method, params) {
  try {
    return await session.send(method, params);
  } catch (error) {
    console.error(`CDP command failed: ${method}`, error);
    throw error;
  }
}

const session = await page.createCDPSession();

try {
  await sendCdp(session, 'Animation.enable');
} finally {
  await session.detach();
}

Keep the command inside the session’s lifetime. Detaching while a command or event workflow is still needed makes the session unusable for that work.

3. Listen for CDP events

A CDP event is delivered to a listener registered on the session. Enable the corresponding protocol domain when that domain requires it, and register the listener before enabling the domain if you need to observe events from the beginning.

const session = await page.createCDPSession();

try {
  session.on('Animation.animationCreated', event => {
    console.log('Animation created:', event);
  });

  await session.send('Animation.enable');

  // Keep the session attached while event notifications are needed.
  await new Promise(resolve => setTimeout(resolve, 1000));
} finally {
  await session.detach();
}

Event names and payload fields are defined by the browser protocol. Do not assume every Chrome or Chromium version provides the same events or payload shape. Remove listeners if your code keeps a session alive for longer than a short operation and the handler is no longer needed.

4. Choose page-level or target-level attachment

Attachment point Use it when Creation method
Page Your work is attached to a Puppeteer page. This is the default for page-level CDP work. await page.createCDPSession()
Target You already have a Puppeteer Target and need a session attached to that debuggable target. await target.createCDPSession()

Puppeteer describes targets as debuggable entities such as pages, frames, or workers. Use the attachment point that corresponds to the entity your workflow is handling.

Create a session from a Target

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

try {
  const page = await browser.newPage();
  await page.goto('https://example.com');

  const target = page.target();
  const session = await target.createCDPSession();

  try {
    await session.send('Animation.enable');
    console.log('CDP session attached to the target');
  } finally {
    await session.detach();
  }
} finally {
  await browser.close();
}

This illustrates target-level creation when you are working with a Target. For a page workflow, prefer page.createCDPSession(): Puppeteer marks Page.target() obsolete and recommends the page method for creating a page session.

5. Connect to an existing browser

Connecting Puppeteer to a browser is separate from creating a CDP session. Puppeteer’s connection options document browserURL and browserWSEndpoint as ways to specify a browser connection. Once connected, create a page or target session using the same methods described above.

import puppeteer from 'puppeteer';

const browser = await puppeteer.connect({
  browserWSEndpoint: process.env.PUPPETEER_WS_ENDPOINT,
});

try {
  const pages = await browser.pages();
  const page = pages[0] ?? await browser.newPage();
  const session = await page.createCDPSession();

  try {
    await session.send('Animation.enable');
  } finally {
    await session.detach();
  }
} finally {
  await browser.disconnect();
}

Set PUPPETEER_WS_ENDPOINT to the WebSocket endpoint provided by your browser environment. If you use browserURL instead, follow the connection format documented for your Puppeteer version and browser setup. disconnect() ends Puppeteer’s connection; it does not close the browser process.

Protocol call timeouts

Puppeteer’s current ConnectOptions reference documents protocolTimeout as the timeout for individual CDP calls and shows a default of 180,000 milliseconds. This value is version-sensitive. Check the documentation for your installed release before treating that default as applicable to your project.

const browser = await puppeteer.connect({
  browserWSEndpoint: process.env.PUPPETEER_WS_ENDPOINT,
  protocolTimeout: 60_000,
});

Choose a timeout that fits the operation and environment. A longer timeout can allow a slow command more time to finish, but it also means your code may wait longer before reporting a stalled call.

6. Detach sessions and manage their lifetime

Call await session.detach() when the session is no longer needed. A detached session cannot send protocol commands and does not emit events. Use try/finally so cleanup still happens when a command throws.

const session = await page.createCDPSession();

try {
  await session.send('Animation.enable');
  // Perform work that needs this session here.
} finally {
  await session.detach();
}

Do not create a session for a short operation and detach it before asynchronous work that depends on its events has completed. Likewise, do not assume a session remains usable after its page, target, browser, or underlying connection has closed.

7. Troubleshoot common failures

Symptom Likely cause What to do
Unsupported operation or method The active browser protocol does not support the command or operation. Check the browser and protocol version and confirm the command exists for that protocol. Use a supported method or browser version.
Protocol error after detaching The session has been detached and can no longer send messages. Keep the session attached until all commands and event handling are finished. Create a new session for later work.
No event callbacks arrive The relevant domain may not be enabled, the listener may have been attached too late, the event may not occur, or the active protocol may not support it. Register the listener, enable the domain when required, then trigger or wait for the relevant browser activity. Confirm event support for the target browser.
ConnectionClosedError The underlying browser connection has closed. Check whether the browser exited, disconnected, or the connection endpoint became unavailable. Reconnect before creating another session.
ProtocolError The browser returned a protocol-level error, such as an unsupported method or invalid parameters. Log the method and error. Compare the command and parameter names with the protocol documentation for the connected browser.
Session seems to belong to the wrong target The session was created from a different page or target than the operation expects. Check which Puppeteer object you used to create it. Use page.createCDPSession() for page work or the intended target.createCDPSession() for target work.
Command waits longer than expected The call may be waiting on a slow operation, or the configured protocol timeout may be too long for the workflow. Review the call’s expected behavior and configure protocolTimeout where supported by your installed Puppeteer version.

Puppeteer documents UnsupportedOperation for operations unsupported by the current protocol, as well as ConnectionClosedError and ProtocolError for connection and protocol failures. These represent different failure layers: a closed transport needs a connection fix; an unsupported method or invalid parameter needs a protocol or command fix.

8. Performance, reliability, and cost considerations

The reviewed Puppeteer references do not establish a general speed or reliability advantage for page sessions over target sessions. Choose based on which debuggable entity your code needs, then measure your own workload if command overhead matters. Avoid unnecessary repeated session creation in a tight loop; keep a session for the bounded unit of work that needs it and detach it afterward.

CDP commands are tied to the browser’s protocol support. For reliable automation, align the installed Puppeteer and browser versions, check method availability, handle rejected commands, set an appropriate timeout for individual calls, and clean up sessions and browser connections in finally blocks.

CDP itself does not set a service price. Your costs depend on how you run the browser and its environment. For screenshots, a hosted screenshot API can avoid managing browser setup; see the option below.

9. Or skip the browser setup

If your goal is a website screenshot rather than a custom CDP workflow, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for parameters and response details.

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()))
);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. 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, with no card required.

10. FAQ

Can I create a CDPSession directly?

Obtain one from a Puppeteer page or target using page.createCDPSession() or target.createCDPSession(). Puppeteer documents the CDPSession constructor as internal.

Does a CDP session work with every browser?

Available methods and events depend on the active browser protocol. Check the protocol supported by the browser you connect to and the Puppeteer version in your project.

Should page code use page.target().createCDPSession()?

Use page.createCDPSession() for page session creation. Puppeteer marks Page.target() obsolete and recommends the page method.

Can one session be reused after detaching?

No. After detach(), that session cannot send messages or emit events. Create another session if you need CDP access again.

Official references