# Browser

The `browser` template gives an agent its own Chromium to drive. It is a real
browser running headful on its own display, not a headless build, and you
connect to it over the Chrome DevTools Protocol (CDP) from Playwright,
Puppeteer or anything else that speaks CDP. Port 9222 is CDP; port 6080 is a
live view of the screen in noVNC.

The browser is [fingerprint-chromium](https://github.com/adryfish/fingerprint-chromium),
an open-source build of ungoogled-chromium (BSD-3-Clause), pinned to version
150. Each profile gets a fingerprint of its own: canvas, audio, fonts and the
hardware values a page can read come from a seed that the profile keeps for
its whole life.

## Start a browser

Create a sandbox from `browser` and connect to port 9222. Chromium starts on
the first CDP request, so the first connection takes a second or two longer
than the ones after it.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'
import { chromium } from 'playwright'

const sandbox = await Sandbox.create('browser', { timeoutMs: 1_800_000 })

const browser = await chromium.connectOverCDP(
  `https://${sandbox.getHost(9222)}`
)
const context = browser.contexts()[0]
const page = context.pages()[0] ?? (await context.newPage())

await page.goto('https://example.com')
console.log(await page.title())
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox
from playwright.sync_api import sync_playwright

sandbox = Sandbox.create("browser", timeout=1800)

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(f"https://{sandbox.get_host(9222)}")
    context = browser.contexts[0]
    page = context.pages[0] if context.pages else context.new_page()

    page.goto("https://example.com")
    print(page.title())
```

</Tab>
</CodeTabs>

<Callout kind="warning">
With the default `allowPublicTraffic: true`, anyone who has the sandbox URL can
drive the browser and watch its screen, including every page it is signed in
to. Make the sandbox private whenever the browser holds a login.
</Callout>

Use the browser's existing context, `browser.contexts()[0]`. It is the
profile on disk, with its fingerprint, cookies and logins. A context you make
with `newContext()` is a fresh incognito-style context that keeps nothing.

Connect, never launch. `chromium.launch()` starts a second, local browser with
automation switches turned on, and a page can see them; `connectOverCDP`
attaches to the one already running in the sandbox.

The URL is the HTTPS address of port 9222. Its `/json/version` answers with a
`wss://` WebSocket address on the same host, which is what Playwright reads.
A client that wants the WebSocket address directly can use
`wss://<host>/` with no path; it reaches the browser target.

## Connect to a private sandbox

With `network: { allowPublicTraffic: false }` the CDP and live-view ports stop
answering callers without the sandbox's traffic access token. Send the token
in the `e2b-traffic-access-token` header. Playwright sends the headers you give
`connectOverCDP` on both the HTTP request and the WebSocket.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'
import { chromium } from 'playwright'

const sandbox = await Sandbox.create('browser', {
  network: { allowPublicTraffic: false },
})

const token = sandbox.trafficAccessToken
if (!token) throw new Error('no traffic access token')

const browser = await chromium.connectOverCDP(
  `https://${sandbox.getHost(9222)}`,
  { headers: { 'e2b-traffic-access-token': token } }
)
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox
from playwright.sync_api import sync_playwright

sandbox = Sandbox.create("browser", network={"allow_public_traffic": False})

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp(
        f"https://{sandbox.get_host(9222)}",
        headers={"e2b-traffic-access-token": sandbox.traffic_access_token},
    )
```

</Tab>
</CodeTabs>

## Watch it live

The screen is served by noVNC at `/vnc.html` on port 6080. Open it in a
browser tab to watch the agent, or click into it to take over: the keyboard
and mouse are shared with the agent.

```ts
console.log(`https://${sandbox.getHost(6080)}/vnc.html?autoconnect=true&resize=scale`)
```

The live view takes the token as a header too, so a browser tab cannot open
the live view of a private sandbox on its own. Put a proxy you control in
front of it that adds the header, or keep the sandbox public only while you
watch.

## The `browser` command

Inside the sandbox, `browser` starts, stops and configures Chromium. Run it
with `commands.run` before you connect, and your settings apply from the first
page. The settings are saved, so if Chromium exits, the next CDP request starts
it again the same way.

```bash
browser start   [--profile NAME] [--proxy URL] [--seed N] [--timezone TZ]
                [--lang LANG] [--platform linux|windows|macos] [-- CHROME FLAGS]
browser restart [the same flags]
browser stop
browser status
```

Every flag has an environment variable: `BROWSER_PROFILE`, `BROWSER_PROXY`,
`BROWSER_SEED`, `BROWSER_TIMEZONE`, `BROWSER_LANG`, `BROWSER_PLATFORM` and
`BROWSER_ARGS`. Chromium's log is `~/.browser/chrome.log`.

### Profiles

A profile is a directory under `~/.browser/profiles/`, `default` unless you
name another. It holds the cookies, local storage, history and logins, and the
fingerprint seed. The seed is drawn at random the first time a profile starts
and read back every time after, so one profile is one identity, and two
sandboxes never share one by accident.

<CodeTabs>
<Tab label="TypeScript">

```ts
await sandbox.commands.run('browser restart --profile shop-account')
```

</Tab>
<Tab label="Python">

```python
sandbox.commands.run("browser restart --profile shop-account")
```

</Tab>
</CodeTabs>

The disk survives [pause and resume](/docs/sandbox/pause-and-snapshots), so a
paused sandbox comes back with every profile, signed in where you left it. To
carry an identity to another sandbox, copy the profile directory with the
[filesystem](/docs/sandbox/filesystem) API, or pass `--seed` to give a new
profile a seed you chose (an integer from 0 to 2147483647).

### Proxy

`--proxy` sends every request through an HTTP or SOCKS proxy, for example
`--proxy socks5://203.0.113.7:1080`. The proxy URL cannot carry a username and
password; use a proxy that allows your sandbox's address instead.

<CodeTabs>
<Tab label="TypeScript">

```ts
await sandbox.commands.run('browser restart', {
  envs: { BROWSER_PROXY: 'http://203.0.113.7:3128' },
})
```

</Tab>
<Tab label="Python">

```python
sandbox.commands.run(
    "browser restart",
    envs={"BROWSER_PROXY": "http://203.0.113.7:3128"},
)
```

</Tab>
</CodeTabs>

### Timezone and language

On every start the browser looks up the timezone of the IP it will browse from
— through the proxy when there is one — and uses it, so the clock a page reads
agrees with where the request comes from. The lookup asks `ipwho.is` and gives
up after 4 seconds; then the timezone is UTC. Set it yourself with
`--timezone Europe/Paris`. The timezone is read when the browser starts, so a
sandbox that resumes behind a different IP keeps the old one until
`browser restart`.

The language is `en-US` unless you pass `--lang`, for example `--lang de-DE`;
`Accept-Language` follows it.

### Platform

The fingerprint describes a Linux computer by default, which is what the
sandbox is. `--platform windows` or `--platform macos` changes the user agent,
the platform and the reported graphics card to match; the fonts and the
network stack underneath are still Linux.

## Size and price

The template is 2 vCPU and 4 GB, billed like every other sandbox; see
[Limits and pricing](/docs/limits-and-pricing). The browser renders on the CPU,
with no GPU, so pages heavy on WebGL or video are slower than on a desktop.

## What it is not

The browser does not solve CAPTCHAs and is not a way around a site's terms.
A fingerprint per profile, a matching timezone and a headful display remove
the differences that make an automated browser look unlike a person's; they
do not change what the agent does on the page. Sites that check behaviour,
IP reputation or account history judge those as they would for anyone.
