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

# Automate a browser

> Launch a BrowserBox, connect via Playwright or Puppeteer, take a screenshot, and scrape a web page — all inside an isolated VM.

BrowserBox runs a full browser (Chromium, Firefox, or WebKit) inside an isolated VM and exposes it via Playwright Server or Chrome DevTools Protocol. You connect with Playwright or Puppeteer from your host — no browser install needed on the host.

## What you'll build

A script that:

1. Launches a BrowserBox with Chromium
2. Connects via Playwright or Puppeteer
3. Navigates to a page and takes a screenshot
4. Scrapes content from the page

## Prerequisites

<Tabs>
  <Tab title="Python (Playwright)">
    ```bash theme={null}
    pip install boxlite playwright
    ```
  </Tab>

  <Tab title="Node.js (Puppeteer)">
    ```bash theme={null}
    npm install @boxlite-ai/boxlite puppeteer-core
    ```

    <Note>
      Use `puppeteer-core` (not `puppeteer`) since the browser runs inside the BrowserBox, not on your host.
    </Note>
  </Tab>
</Tabs>

## Step 1: Launch a BrowserBox

Create a BrowserBox and get a connection endpoint.

<Tabs>
  <Tab title="Python (Playwright)">
    ```python title="browser.py" theme={null}
    import asyncio
    from boxlite import BrowserBox, BrowserBoxOptions

    async def main():
        opts = BrowserBoxOptions(browser="chromium", memory=2048, cpu=2)
        async with BrowserBox(opts) as browser:
            ws = await browser.playwright_endpoint()
            print(f"Playwright endpoint: {ws}")
            # → ws://localhost:3000/


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab title="Node.js (Puppeteer)">
    ```javascript title="browser.js" theme={null}
    import { BrowserBox } from '@boxlite-ai/boxlite';

    async function main() {
      const box = new BrowserBox({ browser: 'chromium' });
      try {
        const ws = await box.endpoint();
        console.log(`CDP endpoint: ${ws}`);
        // → ws://localhost:3000/...
      } finally {
        await box.stop();
      }
    }

    main();
    ```

    <Note>
      For Puppeteer, use `endpoint()` which starts the browser with CDP. Do **not** call `start()` first — `start()` launches Playwright Server, which is a different (mutually exclusive) connection mode.
    </Note>
  </Tab>
</Tabs>

### Connection modes

BrowserBox has two mutually exclusive connection modes:

| Mode              | Method                  | Protocol  | Use with                                    |
| ----------------- | ----------------------- | --------- | ------------------------------------------- |
| Playwright Server | `playwright_endpoint()` | WebSocket | Playwright (all browsers)                   |
| Direct CDP/BiDi   | `endpoint()`            | WebSocket | Puppeteer, Selenium (chromium/firefox only) |

### Supported browsers

| Browser    | Works with Playwright | Works with Puppeteer |
| ---------- | --------------------- | -------------------- |
| `chromium` | Yes                   | Yes (CDP)            |
| `firefox`  | Yes                   | Yes (WebDriver BiDi) |
| `webkit`   | Yes                   | No                   |

## Step 2: Take a screenshot

Connect to the browser and capture a screenshot.

<Tabs>
  <Tab title="Python (Playwright)">
    ```python title="screenshot.py" theme={null}
    import asyncio
    from boxlite import BrowserBox, BrowserBoxOptions
    from playwright.async_api import async_playwright


    async def main():
        opts = BrowserBoxOptions(browser="chromium", memory=2048, cpu=2)
        async with BrowserBox(opts) as browser:
            ws = await browser.playwright_endpoint()

            async with async_playwright() as p:
                # Connect to the browser running inside the VM
                b = await p.chromium.connect(ws)
                page = await b.new_page()

                await page.goto("https://example.com")
                await page.screenshot(path="screenshot.png")
                print("Screenshot saved to screenshot.png")

                await b.close()


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab title="Node.js (Puppeteer)">
    ```javascript title="screenshot.js" theme={null}
    import { BrowserBox } from '@boxlite-ai/boxlite';
    import puppeteer from 'puppeteer-core';

    async function main() {
      const box = new BrowserBox({ browser: 'chromium' });
      try {
        const ws = await box.endpoint();

        // Connect to the browser running inside the VM
        const instance = await puppeteer.connect({ browserWSEndpoint: ws });
        const page = await instance.newPage();

        await page.goto('https://example.com');
        await page.screenshot({ path: 'screenshot.png' });
        console.log('Screenshot saved to screenshot.png');

        await instance.close();
      } finally {
        await box.stop();
      }
    }

    main();
    ```
  </Tab>
