Skip to content
Browsa
  1. Guides
  2. Playwright and Puppeteer

Connect Playwright or Puppeteer to a cloud browser over CDP.

A cloud browser over CDP is a Chromium that runs on someone else's server and speaks the Chrome DevTools Protocol, so your own Playwright or Puppeteer code drives it the same way it drives a local browser. On Browsa, one API call starts the browser and returns its CDP address; you change the line that launches the browser and keep the rest of your script.

Time
About 5 minutes
Cost
1 credit per started 12 minutes of browser time, 5 for a full hour
Works with
Playwright (Python, Node), Puppeteer, any CDP client

How do I run Playwright in the cloud?

  1. Start a browser. POST /v1/agents (or create_agent() in the SDK) starts a Chromium in Browsa's cloud and returns its id, its cdp_http address and a live_url you can open to watch it.
  2. Connect. Replace chromium.launch() with chromium.connect_over_cdp(cdp_http, headers={"X-API-Key": API_KEY}), or puppeteer.launch() with puppeteer.connect({ browserURL, headers }). The browser already has a context and an open page: use it.
  3. Clean up. Call destroy_agent() when you are done, or let the browser shut down when its TTL ends. You keep 1 credit per started 12 minutes it ran; the rest of what it reserved comes back.

You need a Browsa API key (it starts with agt_live_). Sign up for 100 free credits once you verify your email, no card. There is no Chrome to install on your machine or in your Docker image.

Connect your client

Playwright in Python

Terminal
pip install browsa playwright

You don't need playwright install: the browser runs on Browsa.

cloud_playwright.py
import os

from browsa import Client
from playwright.sync_api import sync_playwright

API_KEY = os.environ["BROWSA_API_KEY"]
client = Client(api_key=API_KEY)
agent = client.create_agent(ttl="10m")  # a cloud Chromium, up to 10 minutes

try:
    with sync_playwright() as p:
        browser = p.chromium.connect_over_cdp(
            agent["cdp_http"], headers={"X-API-Key": API_KEY}
        )
        context = browser.contexts[0]
        page = context.pages[0] if context.pages else context.new_page()
        page.goto("https://example.com")
        print(page.title())
        # the rest is plain Playwright
finally:
    client.destroy_agent(agent["id"])

Playwright in TypeScript or Node

Terminal
npm i browsa playwright
cloud-playwright.ts
import { Client } from "browsa";
import { chromium } from "playwright";

const apiKey = process.env.BROWSA_API_KEY!;
const client = new Client({ apiKey });
const agent = await client.createAgent({ ttl: "10m" });

try {
  const browser = await chromium.connectOverCDP(agent.cdp_http, {
    headers: { "X-API-Key": apiKey },
  });
  const context = browser.contexts()[0];
  const page = context.pages()[0] ?? (await context.newPage());
  await page.goto("https://example.com");
  console.log(await page.title());
} finally {
  await client.destroyAgent(agent.id);
}

Puppeteer

Use puppeteer-core. It has the same API as puppeteer but does not download a browser, which you no longer need.

Terminal
npm i browsa puppeteer-core
cloud-puppeteer.ts
import { Client } from "browsa";
import puppeteer from "puppeteer-core";

const apiKey = process.env.BROWSA_API_KEY!;
const client = new Client({ apiKey });
const agent = await client.createAgent({ ttl: "5m" });

try {
  const browser = await puppeteer.connect({
    browserURL: agent.cdp_http,
    headers: { "X-API-Key": apiKey },
  });
  const [page] = await browser.pages();
  await page.goto("https://example.com");
  console.log(await page.title());
  await browser.disconnect();
} finally {
  await client.destroyAgent(agent.id);
}

Raw CDP and other clients

The create response has two addresses. cdp_http is the browser's HTTP endpoint, the one Playwright and Puppeteer expect. cdp_url is the WebSocket of a single page, for clients that speak CDP directly. Both need your key in an X-API-Key header. Any library that can attach to an existing Chromium over CDP with a custom header can use the same address, which is how the browser-use and Stagehand guides work.

Without the SDK, start the browser with plain HTTP:

curl
curl https://browsa.io/v1/agents \
  -H "X-API-Key: $BROWSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttl": "10m", "initial_url": "https://example.com"}'

# -> { "id": "agt_…", "status": "running", "cdp_http": "https://browsa.io/api/v1/cdp/…",
#      "cdp_url": "wss://browsa.io/api/v1/cdp/…/ws/page/…", "live_url": "…", "cost_credits": 5 }

cost_credits is what the browser holds while it runs: 1 credit for a TTL up to 5 minutes, 5 for a longer one. When it ends, the blocks of 12 minutes it never used go back to your balance.

What works differently from a local browser?

Viewport and user agent

Screen size, user agent, timezone, language, geolocation and CPU count are set together when the browser starts. CDP emulation commands that change one of them alone return success and change nothing, so page.set_viewport_size(), page.setViewport() and page.setUserAgent() have no effect.

What to doPass screen_resolution and os_type to POST /v1/agents. To drive emulation yourself, add cdp_overrides=allow to the query of the CDP address; keeping the fingerprint consistent is then up to you.

