← Blog

BLOG

Web Add: Adding Browser Automation to AI Agents

Learn how to web add browser automation to AI agents: CDP wiring, Playwright setup, runtime criteria, and where hosted Chromium fits in production.

9 min readRemote Browser
Ivory computer towers connected across terracotta platforms in a quiet desert landscape
The infrastructure behind your browserREMOTE BROWSER / ILLUSTRATION

"Web add" is the step where an AI agent stops reasoning about the web in the abstract and starts driving a real browser. Concretely, it means adding a browser runtime to your agent stack: a Chromium instance your code can control over the Chrome DevTools Protocol (CDP), with Playwright, Puppeteer, or Selenium attached to it. This guide covers how to web add that capability correctly, what breaks when you do it locally, and which production criteria matter when you move to hosted Chromium.

If you already know you want a remote runtime, start with Remote Browser for AI agents. If you are still deciding, read on — the wiring is the same either way, and the differences show up in operations, not in the first script.

What "web add" actually means in an agent stack

An AI web agent has three layers:

  1. Reasoning layer — the model that decides what to do next.
  2. Tool layer — the functions the model can call (click, type, navigate, extract).
  3. Browser layer — the actual Chromium process that renders pages and executes JavaScript.

Most teams get the first two working in a day. The third is where "web add" turns into infrastructure work. A browser is a stateful, memory-hungry process with a lifecycle, a network identity, and a security surface. Adding one to an agent is not like adding an HTTP client.

The practical definition: you have completed a web add when your agent can open a page, interact with it, read the result, and do that repeatedly without leaking processes, sessions, or credentials.

The minimal web add: CDP plus Playwright

The fastest correct path is to launch Chromium with a remote debugging port and connect Playwright over CDP. This is the same mechanism you will use later against a hosted runtime, so the code does not change when you move.

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

// Local: launch Chromium with a debugging port.
// Hosted: replace with the WebSocket endpoint from your runtime's session API.
const CDP_ENDPOINT = process.env.CDP_ENDPOINT ?? "http://127.0.0.1:9222";

async function webAdd(): Promise<void> {
  const browser: Browser = await chromium.connectOverCDP(CDP_ENDPOINT);

  // connectOverCDP attaches to an existing browser; contexts already exist.
  const context = browser.contexts()[0] ?? (await browser.newContext());
  const page: Page = await context.newPage();

  await page.goto("https://example.com", { waitUntil: "domcontentloaded" });

  // Expose a small, typed tool surface to the agent.
  const title: string = await page.title();
  const links: string[] = await page.$$eval("a", (els) =>
    els.map((el) => (el as HTMLAnchorElement).href).slice(0, 10)
  );

  console.log({ title, links });

  // Do not call browser.close() on a shared remote browser.
  // Close the page and let the session lifecycle handle the rest.
  await page.close();
}

webAdd().catch((err) => {
  console.error("web add failed:", err);
  process.exit(1);
});

Two details matter here. First, connectOverCDP does not launch a browser — it attaches to one, so browser.close() semantics differ from launch(). Second, the endpoint is the only thing that changes between local and hosted. That is the design goal: keep the agent code stable and swap the runtime underneath. The Playwright CDP documentation covers the connection contract, and the Chrome DevTools Protocol spec covers the underlying domains your agent will eventually touch.

Where local web add breaks down

A single local browser works fine for a demo. It fails predictably at scale, and the failures are boring rather than exotic:

  • Process leaks. A crashed agent leaves Chromium running. Ten crashes later, the box is out of memory.
  • Session collisions. Two agent runs share one browser profile, and cookies from run A leak into run B.
  • No isolation. A page that triggers a download or a popup can hijack the context your agent is using.
  • Debugging blind spots. When a run fails at step 14, you have logs but no way to see what the page looked like.
  • Network identity. Datacenter IPs get rate-limited or blocked on many sites, and rotating them locally is its own project.

None of these are model problems. They are runtime problems, and they are the reason hosted Chromium exists as a category.

Production criteria for a web add runtime

Before you commit to a runtime, evaluate it against the things that actually cause incidents.

CriterionWhy it mattersWhat to check
CDP accessYour agent code depends on itRaw WebSocket endpoint, not a proprietary wrapper
Session isolationPrevents cross-run data leaksOne browser per session, separate storage
Persistent profilesLogin state survives across runsNamed profiles you can reuse and revoke
Live viewerDebugging without re-runningReal-time view of the active page
Proxy and network settingsIP reputation affects successConfigurable proxy per session
Usage controlsCost predictabilityPer-session limits and metering you can inspect
Framework compatibilityAvoids rewritesPlaywright, Puppeteer, Selenium over CDP

The table is deliberately unglamorous. Stealth and CAPTCHA handling get the marketing attention, but isolation and observability are what keep an agent fleet from generating support tickets. For a deeper comparison of runtime options, see Remote Browser online.

Isolation is not optional

