BLOG
Cursor Browser Use: Drive Real Chromium From Your IDE
Cursor browser use explained: how to give Cursor's agent a real Chromium session over CDP, what breaks locally, and how to run it on a hosted runtime.

Cursor browser use means letting Cursor's agent control a real Chromium instance — navigating pages, clicking, filling forms, and reading the DOM — instead of guessing at markup from static files. The catch is that Cursor is an editor, not a browser runtime. It has no built-in browser process, no session lifecycle, and no way to keep a logged-in profile alive between prompts. To make browser use work inside Cursor, you connect the agent to an external Chromium over the Chrome DevTools Protocol (CDP), and the quality of that connection determines whether the workflow holds up. This guide covers the wiring, the failure modes, and the production criteria for running it on a hosted runtime.
What "Cursor browser use" actually requires
Cursor's agent can write Playwright or Puppeteer code, run it in a terminal, and read the output. That is code generation, not browser control. For genuine browser use — where the agent observes a live page and acts on what it sees — you need three things:
- A running Chromium process the agent can reach.
- A CDP endpoint (a WebSocket URL) that exposes that process.
- A control loop where the agent reads page state, decides an action, executes it, and re-reads state.
Cursor supplies only the third piece, and only partially. The first two are your responsibility. Locally, that means launching Chrome with --remote-debugging-port and hoping the agent's script connects before the window closes. In practice, that is where most Cursor browser setups stall.
The alternative is to point Cursor at a hosted Chromium session that already exposes a CDP endpoint. The agent's code stays the same; the endpoint just resolves to a remote browser instead of localhost. That is the pattern this guide builds toward.
Why local Chrome breaks inside Cursor
Running Chrome on your laptop and driving it from Cursor works for a demo and fails for anything recurring. The specific problems:
- Session lifetime. A local Chrome window dies when you close it, when the machine sleeps, or when the agent's script exits without cleanup. The next prompt starts from zero — no cookies, no login, no scroll position.
- Port collisions and stale processes.
--remote-debugging-port=9222is a single global slot. Two agent runs, or a crashed run that left Chrome alive, and the next connection attaches to the wrong browser or fails outright. - No isolation. Your personal Chrome profile — extensions, saved passwords, open tabs — is the same profile the agent drives. That is both a security problem and a reproducibility problem.
- Headless vs. headed drift. Agents that work against a headed window often behave differently headless, and vice versa. Debugging that difference inside an IDE terminal is slow.
- No observability. When a step fails, you get a stack trace, not a recording. You cannot see what the page looked like at the moment of failure.
None of these are Cursor bugs. They are the cost of treating a desktop browser as infrastructure. The fix is to move the browser out of the IDE and connect to it over CDP.
Connecting Cursor to a hosted Chromium session
The mechanics are the same whether the browser is local or remote: you get a CDP WebSocket endpoint and call chromium.connectOverCDP(). The difference is where the endpoint comes from. A hosted runtime issues one per session, keeps the session alive independently of your editor, and lets you attach a live viewer so you can watch the agent work.
Here is the minimal TypeScript pattern. It assumes the session endpoint is in an environment variable, which is how you should pass it — never hardcode a CDP URL into a file the agent can rewrite.
import { chromium, type Browser, type Page } from "playwright";
const CDP_ENDPOINT = process.env.BROWSER_CDP_ENDPOINT;
if (!CDP_ENDPOINT) {
throw new Error("BROWSER_CDP_ENDPOINT is not set");
}
async function runTask(): Promise<void> {
// connectOverCDP attaches to an existing browser; it does not launch one.
const browser: Browser = await chromium.connectOverCDP(CDP_ENDPOINT, {
timeout: 30_000,
});
// A remote session usually exposes one default context.
const context = browser.contexts()[0] ?? (await browser.newContext());
const page: Page = context.pages()[0] ?? (await context.newPage());
try {
await page.goto("https://example.com", { waitUntil: "domcontentloaded" });
await page.getByRole("link", { name: /more information/i }).click();
const heading = await page.locator("h1").innerText();
console.log("Heading:", heading);
} finally {
// Close the connection, not the remote browser.
await browser.close();
}
}
runTask().catch((err) => {
console.error(err);
process.exit(1);
});Two details matter more than they look. First, browser.close() on a CDP connection detaches your client; it does not necessarily terminate the remote browser. That is what you want — the session survives so the next Cursor prompt can reconnect to the same state. Second, connectOverCDP is Chromium-only. If your agent's code assumes Firefox or WebKit, it will not work over CDP. See the Playwright CDP documentation for the exact contract.
For the endpoint format, session creation, and profile handling, the Remote Browser documentation covers the API surface. The pattern is: create a session, read its CDP URL, pass that URL to Cursor's script, and keep the session ID so you can reconnect or tear it down.
The control loop: what Cursor's agent should actually do
Once the connection exists, the hard part is the loop. A naive agent dumps the full page HTML into the model context on every step. That burns tokens, truncates on large pages, and gives the model markup it cannot reliably act on. A better loop:
- Snapshot a compact view of the page. Accessibility tree or a trimmed DOM, not raw HTML.
- Ask the model for one action — a click target, a typed value, a navigation.
- Execute via Playwright locators, not raw coordinates.
- Re-snapshot and verify the action had the intended effect before continuing.
This is where Cursor's agent needs guardrails. Give it a small, fixed set of tools (navigate, click, type, read, screenshot) rather than open-ended code execution against the page. Open-ended execution is how agents end up in infinite retry loops or clicking the wrong element because a selector matched three nodes.
A practical rule: if a step can fail silently, add an assertion. After a click, assert the expected element appeared. After a form submit, assert the confirmation text. The agent should treat a failed assertion as a signal to re-snapshot, not to retry the same action.
Local vs. hosted runtime: a comparison
| Criterion | Local Chrome in Cursor | Hosted Chromium over CDP |
|---|---|---|
| Session survives editor restart | No | Yes, until you stop it |
| Persistent login/profile | Manual, tied to your desktop profile | Configurable persistent profiles per session |
| Isolation between runs | None by default | One session per task |
| Live debugging | DevTools window on your machine | Live viewer, attachable from any client |
| Parallel agent runs | Port collisions | Independent sessions |
| Proxy / network settings | OS-level, hard to scope | Configurable per session |
| Cost model | Your machine's resources | Metered per browser-hour (see /pricing) |
| Setup effort | Low for a demo, high to stabilize | One-time endpoint wiring |
The trade-off is real. Local Chrome is free and instant for a one-off script. Hosted Chromium costs money and adds a network hop. The crossover point is when you need the same session across multiple prompts, when you need more than one agent running at once, or when a failed run needs to be inspected after the fact. At that point, the local setup's hidden costs — debugging time, stale processes, unreproducible state — exceed the metered cost of a hosted session.
Production criteria for Cursor browser use
If you are moving from "it worked once" to "it runs on a schedule," evaluate against these:
Session lifecycle. Can you create, reconnect, and explicitly stop a session? An agent that leaves sessions running is an agent that bills you for idle browsers. Your wrapper code should always tear down in a finally block, and your runtime should expose a stop call that is idempotent.
Profile persistence. Does the session keep cookies and local storage across reconnects? For workflows behind a login, this is the difference between a two-step task and a two-hour one. Persistent profiles are a runtime feature, not something you bolt on in the agent.
Observability. Can you watch the session live, and can you retrieve a recording or screenshot after a failure? Cursor's terminal output is not enough. You want to see the page the agent saw.
Network controls. Can you set a proxy or region per session? Many real tasks depend on where the request originates. Configurable browser settings at the session level beat OS-level configuration you cannot scope per task.
Concurrency and isolation. Can two Cursor prompts run two agents without sharing state? Session isolation is the baseline; anything less and you are debugging cross-talk.
Usage controls. Do you have a way to cap spend and see per-session usage? Metered runtimes should expose this; check /pricing for how Remote Browser meters browser-hours.
None of these are Cursor features. They are runtime features, which is the point: Cursor is the interface, the runtime is the infrastructure. Treating them as separate layers is what makes the setup maintainable.
Where this fits relative to other approaches
Cursor browser use is one entry point into a broader category. If you are comparing options, the relevant distinctions are:
- Browser extensions that let an agent drive your existing tab. Convenient, but tied to your desktop session and hard to run headless or in CI.
- Local Playwright/Puppeteer scripts launched by the agent. Fine for deterministic tests, fragile for exploratory tasks.
- Hosted Chromium over CDP, which is what this guide describes. The agent code is standard Playwright; only the endpoint changes.
The last option is the one that scales from a single Cursor prompt to a scheduled job without rewriting the agent. For a broader look at the runtime layer, see Remote Browser for AI agents and the comparison of remote web browser options. If you want to try a session without installing anything, remote browser online walks through the no-local-setup path.
A concrete Cursor workflow
Putting it together, a workable setup looks like this:
- Provision a session from a small script or a CLI call. Capture the CDP URL and session ID.
- Export the endpoint into the environment Cursor's terminal uses.
- Give the agent a tool file with four or five functions —
navigate,click,type,read,screenshot— each wrapping a Playwright call against the connected browser. - Instruct the agent to call
readafter every action and to stop after N failed assertions rather than retrying indefinitely. - Watch the live viewer while the agent runs. When something goes wrong, you see it in real time instead of reconstructing it from logs.
- Stop the session when the task completes, and keep the recording for the next debugging session.
The instruction in step 4 matters more than it seems. Agents left to their own devices will retry a failing click until the context window fills. A hard stop with a clear failure message is cheaper and easier to debug than a long transcript of near-misses.
Bottom line
Cursor browser use is not a feature you toggle. It is a connection you wire: Cursor's agent on one side, a real Chromium session on the other, CDP in between. Local Chrome gets you a demo; a hosted runtime gets you sessions that survive editor restarts, profiles that persist, and failures you can actually see. The agent code is ordinary Playwright either way — the runtime is what decides whether the workflow holds up past the first prompt.
Start with the documentation to create a session and get its CDP endpoint, then point your Cursor script at it. If you are still deciding between local and hosted, the remote control browser guide covers the trade-offs in more depth.