Connect with CDP

View as Markdown

Starting a runtime mints a credentialed CDP endpoint scoped to that run. Connect Playwright, Puppeteer, or any CDP client and drive the browser directly. BCTRL records the run in the background.

Get a connect URL

1const runtime = await bctrl.runtimes.create({ type: "browser", name: "main" });
2const { connectUrl, runId } = await bctrl.runtimes.start(runtime.id);

The connectUrl stays live for the lifetime of the run. Each start mints a fresh one.

Playwright

1import { chromium } from "playwright";
2
3const browser = await chromium.connectOverCDP(connectUrl);
4const context = browser.contexts()[0] ?? (await browser.newContext());
5const page = context.pages()[0] ?? (await context.newPage());
6
7await page.goto("https://example.com");

The runtime launches with a default context and page already open. Reuse them instead of creating new ones.

Puppeteer

1import puppeteer from "puppeteer-core";
2
3const browser = await puppeteer.connect({ browserWSEndpoint: connectUrl });
4const [page] = await browser.pages();
5
6await page.goto("https://example.com");

The controller lock

A runtime allows one controller at a time. While you hold a CDP connection, a hosted invocation on the same runtime fails with runtime.controller_busy. The reverse holds too: an active invocation blocks a new CDP connection.

1try {
2 await bctrl.runtimes.invocations.create(runtime.id, {
3 action: "act",
4 instruction: "Click submit.",
5 });
6} catch (error) {
7 if (Bctrl.isControllerBusy(error)) {
8 // release the CDP connection first
9 }
10}

Next