> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Connect Playwright or Puppeteer to Zenrows Browser

> Run your Playwright or Puppeteer scripts in Zenrows Browser, a cloud browser with residential IPs, from any country or region.

Zenrows Browser, the browser for agents, is a cloud Chrome that your scripts and AI agents drive over the Chrome DevTools Protocol (CDP). Residential IPs and anti-bot handling are built in, so you keep your Playwright or Puppeteer code and change one line: the browser you connect to.

**Uses:** [Browser](/browser-sessions/introduction) with [`proxy_country`](/browser-sessions/features/country), [`proxy_region`](/browser-sessions/features/world-region) and [`session_ttl`](/browser-sessions/features/session-ttl)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use the Zenrows browser to open https://www.scrapingcourse.com/ecommerce/ as a visitor from the US,
tell me the page title and the first three product names, then close the browser session.
```

## Connect and open a page

Every connection starts a new browser session. Use the WebSocket URL `wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY` wherever you would launch a local browser, and close the browser when you finish so the session ends right away.

<CodeGroup>
  ```python Python (Playwright) theme={"dark"}
  # pip install playwright
  import asyncio
  from playwright.async_api import async_playwright

  CONNECTION_URL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY"

  async def main():
      async with async_playwright() as p:
          browser = await p.chromium.connect_over_cdp(CONNECTION_URL)
          try:
              context = browser.contexts[0]
              page = await context.new_page()
              await page.goto("https://www.scrapingcourse.com/ecommerce/")
              print("Title:", await page.title())
          finally:
              await browser.close()  # ends the session and stops billing

  asyncio.run(main())
  ```

  ```javascript Node.js (Playwright) theme={"dark"}
  // npm install playwright-core
  import { chromium } from "playwright-core";

  const connectionURL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY";

  const browser = await chromium.connectOverCDP(connectionURL);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://www.scrapingcourse.com/ecommerce/");
    console.log("Title:", await page.title());
  } finally {
    await browser.close(); // ends the session and stops billing
  }
  ```

  ```javascript Puppeteer theme={"dark"}
  // npm install puppeteer-core
  import puppeteer from "puppeteer-core";

  const browser = await puppeteer.connect({
    browserWSEndpoint: "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY",
  });
  try {
    const page = await browser.newPage();
    await page.goto("https://www.scrapingcourse.com/ecommerce/");
    console.log("Title:", await page.title());
  } finally {
    await browser.close(); // ends the session and stops billing
  }
  ```

  ```bash CLI theme={"dark"}
  # Print the WebSocket URL for your account, ready to paste into any CDP client
  zenrows browser connect --proxy-country us
  ```
</CodeGroup>

The browser arrives with a default context already open, so `browser.contexts[0]` (Python) or `browser.contexts()[0]` (Node.js) is the natural place for new pages. Installing `playwright-core` or `puppeteer-core` is enough: the browser runs in the cloud, so you skip the local browser download.

## Choose the country, region and session lifetime

Add parameters to the WebSocket URL:

| Parameter | Example | What it does |
| - | - | - |
| `proxy_country` | `proxy_country=us` | Residential IP from one country (ISO 3166-1 alpha-2 code) |
| `proxy_region` | `proxy_region=eu` | Residential IP from a world region: `eu`, `na`, `ap`, `sa`, `af`, `me` |
| `session_ttl` | `session_ttl=2m` | How long the session stays open: a duration such as `2m` or `10m` (default `3m`) |

Use either `proxy_country` or `proxy_region` on a connection. This example opens a US session that lives for two minutes and prints the IP the target site sees:

<CodeGroup>
  ```python Python (Playwright) theme={"dark"}
  import asyncio
  from playwright.async_api import async_playwright

  CONNECTION_URL = (
      "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY"
      "&proxy_country=us&session_ttl=2m"
  )

  async def main():
      async with async_playwright() as p:
          browser = await p.chromium.connect_over_cdp(CONNECTION_URL)
          try:
              page = await browser.contexts[0].new_page()
              await page.goto("https://www.scrapingcourse.com/ecommerce/")
              print("Title:", await page.title())
              await page.goto("https://httpbin.io/ip")
              print("IP:", await page.inner_text("body"))
          finally:
              await browser.close()

  asyncio.run(main())
  ```

  ```javascript Node.js (Playwright) theme={"dark"}
  import { chromium } from "playwright-core";

  const connectionURL =
    "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY&proxy_region=eu&session_ttl=2m";

  const browser = await chromium.connectOverCDP(connectionURL);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://www.scrapingcourse.com/ecommerce/");
    console.log("Title:", await page.title());
    await page.goto("https://httpbin.io/ip");
    console.log("IP:", await page.innerText("body"));
  } finally {
    await browser.close();
  }
  ```

  ```javascript Puppeteer theme={"dark"}
  import puppeteer from "puppeteer-core";

  const browser = await puppeteer.connect({
    browserWSEndpoint: "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY&proxy_country=de",
  });
  try {
    const page = await browser.newPage();
    await page.goto("https://httpbin.io/ip");
    console.log("IP:", await page.$eval("body", (el) => el.innerText));
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

## Build the URL with the Zenrows Browser SDK

In Node.js, the [`@zenrows/browser-sdk`](/browser-sessions/sdk-overview) package builds and validates the URL for you. `sessionTTL` takes seconds, for example `120`, and `proxy.location` takes a `ProxyCountry` or `ProxyRegion` value.

```javascript Node.js (SDK) theme={"dark"}
// npm install @zenrows/browser-sdk playwright-core
import { ScrapingBrowser, ProxyCountry } from "@zenrows/browser-sdk";
import { chromium } from "playwright-core";

const scrapingBrowser = new ScrapingBrowser({ apiKey: "YOUR_ZENROWS_API_KEY" });
const connectionURL = scrapingBrowser.getConnectURL({
  proxy: { location: ProxyCountry.US },
  sessionTTL: 120,
});

const browser = await chromium.connectOverCDP(connectionURL);
try {
  const page = await browser.contexts()[0].newPage();
  await page.goto("https://www.scrapingcourse.com/ecommerce/");
  console.log("Title:", await page.title());
} finally {
  await browser.close();
}
```

`new ScrapingBrowser()` with no arguments reads the key from the `ZENROWS_API_KEY` environment variable.

## Example output

From the US session above (the IP changes on every session):

```text theme={"dark"}
Title: Ecommerce Test Site to Learn Web Scraping – ScrapingCourse.com
IP: {
  "origin": "158.46.196.221:41723"
}
```

The SDK example prints the title:

```text theme={"dark"}
Title: Ecommerce Test Site to Learn Web Scraping – ScrapingCourse.com
```

## Customize it

| Change | How |
| - | - |
| A specific country | `&proxy_country=gb` (any ISO code), see [Country targeting](/browser-sessions/features/country) |
| A world region | `&proxy_region=ap`, see [World region](/browser-sessions/features/world-region) |
| A longer task | `&session_ttl=10m`, see [Session TTL](/browser-sessions/features/session-ttl) |
| A fresh, isolated profile | `await browser.new_context()` in the same session, see [Log in once and reuse the session](/academy/browser/login-and-reuse-sessions) |
| Many browsers at once | Open one connection per task, see [Run browser sessions in parallel](/academy/browser/parallel-sessions) |
| Faster page loads | `page.goto(url, wait_until="domcontentloaded")` returns as soon as the HTML is ready |

## Troubleshooting

* **`page.goto` times out waiting for `load`**: a heavy asset on the page is still loading. Pass `wait_until="domcontentloaded"` (Python) or `{ waitUntil: "domcontentloaded" }` (Node.js), then wait for the element you need.
* **The session closes in the middle of a long task**: the session reached its lifetime. Raise `session_ttl`, for example `&session_ttl=15m`.
* **Usage keeps growing after your script ends**: the session stayed open until its lifetime ran out. Call `browser.close()` in a `finally` block on every path.
* **401 when connecting**: the API key in the URL is wrong or missing. Copy it again from the [Browser dashboard](https://app.zenrows.com/browser).

## Related recipes

<CardGroup cols={2}>
  <Card title="Log in once and reuse the session" icon="login" href="/academy/browser/login-and-reuse-sessions">
    Save cookies after one login and start every next session signed in.
  </Card>

  <Card title="Run browser sessions in parallel" icon="arrows-split" href="/academy/browser/parallel-sessions">
    Open several cloud browsers at once with a concurrency limit.
  </Card>

  <Card title="Build a browser agent with Zenrows" icon="robot" href="/academy/browser/build-a-browser-agent">
    Let Claude or GPT drive the browser with the Zenrows tools.
  </Card>

  <Card title="Let your AI assistant browse with Browser tools" icon="message-chatbot" href="/academy/browser/ai-assistant-browsing">
    Drive the browser from Claude, Cursor or the CLI.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.