What Is WebDriver BiDi and How Does It Work with Puppeteer?
WebDriver BiDi lets browser automation send commands and receive events over a bidirectional connection. Learn how Puppeteer enables it in Firefox and Chrome, and where support differs.
WebDriver BiDi is a browser automation protocol that lets controlling software send commands to a browser and receive browser events over a bidirectional connection. Puppeteer supports BiDi automation with Firefox and Chrome: Firefox uses it by default, while Chrome uses the Chrome DevTools Protocol (CDP) unless you select BiDi explicitly.
BiDi is useful when you want event-driven browser automation or a protocol designed to work across browsers. It does not mean every browser implements every protocol feature, or that every Puppeteer API works over BiDi. Check the exact APIs your project uses before switching.
How WebDriver BiDi works
Traditional WebDriver is organized around commands and responses. BiDi adds a WebSocket connection through which the browser can also send events to the controlling software. That means automation can react to browser activity as it happens instead of obtaining every observation through a separate polling command. It does not guarantee that every operation is faster.
The W3C protocol groups commands and events into modules. In the usual WebDriver session flow, the client requests a bidirectional connection by setting the webSocketUrl capability to true. The remote end establishes the connection and returns its URL in the session capabilities. The specification also describes BiDi-only sessions where the WebSocket endpoint is communicated out of band. See the W3C WebDriver BiDi specification; it is a Working Draft and may change.
Enable BiDi in Puppeteer
Install Puppeteer in a Node.js project, then launch the browser with the appropriate options. These examples use the current Puppeteer API shown in its WebDriver BiDi guide.
npm install puppeteer
Save the following as bidi-example.mjs and run it with node bidi-example.mjs. Puppeteer downloads its bundled browser during installation; consult Puppeteer’s installation documentation if your environment uses a separately installed browser.
import puppeteer from 'puppeteer';
async function captureWith(browserType, protocol) {
const options = { browser: browserType };
if (protocol) options.protocol = protocol;
const browser = await puppeteer.launch(options);
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(`${browserType}:`, await page.title());
await page.screenshot({ path: `${browserType}.png` });
} finally {
await browser.close();
}
}
// Firefox uses WebDriver BiDi by default in Puppeteer.
await captureWith('firefox');
// Chrome uses CDP by default; select BiDi explicitly.
await captureWith('chrome', 'webDriverBiDi');
If you only need one browser, remove the other call. The finally block closes the browser even if navigation or capture fails. For a long-running test suite, reuse a browser process where appropriate and create or close pages per test; make sure each test still cleans up its own page and state.
Browser defaults and protocol selection
| Browser | Puppeteer default | To use BiDi |
|---|---|---|
| Firefox | WebDriver BiDi | No protocol option is needed |
| Chrome | CDP | Set protocol: 'webDriverBiDi' |
Puppeteer retains CDP as Chrome’s default because not all CDP features are supported over BiDi. If a Puppeteer operation is unavailable over the selected protocol, Puppeteer may throw UnsupportedOperation.
Choose BiDi or CDP based on your APIs
BiDi is a cross-browser protocol, but protocol choice should follow the browser and the methods your automation actually calls. Puppeteer documents supported and unsupported operations separately. Its BiDi support includes common workflows such as navigation, page evaluation, selectors and locators (with an ARIA exception), input, dialog handling, screenshots and PDF generation with parameter limits, permissions, and request interception. The guide also identifies gaps involving emulation, CDP-specific APIs, accessibility, coverage, tracing, request and response helpers, and other methods.
Before changing a project, make an inventory of its Puppeteer calls and compare each one with the current support lists in the Puppeteer BiDi documentation. Pay particular attention to optional parameters and features that are specific to CDP. Support can change as Puppeteer and browser implementations evolve.
Migration checklist
- Record the browser and Puppeteer version used by the project.
- List the Puppeteer methods and options used by tests, helpers, and fixtures.
- Check those methods against the current BiDi supported and unsupported lists.
- Try BiDi in a focused test before changing the protocol for an entire suite.
- Keep a clear path to the existing protocol if a required operation is unsupported.
Troubleshooting Puppeteer BiDi
| Symptom | Likely cause | What to do |
|---|---|---|
UnsupportedOperation |
The selected protocol does not support that Puppeteer operation or option. | Check the current BiDi support list. Use a supported API or run that workflow with the protocol that supports it. |
| Chrome is still using CDP | Chrome defaults to CDP in Puppeteer. | Set protocol: 'webDriverBiDi' in the Chrome launch options. |
| Firefox launch fails in the environment | The installed Puppeteer/browser setup may not match the example or runtime requirements. | Review Puppeteer’s current installation instructions and ensure the Firefox browser expected by your setup is available. |
| Navigation completes before the page is ready for the test | The chosen navigation lifecycle event may occur before the page’s application-specific work is complete. | Wait for a meaningful selector or application condition after navigation instead of assuming one lifecycle event means the page is fully ready. |
| A screenshot or PDF option fails | BiDi may support the operation with parameter limits that differ from CDP. | Compare the specific options with Puppeteer’s BiDi documentation and simplify or change unsupported parameters. |
Performance, reliability, and cost
The protocol description establishes event streaming, not a measured speed advantage. Do not assume BiDi is faster for every task; real performance depends on the browser, workload, network, and automation code. Avoid unnecessary polling when an event or explicit wait can express the condition you need, and use bounded timeouts so failed navigation does not stall a suite indefinitely.
For reliability, test the same workflows against the browser versions and Puppeteer version you deploy. The BiDi standard is a Working Draft, and Puppeteer’s Next documentation can change. Browser implementations also differ in feature coverage, so cross-browser protocol support alone is not proof that a test behaves identically everywhere.
Running Puppeteer locally has no per-screenshot API charge, but it consumes your compute, browser setup, and maintenance time. If you only need a website image or PDF and do not need custom browser automation, a hosted screenshot endpoint can avoid managing browser launches and protocol compatibility. ScreenshotNeo is a website screenshot API and MCP server for developers; its options and plans are at ScreenshotNeo.
Or skip the browser setup
For a one-call website capture, ScreenshotNeo accepts a URL and returns an image or PDF. The code below requests a WebP image; see the ScreenshotNeo API documentation for output and capture 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,
)
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}`);
await Bun.write('shot.webp', res);
ScreenshotNeo accepts cookie and 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, and failed loads are never billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.
Frequently asked questions
Does WebDriver BiDi replace CDP?
No. BiDi is a W3C protocol, while CDP remains available and is Puppeteer’s Chrome default. Which one fits depends on the browser and the features your automation needs.
Does Puppeteer support BiDi with both Chrome and Firefox?
Yes. Puppeteer’s guide documents BiDi support for both. Firefox uses it by default; Chrome requires explicit protocol selection.
Is WebDriver BiDi a finalized standard?
No. The W3C document is currently a Working Draft. Check the specification and Puppeteer documentation for updates when planning a new implementation.


