← Blog

BLOG

Browser Use Remote Browser Examples for Production

Concrete browser use remote browser examples: CDP wiring, Playwright code, session profiles, and production criteria for AI agents.

September 20, 20269 min readRemote Browser

# Browser Use Remote Browser Examples for Production

Most browser use remote browser examples you find online stop at connect_over_cdp() and a screenshot. That works for a demo. It does not survive a queue of concurrent agents, a login flow that needs a persistent profile, or a page that renders differently on the third retry.

This post walks through concrete examples of running browser-use-style agents against a hosted remote browser, then covers the production criteria that separate a working script from a working system. If you want the architectural background first, read Remote browsers for AI agents; this piece is the implementation layer.

What "browser use remote browser" actually means

Browser Use is an agent framework: an LLM decides what to click, type, and read, and a browser executes those decisions. A remote browser is the execution target — a Chromium instance running on infrastructure you don't manage, reachable over a WebSocket CDP endpoint.

The combination matters because agent loops are stateful and long-running. An agent may take 40 steps across 6 minutes, hold a session cookie from step 3, and need a live view so a human can intervene at step 38. Running that against a local chromium.launch() means the session dies with your process.

Three connection patterns cover nearly every browser-use deployment:

  • CDP over WebSocket — the agent framework connects to a remote Chromium endpoint and drives it with the full DevTools Protocol.
  • Playwright/Puppeteer `connectOverCDP` — you keep your existing automation code and swap the launch call for a connect call.
  • REST session API — you request a session, get a connection URL back, and hand that URL to whatever runtime you use.

All three land on the same primitive: a hosted Chromium session with an addressable endpoint.

Example 1: Connect Playwright to a hosted session over CDP

This is the most common pattern. You request a session, receive a CDP WebSocket URL, and connect. The TypeScript below assumes you have a session endpoint from your runtime provider.

import { chromium, Browser, Page } from 'playwright';

type SessionInfo = {
  id: string;
  cdpUrl: string; // wss://... from your session API
};

async function openAgentSession(cdpUrl: string): Promise<{ browser: Browser; page: Page }> {
  const browser = await chromium.connectOverCDP(cdpUrl, {
    timeout: 30_000,
  });

  // A hosted session usually starts with one default context.
  const context = browser.contexts()[0] ?? (await browser.newContext());
  const page = context.pages()[0] ?? (await context.newPage());

  page.setDefaultTimeout(20_000);
  page.setDefaultNavigationTimeout(45_000);

  return { browser, page };
}

async function runTask(cdpUrl: string, taskUrl: string) {
  const { browser, page } = await openAgentSession(cdpUrl);

  try {
    await page.goto(taskUrl, { waitUntil: 'domcontentloaded' });

    // Agent step: locate an element, act, verify.
    const search = page.getByRole('searchbox', { name: /search/i });
    await search.fill('quarterly report');
    await search.press('Enter');

    await page.waitForLoadState('networkidle');
    const results = await page.locator('[data-testid="result-row"]').count();

    return { ok: results > 0, count: results };
  } finally {
    // Disconnect without killing the remote browser if you need to resume later.
    await browser.close();
  }
}

Two details matter here. First, connectOverCDP does not launch anything — it attaches. If the remote session is already gone, you get a connection error, not a fresh browser. Second, browser.close() on a CDP connection disconnects your client; whether the remote session terminates depends on your provider's session lifecycle. Check that behavior explicitly rather than assuming it.

The Playwright documentation for `browserType.connectOverCDP` is the authoritative reference for connection semantics and supported options.

Example 2: Raw CDP for agent frameworks that don't use Playwright

Some browser-use implementations talk to the DevTools Protocol directly — sending Page.navigate, Runtime.evaluate, and DOM.querySelector messages over the WebSocket. This gives you finer control and lower overhead, at the cost of writing your own element resolution.

import WebSocket from 'ws';

type CdpMessage = { id: number; method: string; params?: Record<string, unknown> };

class CdpClient {
  private ws: WebSocket;
  private nextId = 1;
  private pending = new Map<number, (v: unknown) => void>();

  constructor(url: string) {
    this.ws = new WebSocket(url);
    this.ws.on('message', (raw) => {
      const msg = JSON.parse(raw.toString());
      if (msg.id && this.pending.has(msg.id)) {
        this.pending.get(msg.id)!(msg.result);
        this.pending.delete(msg.id);
      }
    });
  }

  send(method: string, params: Record<string, unknown> = {}): Promise<unknown> {
    const id = this.nextId++;
    const payload: CdpMessage = { id, method, params };
    return new Promise((resolve) => {
      this.pending.set(id, resolve);
      this.ws.send(JSON.stringify(payload));
    });
  }

  async evaluate(expression: string) {
    const res = (await this.send('Runtime.evaluate', {
      expression,
      returnByValue: true,
      awaitPromise: true,
    })) as { result: { value: unknown } };
    return res.result.value;
  }
}

// Usage inside an agent step
const cdp = new CdpClient(cdpUrl);
await cdp.send('Page.enable');
await cdp.send('Page.navigate', { url: 'https://example.com' });
const title = await cdp.evaluate('document.title');

