---
title: "How to connect Selenium to a Donut Browser profile"
description: "Launch a Donut Browser profile from the local API, attach Selenium to its debugging port with a matching ChromeDriver, and run tests in Python or Node.js."
url: "https://donutbrowser.com/docs/guides/selenium/"
---

# How to connect Selenium to a Donut Browser profile

Selenium usually starts its own Chrome with a clean profile and a WebDriver flag. To keep a profile's device, cookies and proxy, let Donut Browser start the browser and attach Selenium to it with `debuggerAddress`. ChromeDriver then drives the running profile over its DevTools port. You need Pro, Team or Enterprise, Selenium 4 for Python or Node.js, and the [local API](https://donutbrowser.com/docs/quickstart) turned on with its token exported as `DONUT_API_KEY`.

## 1. Find the engine version

ChromeDriver must have the same major version as the browser. Donut profiles run on Wayfern, which is Chromium, and the Wayfern version is the Chromium version. Read it from the profile:

```bash
curl -sS http://127.0.0.1:10108/v1/profiles/<profile id> \
  -H "Authorization: Bearer $DONUT_API_KEY" | jq -r '.version'
# 155.0.8059.79  -> major version 155
```

The profile panel shows the same version on its **Overview** section.

## 2. Get the matching ChromeDriver

Download ChromeDriver for that major version and your system from the [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/) downloads. The same versions are listed as JSON at `latest-versions-per-milestone-with-downloads.json`. A minor version difference is fine; the major version must match.

```bash
# macOS on Apple silicon, major version 155
curl -sS https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone-with-downloads.json |
  jq -r '.milestones["155"].downloads.chromedriver[] | select(.platform == "mac-arm64") | .url'
```

The platform names are `mac-arm64`, `mac-x64`, `win64`, `win32`, `linux64` and `linux-arm64`. Unzip the file and note the path of `chromedriver` (`chromedriver.exe` on Windows).

Give Selenium this path. Without it, Selenium Manager picks a ChromeDriver for the Chrome installed on your computer, which can have another major version than Wayfern.

## 3. Launch the profile

`POST /v1/profiles/{id}/run` starts the profile and returns its DevTools port. If the profile already runs, the call returns its current port with `"attached": true` and does not restart it.

```bash
curl -sS -X POST http://127.0.0.1:10108/v1/profiles/<profile id>/run \
  -H "Authorization: Bearer $DONUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "about:blank", "headless": false}'
# {"profile_id":"…","remote_debugging_port":53127,"headless":false,"attached":false}
```

The port is bound to `127.0.0.1`. Other plans than Pro, Team and Enterprise get `402`.

## 4. Attach Selenium

Set `debuggerAddress` to `127.0.0.1:<port>` and pass the ChromeDriver path. Selenium then connects to the running browser instead of starting a new one.

```python
# pip install selenium requests
import os, requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

API = "http://127.0.0.1:10108/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['DONUT_API_KEY']}"}
PROFILE_ID = "<profile id>"

run = requests.post(f"{API}/profiles/{PROFILE_ID}/run", headers=HEADERS,
                    json={"url": "about:blank"}, timeout=120)
run.raise_for_status()
port = run.json()["remote_debugging_port"]

options = Options()
options.debugger_address = f"127.0.0.1:{port}"
driver = webdriver.Chrome(
    service=Service(executable_path="/path/to/chromedriver"),
    options=options,
)
try:
    driver.get("https://example.com")
    print(driver.title)
    print(len(driver.find_elements(By.TAG_NAME, "p")), "paragraphs")
finally:
    driver.quit()  # ends the Selenium session; the profile keeps running
    requests.post(f"{API}/profiles/{PROFILE_ID}/kill", headers=HEADERS, timeout=60)
```

```javascript
// npm install selenium-webdriver
import { Builder, By } from "selenium-webdriver";
import chrome from "selenium-webdriver/chrome.js";

const API = "http://127.0.0.1:10108/v1";
const headers = {
  Authorization: `Bearer ${process.env.DONUT_API_KEY}`,
  "Content-Type": "application/json",
};
const profileId = "<profile id>";

const run = await fetch(`${API}/profiles/${profileId}/run`, {
  method: "POST",
  headers,
  body: JSON.stringify({ url: "about:blank" }),
});
if (!run.ok) throw new Error(`run failed: ${run.status}`);
const { remote_debugging_port: port } = await run.json();

const options = new chrome.Options();
options.debuggerAddress(`127.0.0.1:${port}`);
const driver = await new Builder()
  .forBrowser("chrome")
  .setChromeOptions(options)
  .setChromeService(new chrome.ServiceBuilder("/path/to/chromedriver"))
  .build();

try {
  await driver.get("https://example.com");
  console.log(await driver.getTitle());
  console.log((await driver.findElements(By.css("p"))).length, "paragraphs");
} finally {
  await driver.quit(); // ends the Selenium session; the profile keeps running
  await fetch(`${API}/profiles/${profileId}/kill`, { method: "POST", headers });
}
```

The browser keeps the profile's fingerprint, cookies and proxy. Options that Selenium would pass to a new browser, such as arguments, extensions or a user data directory, do not apply when you attach.

## 5. Stop the profile

`driver.quit()` ends the Selenium session and leaves the browser open. Stop it with `POST /v1/profiles/{id}/kill`, as the scripts above do, or with the stop button in the app.

## Limits

- Wayfern accepts up to 100 automation (CDP) commands a minute. ChromeDriver turns each WebDriver command into one or more CDP commands. When a command goes over the limit, the browser answers "Automation rate limit exceeded (N requests/minute). Retry shortly." and Selenium raises an error with that text. Add waits between steps in long loops.
- When your plan sets an hourly limit on launch requests, `run` and `kill` answer `429` with a `Retry-After` header.
- One profile, one session: run several profiles in parallel with one driver each. See [How to run several browser profiles at once with Playwright](https://donutbrowser.com/docs/guides/run-profiles-in-parallel) for the batch launch route.

## Troubleshooting

| Error | What to do |
| --- | --- |
| "session not created: This version of ChromeDriver only supports Chrome version …" | The ChromeDriver major version differs from Wayfern's. Repeat steps 1 and 2. |
| "cannot connect to chrome at 127.0.0.1:…" | The port is from an old launch, or the profile stopped. Launch it again and use the new port. |
| "Browser automation requires a paid Donut Browser plan." | The browser refuses automation on your plan. Driving a profile needs Pro, Team or Enterprise. |
| "Automation rate limit exceeded (N requests/minute). Retry shortly." | Slow the script down, then retry the command. |
| `409` from `run` | A teammate holds the profile, or it runs on a remote machine. |
| `400` from `run` | The profile was made for another operating system, or the `url` is not `http`, `https` or `about:blank`. |

## Related

- [Wayfern and Playwright](https://donutbrowser.com/docs/wayfern): the same connection with Playwright or Puppeteer.
- [API reference: Launch a profile](https://donutbrowser.com/docs/rest-api#run-profile)
- [How to run several browser profiles at once with Playwright](https://donutbrowser.com/docs/guides/run-profiles-in-parallel)
- [How to create many browser profiles, each with its own proxy](https://donutbrowser.com/docs/guides/bulk-create-profiles)

Launching profiles for Selenium, Playwright and Puppeteer is part of [Pro](https://donutbrowser.com/pricing).