Contexts and pages

The browser starts with a context and an open page.

What to doUse browser.contexts[0] and its first page instead of creating a new context.

File downloads

Downloaded files stay in the cloud browser, and Playwright's download event reports canceled.

What to doFetch the file inside the page (see below), which uses the browser's cookies and IP address.

Lifetime

A browser runs until its TTL ends, at most 1 hour, or until you destroy it. It does not end on idleness.

What to doAsk for the TTL you need ("5m" to "1h"). Start a new browser for longer jobs, or use a saved identity to keep logins between them.

Network

Browsa does not supply proxies. Without one, traffic leaves from Browsa's data center with a data-center IP address.

What to doPass your own HTTP, HTTPS or SOCKS5 proxy in proxy, or a saved one in proxy_id.

Automation signals

Playwright and Puppeteer turn on CDP's Runtime domain when they attach, and some bot checks can see that.

What to doIf a site flags your client, try a raw CDP client without Runtime.enable, or Browsa's own task agent (POST /v1/tasks). See the dated results on the benchmarks page.

Parallel browsers

6 browser slots per workspace. A request with every slot in use returns 429 with Retry-After: 30 and is not charged.

What to doQueue work on your side, or email support@browsa.io for more slots.

How do I reconnect after a dropped connection?

Closing your Playwright or Puppeteer connection does not stop the browser. It keeps running until its TTL ends, so another process can attach to it. Get its address again with GET /v1/agents/{id}/cdp (or get_agent(agent_id) in Python, getAgent(agentId) in TypeScript) and connect with the same X-API-Key header. The endpoint returns 409 when the browser is no longer running.

How do I download a file?

Fetch it from inside the page and return the bytes:

Python
import base64

b64 = page.evaluate("""async (url) => {
  const buf = new Uint8Array(await (await fetch(url)).arrayBuffer());
  let s = ""; for (const b of buf) s += String.fromCharCode(b);
  return btoa(s);
}""", "https://example.com/report.pdf")
open("report.pdf", "wb").write(base64.b64decode(b64))

How do I use my own proxy?

Add a proxy object when you start the browser. The browser then uses the proxy's IP address, and with country set, its timezone and language follow that country. Proxy traffic costs nothing extra on Browsa.

curl
curl https://browsa.io/v1/agents \
  -H "X-API-Key: $BROWSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ttl": "10m",
    "proxy": {"type": "http", "host": "proxy.example.com", "port": 8080,
              "username": "your-proxy-user", "password": "your-proxy-password"},
    "country": "de"
  }'

A raw browser starts even when the proxy is unreachable, so check it first. A saved proxy has a check endpoint and can be passed as proxy_id.

How do I keep logins between runs?

Use a persistent identity instead of a burner. It keeps its cookies, local storage and fingerprint between sessions. Create it once with "mode": "persistent", then open a session whenever a script needs it: POST /v1/agents/{id}/sessions returns the same shape as a new browser, including cdp_http, and you connect to it exactly as above. An identity costs 50 credits every 30 days and each session costs 1 credit. See Open a session.

What does it cost?

What you runCredits
Browser that runs up to 12 minutes1
Browser that runs a full hour5
Persistent identity, every 30 days50
Session on a persistent identity (up to 1 hour)1
Browser that fails to startRefunded

One credit is $0.10 on the $20 Starter pack and less on larger packs. Credits never expire. Full pricing.

Troubleshooting

401 Unauthorized when connecting

The X-API-Key header did not reach Browsa, or the key is wrong. Send it on the connect call exactly as in the examples, and check that the key starts with agt_.

402 when starting a browser

INSUFFICIENT_CREDIT means the balance cannot cover the TTL you asked for; BUDGET_REACHED means your monthly spending limit is reached. Check GET /v1/credits and GET /v1/budget.

The connection drops after a few minutes

The browser reached the end of its TTL. Ask for a longer one, up to "1h".

Questions

Why does set_viewport_size() not change the browser?

A Browsa browser's screen size, user agent, timezone, language and geolocation are set together when the browser is created, so a page finds one consistent machine. CDP emulation commands that would change one of them alone return success and change nothing. Set screen_resolution and os_type when you create the browser, or add cdp_overrides=allow to the CDP address if you want to drive emulation yourself.

How do I download a file from a cloud browser?

Files a page downloads stay in the cloud browser, and Playwright reports the download as canceled. Fetch the file from inside the page with page.evaluate(), which uses the browser's cookies and IP address, and return the bytes as base64.

How many browsers can run at once?

A workspace has 6 browser slots. Each running browser takes one, and so does each saved identity. A new browser with every slot in use returns 429 TOO_MANY_REQUESTS with Retry-After: 30, and nothing is charged. Email support@browsa.io if you need more.

Does Browsa provide proxies?

No. Without a proxy the browser uses the IP address of Browsa's data center. You can pass your own HTTP, HTTPS or SOCKS5 proxy per browser, and the browser's timezone and language follow the proxy's country.

Next steps

Run your script on a cloud browser.

100 free credits. No card, no subscription.