BLOG
Playwright ConnectOverCDP Chrome: Remote Browser Guide
Learn how playwright connectovercdp chrome works, when to use it, and how to connect Playwright to a hosted remote Chromium runtime for AI agents.
# Playwright ConnectOverCDP Chrome: Remote Browser Guide
playwright connectovercdp chrome is the API path that lets a Playwright script attach to an already-running Chrome or Chromium instance over the Chrome DevTools Protocol instead of launching a browser locally. If you are wiring an AI agent, a CI job, or a long-lived automation service to a hosted browser, this is usually the connection method you want. This guide covers how connectOverCDP behaves, where it breaks in production, and how to point it at a hosted Chromium runtime so you stop shipping Chrome binaries with your agent.
What connectOverCDP actually does
Playwright normally owns the browser lifecycle: chromium.launch() spawns a process, manages a temp profile, and tears it down when the context closes. connectOverCDP inverts that. You hand Playwright a CDP endpoint — typically a WebSocket URL like ws://host:port/devtools/browser/<id> — and it attaches to a browser that already exists. Playwright does not start it, does not own its profile, and does not kill it on disconnect.
That distinction matters more than it sounds. When you connect over CDP:
- The browser keeps running after your script exits, which is what you want for persistent sessions and agent memory.
- Cookies, localStorage, and logged-in state live in the remote profile, not in a throwaway temp directory.
- You can attach multiple clients to the same browser, though you need to coordinate which one owns which context.
- Playwright's
browser.close()disconnects your client; it does not necessarily terminate the remote browser.
The trade-off is that you inherit responsibility for the remote side. Someone has to run that Chromium process, expose the CDP port safely, and keep it alive. That is exactly the problem a hosted runtime solves.
Chromium-only, and why that matters
connectOverCDP is Chromium-specific. Firefox and WebKit do not expose a compatible CDP surface, so the method is effectively a Chrome/Chromium/Edge path. If your test matrix includes WebKit, you cannot route it through CDP — you need Playwright's own browser server protocol (connect() with a wsEndpoint from launchServer) or a hosted runtime that speaks both.
For AI agents and browser-use workloads, this is rarely a blocker. The overwhelming majority of agent tasks target real-world sites that expect a Chromium engine, and CDP gives you the low-level access — network interception, Runtime.evaluate, target management — that agent frameworks rely on. The Playwright documentation is explicit that this method connects to Chromium-based browsers, and the Chrome DevTools Protocol is the underlying contract.
The local version, and where it falls apart
The naive setup looks like this: launch Chrome with --remote-debugging-port=9222, then connect.
chrome --remote-debugging-port=9222 --user-data-dir=/tmp/profileimport { chromium } from 'playwright';
const browser = await chromium.connectOverCDP('http://localhost:9222');
const context = browser.contexts()[0] ?? await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
// Disconnects the client; the Chrome process keeps running.
await browser.close();This works on a laptop. In production it degrades fast:
- Process management. You now own Chrome's lifecycle, crash recovery, and zombie cleanup across every worker.
- Port exposure. A raw CDP port is unauthenticated by default. Exposing it beyond localhost is a security incident waiting to happen.
- Profile contention. Two agents sharing one
--user-data-dircorrupt each other's state. You need per-session isolation. - Version drift. Chrome auto-updates; your pinned Playwright version may not match the protocol surface.
- No observability. When an agent stalls, you have no live view of what the page is doing.
Each of these is a small problem individually. Together they are the reason teams move the browser off their own infrastructure.
Connecting Playwright to a hosted Chromium runtime
A hosted runtime gives you a CDP endpoint per session. You request a session, get back a WebSocket URL, and pass it to connectOverCDP. The runtime handles process lifecycle, isolation, and the network path.
import { chromium, Browser, BrowserContext, Page } from 'playwright';
interface SessionInfo {
cdpUrl: string; // ws://.../devtools/browser/<id>
sessionId: string;
}
async function openRemoteSession(): Promise<SessionInfo> {
const res = await fetch('https://api.remote-browser.dev/v1/sessions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.REMOTE_BROWSER_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Configurable browser settings: region, proxy, viewport, timeouts.
region: 'us-east',
viewport: { width: 1280, height: 800 },
}),
});
if (!res.ok) throw new Error(`session create failed: ${res.status}`);
return res.json() as Promise<SessionInfo>;
}
async function runTask(): Promise<void> {
const session = await openRemoteSession();
const browser: Browser = await chromium.connectOverCDP(session.cdpUrl, {
timeout: 30_000,
});
// Reuse the runtime's default context so the profile persists.
const context: BrowserContext = browser.contexts()[0];
const page: Page = await context.newPage();
try {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Sign in' }).click();
// ...agent or test logic...
} finally {
// Disconnect only. The runtime decides when to reap the session.
await browser.close();
}
}
runTask().catch((err) => {
console.error(err);
process.exit(1);
});Two details are worth calling out. First, browser.contexts()[0] — a hosted runtime usually pre-creates a context bound to a persistent profile, so you should reuse it rather than calling newContext(), which would give you a blank slate and lose login state. Second, browser.close() here means *disconnect*. Session teardown is a separate API call or a timeout policy on the runtime side.
If you want the conceptual background on why this architecture exists, see Remote Browser for AI Agents.
Local Chrome vs hosted Chromium over CDP
| Criterion | Local Chrome + CDP | Hosted Chromium runtime |
|---|---|---|
| Browser lifecycle | You manage process, crashes, cleanup | Runtime manages per-session |
| Profile persistence | Manual --user-data-dir, contention risk | Persistent profiles per session |
| Session isolation | Shared profile unless you build it | Isolated by default |
| CDP endpoint security | Raw port, unauthenticated by default | Authenticated endpoint per session |
| Scaling | One process per worker, your CPU/RAM | Horizontal, off your infra |
| Live debugging | Attach DevTools manually | Built-in live viewer |
| Proxy / network config | Flags at launch | Configurable per session |
| Version pinning | Chrome auto-update drift | Runtime-controlled Chromium build |
| Cost model | Your compute, hidden ops time | Metered per session — see /pricing |
The table is not a sales pitch; it is a list of things you will eventually build yourself if you self-host. The question is whether browser infrastructure is your product or a dependency.
Production criteria before you commit
Before you route agent traffic through any CDP endpoint, check these:
- Authentication on the endpoint. A CDP WebSocket URL is a full-control credential. It must be scoped, short-lived, and never logged.
- Session isolation guarantees. Confirm that two concurrent sessions cannot read each other's cookies or storage.
- Profile semantics. Know whether a profile persists across sessions, how long, and how to reset it.
- Reconnection behavior. If the WebSocket drops mid-task, does the session survive? Can you reattach to the same page?
- Observability. A live viewer and session logs turn a 40-minute debugging session into a 2-minute one.
- Network controls. Proxy configuration and configurable browser settings matter for sites that behave differently by region.
- Usage controls. Per-session timeouts and spend caps prevent a runaway agent loop from becoming an invoice.
For the operational side of this — keeping sessions alive across cloud workers, reconnecting, and handling disconnects — see Remote Control Browser.
Where connectOverCDP fits in an agent stack
Agent frameworks differ in how they reach the browser. Some drive Playwright directly; some speak CDP; some ship their own abstraction. connectOverCDP is the common denominator because it is the lowest-common interface that still gives you Playwright's ergonomics — locators, auto-waiting, tracing — on top of a browser you do not own.
A few practical patterns:
- One session per task. Simplest isolation model. Create, run, disconnect, let the runtime reap.
- One session per user or tenant. Persistent profile, reused across tasks. Good for agents that need to stay logged in.
- One session per long-running agent. The agent holds the CDP connection and opens/closes pages as needed. Watch for memory growth in the page set.
- Hybrid. Short-lived sessions for scraping, persistent sessions for authenticated workflows.
Puppeteer users get the equivalent via puppeteer.connect({ browserWSEndpoint }), and Selenium via a remote WebDriver endpoint. The CDP endpoint is the shared substrate; the client library is a preference. If you are comparing connection styles, Remote Web Browser walks through the alternatives.
Common failure modes
`connectOverCDP` times out. Usually a network path issue — the endpoint is reachable from your laptop but not from the container. Check egress rules and DNS before blaming Playwright.
`browser.contexts()` is empty. Some runtimes do not pre-create a context. Fall back to newContext(), but understand you may be losing the persistent profile.
Pages close unexpectedly. If the runtime has an idle timeout, an agent that pauses to call an LLM may return to a dead session. Either keep the session warm or design for reconnection.
Targets multiply. Every newPage() creates a CDP target. Agents that open pages in a loop without closing them will exhaust memory. Track and close.
Version mismatch errors. Playwright validates the protocol it speaks. If the remote Chromium is far ahead or behind your Playwright version, expect subtle breakage. Pin both.
When to use connectOverCDP — and when not to
Use it when you need to attach to an existing browser, preserve state across script runs, share a browser across processes, or drive a browser you do not host. That covers most agent and automation workloads.
Do not use it when you need Firefox or WebKit coverage, when you want Playwright to fully own the browser lifecycle in a single process, or when your workload is a one-shot script with no state to preserve — chromium.launch() is simpler and has fewer moving parts.
The decision is really about ownership. connectOverCDP is the right call the moment browser infrastructure stops being something you want to run yourself. Start with the documentation to see the session API, and check /pricing for current usage details before you size a workload.