If two agents share a browser context, they share cookies, localStorage, and service workers. In a multi-tenant product, that is a data leak waiting to happen. The correct model is one browser per task or per user, torn down when the task ends. Hosted runtimes make this the default because spinning up a fresh Chromium is cheap when it is not on your laptop.

Persistent profiles need a lifecycle

Persistent profiles are useful — an agent that stays logged in to a dashboard is far more reliable than one that re-authenticates every run. But a profile is a credential store. Treat it that way: scope it to a user or tenant, rotate it on schedule, and have a revocation path. A runtime that offers profiles without a lifecycle story is a liability.

Wiring the agent tool layer

Once the browser connection is stable, the next web add task is exposing a tool surface the model can call reliably. Keep it small and typed.

  • navigate(url) — go to a URL, wait for a defined readiness signal.
  • click(selector) — click a resolved element, fail loudly if ambiguous.
  • type(selector, text) — fill an input, verify the value landed.
  • extract(selector, schema) — return structured data, not raw HTML.
  • screenshot() — capture state for the model or for logs.

Two rules cut failure rates noticeably. First, every tool should return a compact, structured result rather than a full DOM dump — models handle 200 tokens of JSON better than 20,000 tokens of HTML. Second, every tool should have an explicit timeout and a defined failure mode, so the agent can retry or escalate instead of hanging.

For agents that need to see the page, a screenshot tool plus a text extraction tool covers most cases. For agents that need to act, selector resolution is the hard part; prefer accessibility-tree or role-based selectors over brittle CSS paths.

Moving from local to hosted without a rewrite

The migration is mostly configuration. The steps:

  1. Parameterize the endpoint. Read the CDP WebSocket URL from an environment variable or your session API response.
  2. Stop calling `browser.close()`. Close pages and let the session API terminate the browser.
  3. Move profile handling to the runtime. Replace local user-data-dir paths with named profiles.
  4. Add session metadata. Tag each session with a task ID so logs and recordings are traceable.
  5. Set usage limits. Cap session duration so a stuck agent cannot run indefinitely.

If your code already uses connectOverCDP, step one is the only code change. Everything else is operational. The documentation covers the session API and connection details.

Common mistakes when you web add

Launching a browser per tool call. Browser startup dominates latency. Create one session per task, reuse it across steps, and close it at the end.

Ignoring page readiness. waitUntil: "load" is not enough for single-page apps. Wait for a specific element or network idle condition that matches the app you are driving.

Treating CDP as stable across versions. CDP domains evolve. Pin your Chromium version in the runtime, and test after upgrades.

Logging everything. Full DOM snapshots per step blow up storage and rarely help. Log structured tool results plus a screenshot on failure.

Skipping the viewer. When an agent fails, a live or recorded view of the page saves hours. Build the habit of checking it before reading logs.

When hosted Chromium is the right call

Hosted Chromium is worth it when any of these are true:

  • You run more than a handful of concurrent agents.
  • You need isolation between users or tenants.
  • You want persistent profiles without managing storage.
  • You need proxy configuration per session.
  • You want a live viewer for debugging.
  • You want metered usage instead of a fixed fleet of VMs.

It is not worth it when you are prototyping a single agent on your own machine, or when your workload is a nightly batch of a few dozen pages where a local Playwright script is simpler.

The honest framing: hosted runtimes trade a small amount of control for a large reduction in operational surface. For most teams shipping an AI agent for website tasks, that trade is correct. For teams with strict data-residency requirements or unusual browser builds, self-hosting may still win.

Cost and usage considerations

Pricing models for hosted browsers vary — per browser-hour, per session, per concurrent browser. Before you commit, model your actual workload: average session length, concurrency at peak, and how often sessions are idle but open. Idle sessions are the most common source of surprise bills, because an agent that thinks for 40 seconds between steps is still holding a browser.

Two controls help: cap session duration at the runtime level, and close sessions explicitly when a task completes rather than relying on timeouts. Check pricing for current rates and metering details rather than assuming a number.

A practical checklist

Before you call the web add done:

  • [ ] Agent code connects over CDP and works against both local and hosted endpoints.
  • [ ] One browser session per task, with explicit teardown.
  • [ ] Tool surface is small, typed, and returns structured results.
  • [ ] Timeouts on every tool call.
  • [ ] Persistent profiles scoped and revocable.
  • [ ] Proxy settings configurable per session.
  • [ ] Live viewer or recordings available for failed runs.
  • [ ] Usage limits set and monitored.
  • [ ] Chromium version pinned and upgrade-tested.

Summary

Web add is a runtime problem disguised as a coding task. The code to connect an agent to Chromium over CDP is short and stable; the work is in isolation, lifecycle, observability, and cost control. Get those right locally and the move to hosted Chromium is a configuration change. Get them wrong and no amount of model quality will save the run.

Start with the CDP connection, keep the tool surface small, and evaluate runtimes against the criteria in the table above rather than against marketing claims. If you want to see how Remote Browser handles sessions, profiles, and the live viewer, the documentation is the place to start.