Guide
How to connect Selenium to a Donut Browser profile.
On this page
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 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:
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 155The 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 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.
# 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.
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.
# 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)// 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,
runandkillanswer429with aRetry-Afterheader. - One profile, one session: run several profiles in parallel with one driver each. See How to run several browser profiles at once with Playwright 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: the same connection with Playwright or Puppeteer.
- API reference: Launch a profile
- How to run several browser profiles at once with Playwright
- How to create many browser profiles, each with its own proxy
Launching profiles for Selenium, Playwright and Puppeteer is part of Pro.
Last updated .