This is closer to what a browser-use agent does internally: it needs a stable way to read the DOM, act on it, and confirm the result. The Chrome DevTools Protocol documentation lists every domain and method if you need to go deeper than Runtime.evaluate.

Example 3: Persistent profiles for logged-in agent tasks

The hardest browser-use tasks are the ones behind a login. An agent that re-authenticates on every run burns steps, trips rate limits, and often fails MFA.

A hosted remote browser with persistent profiles lets you authenticate once, store the profile, and reuse it. The pattern:

  1. Create a session with a named profile attached.
  2. Complete the login flow manually or with a one-time script.
  3. Close the session — the profile persists server-side.
  4. Future sessions attach the same profile and start already authenticated.
async function withProfile(profileId: string, task: (page: Page) => Promise<void>) {
  const session = await createSession({ profileId }); // your provider's API
  const browser = await chromium.connectOverCDP(session.cdpUrl);
  const context = browser.contexts()[0];
  const page = context.pages()[0] ?? (await context.newPage());

  try {
    await task(page);
  } finally {
    await browser.close();
    await endSession(session.id);
  }
}

Profile reuse is where local setups break down. A local Chrome profile is tied to a machine and a filesystem path. A hosted profile is tied to an account and survives redeploys, container restarts, and CI runs.

Comparison: local browser vs hosted remote browser for browser-use

CriterionLocal ChromiumHosted remote browser
Session lifetimeDies with the processSurvives client disconnect (provider-dependent)
ConcurrencyBounded by host RAM/CPUScales independently of your app servers
Profile persistenceFilesystem path, machine-boundNamed profile, account-bound
Live debuggingScreenshots or local VNCBuilt-in live viewer
Network identityYour IP or local proxyConfigurable proxy and browser settings
CDP accessYes, local portYes, WebSocket endpoint
Cost modelFixed infraMetered per session/browser-hour

The trade-off is control versus operational burden. Local Chromium gives you total control and zero marginal cost per session, but you own the scaling, the profile storage, and the failure modes. A hosted runtime trades some control for isolation, persistence, and a live viewer you didn't have to build.

Production criteria that examples don't show

A working example is not a working system. These are the criteria that decide whether a browser-use deployment holds up.

Session isolation

Every agent task should run in its own session unless you deliberately share state. Cross-contamination between tasks — cookies, localStorage, cached responses — produces bugs that look like model failures but are actually environment failures. Verify that your runtime isolates sessions by default.

Deterministic teardown

Sessions that outlive their task are the most common source of runaway cost. Build teardown into the same finally block that closes your CDP connection, and add a server-side timeout as a backstop. If your agent can crash mid-task, the session must still terminate.

Observability during the run

When an agent fails at step 30, you need to see what the page looked like. A live viewer that streams the session is worth more than any post-hoc log. Screenshots at failure time are a fallback, not a substitute.

Proxy and browser settings

Many production tasks need a specific egress region or a consistent browser configuration. Hosted runtimes typically expose configurable proxy and browser settings per session. Treat these as configuration, not magic — verify the actual behavior rather than assuming a setting does more than it does.

Usage controls

Metered runtimes need guardrails: per-session time limits, concurrency caps, and a way to see spend before it becomes a surprise. Remote Browser exposes these controls through the session API; current limits and pricing are on the pricing page.

Where browser-use fits versus other agent runtimes

Browser-use is one of several ways to drive a remote browser. The others worth knowing:

  • Playwright/Puppeteer scripts — deterministic automation, no LLM in the loop. Best when the task is known and stable.
  • CLI-based agent tools — a command-line wrapper that exposes browser actions to an agent. Useful for local iteration, less so for concurrent production runs.
  • Framework-native agents — LangChain, CrewAI, or similar, with a browser tool attached. The browser is still a remote session underneath.

The common thread: all of them need a browser that outlives the calling process. That's the runtime layer, and it's the same regardless of which framework sits on top. For a broader comparison of connection approaches, see Remote web browser: the practical runtime.

Common failure modes and how to diagnose them

Connection refused or immediate close. The session expired before your client connected, or the CDP URL is stale. Log the session creation timestamp and the connect timestamp; the gap tells you which.

Element not found on retry. Usually a rendering race, not a selector problem. Wait for a specific network response or a stable DOM signal rather than a fixed timeout.

Agent loops on the same step. Often a stale page reference after navigation. Re-query the page or context after any full navigation instead of holding a Page object across it.

Session leaks. Teardown didn't run because the process was killed. Server-side session timeouts are the only reliable fix.

Inconsistent results across runs. Check whether sessions are actually isolated and whether the profile is being reused when you didn't intend it.

Getting started

The shortest path from a local browser-use script to a hosted one is to replace the launch call with a connect call and point it at a session endpoint. Everything else — your agent logic, your prompts, your evaluation — stays the same.

Start with the documentation for session creation and CDP connection details, then check pricing for current metering. If you're still deciding between a local and hosted setup, Remote browser online covers the trade-offs in more depth.

The examples above are deliberately minimal. The production work is in isolation, teardown, and observability — the parts that don't show up in a connect_over_cdp snippet but decide whether your agents finish tasks or just start them.