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

# Playwright

> Connect Playwright to a Surfsky browser over CDP, open a page, and stop the session.

Playwright attaches to a Surfsky browser through the session's CDP WebSocket URL. Use `connect_over_cdp` in Python or `connectOverCDP` in the other languages instead of launching a browser. The browser runs in Surfsky's cloud; no local Chromium is needed.

<Warning>
  Standard Playwright creates utility worlds, helper scripts, and bindings that
  detection scripts can identify. Use a patched fork such as Patchright or
  rebrowser-playwright for sites with bot protection.
</Warning>

<span id="using-surfsky-with-playwright" />

## Prerequisites

Set your [API token and base URL](/quickstart#before-you-start). The examples start a browser with an empty request body, which uses your account's default proxy pool. Proxy, fingerprint, and other start options are in the [API reference](/api-reference/profiles/start-one-time-session).

<span id="code-example" />

## Run an example

Each example starts a one-time browser, connects, opens a page, prints its title, and closes the browser. To keep cookies and login state between runs, start a [persistent profile](/sessions#persistent-profiles) instead; the connection code is the same.

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install playwright requests
    ```

    Save as `surfsky_playwright.py`:

    ```python theme={null}
    import os
    import requests
    from playwright.sync_api import sync_playwright

    base_url = os.environ["SURFSKY_API_BASE_URL"].rstrip("/")
    headers = {"X-Cloud-Api-Token": os.environ["SURFSKY_API_TOKEN"]}

    response = requests.post(f"{base_url}/profiles/one_time", json={}, headers=headers, timeout=120)
    response.raise_for_status()
    session = response.json()

    with sync_playwright() as playwright:
        browser = playwright.chromium.connect_over_cdp(session["ws_url"])
        context = browser.contexts[0]
        page = context.pages[0] if context.pages else context.new_page()
        page.goto("https://example.com")
        print(page.title())
        browser.new_browser_cdp_session().send("Browser.close")
    ```

    ```bash theme={null}
    python surfsky_playwright.py
    ```
  </Tab>

  <Tab title="JavaScript">
    Requires Node.js 22 or newer.

    ```bash theme={null}
    npm install playwright-core
    ```

    Save as `surfsky-playwright.mjs`:

    ```javascript theme={null}
    import { chromium } from "playwright-core";

    const baseUrl = process.env.SURFSKY_API_BASE_URL.replace(/\/+$/, "");
    const headers = {
      "X-Cloud-Api-Token": process.env.SURFSKY_API_TOKEN,
      "Content-Type": "application/json",
    };

    const response = await fetch(`${baseUrl}/profiles/one_time`, {
      method: "POST",
      headers,
      body: "{}",
      signal: AbortSignal.timeout(120_000),
    });
    if (!response.ok)
      throw new Error(`Start failed: ${response.status} ${await response.text()}`);
    const session = await response.json();

    const browser = await chromium.connectOverCDP(session.ws_url);
    const context = browser.contexts()[0];
    const page = context.pages()[0] ?? (await context.newPage());
    await page.goto("https://example.com");
    console.log(await page.title());
    const cdp = await browser.newBrowserCDPSession();
    await cdp.send("Browser.close");
    ```

    ```bash theme={null}
    node surfsky-playwright.mjs
    ```
  </Tab>

  <Tab title="Java">
    Add Playwright to your project using the [official instructions](https://playwright.dev/java/docs/intro). Start a browser with the [REST quickstart](/quickstart#using-the-api-directly) and export the returned `ws_url` as `SURFSKY_WS_URL`.

    ```java theme={null}
    import com.microsoft.playwright.*;

    public class SurfskyExample {
        public static void main(String[] args) {
            String wsUrl = System.getenv("SURFSKY_WS_URL");
            try (Playwright playwright = Playwright.create()) {
                Browser browser = playwright.chromium().connectOverCDP(wsUrl);
                BrowserContext context = browser.contexts().get(0);
                Page page = context.pages().isEmpty() ? context.newPage() : context.pages().get(0);
                page.navigate("https://example.com");
                System.out.println(page.title());
                browser.newBrowserCDPSession().send("Browser.close");
            }
        }
    }
    ```
  </Tab>

  <Tab title=".NET">
    ```bash theme={null}
    dotnet add package Microsoft.Playwright
    ```

    Start a browser with the [REST quickstart](/quickstart#using-the-api-directly) and export the returned `ws_url` as `SURFSKY_WS_URL`. Put this in `Program.cs`:

    ```csharp theme={null}
    using Microsoft.Playwright;

    var wsUrl = Environment.GetEnvironmentVariable("SURFSKY_WS_URL");
    using var playwright = await Playwright.CreateAsync();
    var browser = await playwright.Chromium.ConnectOverCDPAsync(wsUrl);
    var context = browser.Contexts[0];
    var page = context.Pages.Count > 0 ? context.Pages[0] : await context.NewPageAsync();
    await page.GotoAsync("https://example.com");
    Console.WriteLine(await page.TitleAsync());
    var cdp = await browser.NewBrowserCDPSessionAsync();
    await cdp.SendAsync("Browser.close");
    ```

    Run with `dotnet run`.
  </Tab>
</Tabs>

Expected output:

```text theme={null}
Example Domain
```

<span id="close-the-browser-through-cdp" />

## Stop the session

Playwright's `browser.close()` only disconnects from a browser attached over CDP. Stop the session either through CDP, as the examples do:

```python theme={null}
browser.new_browser_cdp_session().send("Browser.close")
```

or through the API with the `internal_uuid` from the start response:

```bash theme={null}
curl --fail-with-body -X POST \
  "$SURFSKY_API_BASE_URL/profiles/INTERNAL_UUID/stop" \
  -H "X-Cloud-Api-Token: $SURFSKY_API_TOKEN"
```

Otherwise the browser stops after the inactivity timeout, 30 seconds by default.

<span id="important-notes" />