</Tabs>

## Step 3: Scrape a page

Extract content from a web page using standard DOM queries.

<Tabs>
  <Tab title="Python (Playwright)">
    ```python title="scrape.py" theme={null}
    import asyncio
    from boxlite import BrowserBox, BrowserBoxOptions
    from playwright.async_api import async_playwright


    async def main():
        opts = BrowserBoxOptions(browser="chromium", memory=2048, cpu=2)
        async with BrowserBox(opts) as browser:
            ws = await browser.playwright_endpoint()

            async with async_playwright() as p:
                b = await p.chromium.connect(ws)
                page = await b.new_page()

                await page.goto("https://news.ycombinator.com")

                # Extract the top 5 story titles
                titles = await page.eval_on_selector_all(
                    ".titleline > a",
                    "elements => elements.slice(0, 5).map(e => e.textContent)"
                )

                print("Top 5 Hacker News stories:")
                for i, title in enumerate(titles, 1):
                    print(f"  {i}. {title}")

                await b.close()


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab title="Node.js (Puppeteer)">
    ```javascript title="scrape.js" theme={null}
    import { BrowserBox } from '@boxlite-ai/boxlite';
    import puppeteer from 'puppeteer-core';

    async function main() {
      const box = new BrowserBox({ browser: 'chromium' });
      try {
        const ws = await box.endpoint();

        const instance = await puppeteer.connect({ browserWSEndpoint: ws });
        const page = await instance.newPage();

        await page.goto('https://news.ycombinator.com');

        // Extract the top 5 story titles
        const titles = await page.$$eval(
          '.titleline > a',
          (links) => links.slice(0, 5).map((a) => a.textContent)
        );

        console.log('Top 5 Hacker News stories:');
        titles.forEach((title, i) => {
          console.log(`  ${i + 1}. ${title}`);
        });

        await instance.close();
      } finally {
        await box.stop();
      }
    }

    main();
    ```
  </Tab>
</Tabs>

## Step 4: Interact with a page

Read content, click links, and handle navigation.

<Tabs>
  <Tab title="Python (Playwright)">
    ```python title="interact.py" theme={null}
    import asyncio
    from boxlite import BrowserBox, BrowserBoxOptions
    from playwright.async_api import async_playwright


    async def main():
        opts = BrowserBoxOptions(browser="chromium", memory=2048, cpu=2)
        async with BrowserBox(opts) as browser:
            ws = await browser.playwright_endpoint()

            async with async_playwright() as p:
                b = await p.chromium.connect(ws)
                page = await b.new_page()

                await page.goto("https://example.com")

                # Get page title and content
                title = await page.title()
                heading = await page.text_content("h1")
                print(f"Title: {title}")
                print(f"Heading: {heading}")

                # Click a link
                await page.click("a")
                print(f"Navigated to: {page.url}")

                await b.close()


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab title="Node.js (Puppeteer)">
    ```javascript title="interact.js" theme={null}
    import { BrowserBox } from '@boxlite-ai/boxlite';
    import puppeteer from 'puppeteer-core';

    async function main() {
      const box = new BrowserBox({ browser: 'chromium' });
      try {
        const ws = await box.endpoint();

        const instance = await puppeteer.connect({ browserWSEndpoint: ws });
        const page = await instance.newPage();

        await page.goto('https://example.com');

        // Get page title and content
        const title = await page.title();
        const heading = await page.$eval('h1', (el) => el.textContent);
        console.log(`Title: ${title}`);
        console.log(`Heading: ${heading}`);

        // Click a link
        await page.click('a');
        console.log(`Navigated to: ${page.url()}`);

        await instance.close();
      } finally {
        await box.stop();
      }
    }

    main();
    ```
  </Tab>
</Tabs>

<Tip>
  BrowserBox allocates 2 CPUs and 2048 MiB of memory by default. Increase these if your pages are heavy or you're running multiple tabs.
</Tip>

## What's next?

<CardGroup cols={2}>
  <Card title="Connect to an LLM" icon="robot" href="/tutorials/llm-integration">
    Give your LLM agent the ability to browse the web by combining BrowserBox with function calling.
  </Card>

  <Card title="BrowserBox API reference" icon="book" href="/reference/python/box-types#browserbox">
    Full API docs for BrowserBox, BrowserBoxOptions, and connection endpoints.
  </Card>

  <Card title="AI agent integration" icon="gear" href="/guides/ai-agent-integration">
    Production patterns for using BoxLite with AI agents.
  </Card>
</CardGroup>
