---
title: Browser
description: Open a URL, enumerate tabs, and drive the rest of it as a desktop.
---

Two methods address the browser directly. Everything else about it is
[the desktop](/docs/sdk/input) — click it, type in it, screenshot it.

## Open

<CodeGroup>

```ts TypeScript
await machine.browser.open("https://example.com");
await machine.browser.open("https://example.com", { newWindow: true });
```

```python Python
machine.browser.open("https://example.com")
machine.browser.open("https://example.com", new_window=True)
```

</CodeGroup>

Opening a URL reuses the running browser unless a new window is asked for. It is an input action
and takes the [lease](/docs/sdk/input#the-input-lease) — it focuses the browser and navigates,
which is why it is a real method rather than something you assemble out of a hotkey and typing
into an address bar.

## Tabs

<CodeGroup>

```ts TypeScript
const tabs = await machine.browser.tabs();
if (tabs.supported) {
  for (const tab of tabs.tabs) console.log(tab.id, tab.title, tab.url);
} else {
  console.log(tabs.reason);
}
```

```python Python
tabs = machine.browser.tabs()
if tabs["supported"]:
    for tab in tabs["tabs"]:
        print(tab["id"], tab["title"], tab["url"])
else:
    print(tabs["reason"])
```

</CodeGroup>

**Tabs answer, they do not throw.** Enumeration depends on what the image's browser exposes, and
`supported` is false when the browser was not started by the machine. An agent that gets a `501`
learns nothing it can act on; one that gets a sentence can fall back to reading the screen.

## Driving pages

There is no page-object model, no selector engine and no injected script. A page is pixels and a
keyboard:

<CodeGroup>

```ts TypeScript
await machine.browser.open("https://example.com");
await machine.keyboard.press("F6"); // focus the address bar
await machine.keyboard.type("search terms");
await machine.keyboard.press("Return");

const shot = await machine.screen.screenshot({ format: "png" });
await machine.mouse.click({ at: { x: 640, y: 400 } });
```

```python Python
machine.browser.open("https://example.com")
machine.keyboard.press("F6")                   # focus the address bar
machine.keyboard.type("search terms")
machine.keyboard.press("Return")

shot = machine.screen.screenshot(format="png")
machine.mouse.click(at={"x": 640, "y": 400})
```

</CodeGroup>

That is deliberate: the point of a real desktop is that a site cannot tell it apart from a
person's, and an injected automation surface is exactly the thing that gives that away.

If you want a headless browser API, run one **inside** the machine over
[a terminal](/docs/sdk/terminal) and reach it through [a published port](/docs/sdk/ports).

## The profile persists

Cookies, logins and extensions live on the disk, so they survive a stop and a start and are
carried by a [snapshot, fork or template](/docs/sdk/snapshots). Logging into a site once and
saving the machine as a template is the intended way to give every future machine that session.

A [secret](/docs/sdk/secrets) is the opposite: delivered to tmpfs, and deliberately not carried by
a capture.
