Selenium plus anti-detect: when each vendor's grid actually works
Selenium plus anti-detect: when each vendor’s grid actually works
Most people who start with anti-detect browsers begin clicking through the UI, manually spinning profiles, copying cookies, doing the slow work by hand. that is fine when you have ten accounts. it stops being fine at a hundred. somewhere around two hundred accounts you are spending more time managing browser windows than doing anything productive, and you start looking at automation.
the natural answer is Selenium or Playwright. you have probably written WebDriver code before. the problem is that standard ChromeDriver is trivially detectable, and every serious anti-detect vendor knows this. the question is not whether you can attach Selenium to an anti-detect browser, it is how each vendor exposes that attachment point, what actually leaks through the connection layer, and where things break under production load. i have tested AdsPower, Multilogin X, GoLogin, and Dolphin Anty seriously enough to have opinions. this article is those opinions, with code.
the stakes are real. a leak at the grid layer, even one you did not introduce, can link your accounts together. if you are running affiliate flows, social automation, or anything touching multiple accounts on a platform that has bot detection, a bad automation setup can wipe work you spent months building. this is not a theoretical concern.
background and prior art
the W3C WebDriver specification defines how test automation tools control browsers. ChromeDriver implements this by spawning a Chrome process with remote debugging enabled and injecting a content script that sets navigator.webdriver = true. that single property is what most basic detection scripts check first. you can suppress it with --disable-blink-features=AutomationControlled at launch, but that only removes one signal. there are dozens more: the presence of window.cdc_* properties left by ChromeDriver, the timing signature of programmatic input, the canvas fingerprint of a headless environment, the absence of certain browser extensions that real users have.
anti-detect browsers were built to solve fingerprinting at the browser level rather than the automation layer. they modify the JavaScript APIs that expose hardware characteristics, spoof screen resolution, timezone, WebGL renderer, and AudioContext, and maintain persistent profile state across sessions. the interesting question for operators is whether those spoofs survive the CDP bridge that Selenium sits on top of. in most cases they do, but the connection method matters, and each vendor has made different choices about how to expose their profile runner to external automation clients.
Playwright is increasingly the better tool for new projects, because it speaks the Chrome DevTools Protocol natively and gives you finer control over network interception. but Selenium still dominates in enterprise environments, older codebases, and anywhere people have invested in WebDriver-based test infrastructure. the patterns here apply to both, because all of these vendors ultimately expose a CDP WebSocket endpoint.
the core mechanism
every modern Chromium-based anti-detect browser, when it starts a profile, opens a remote debugging port. the browser listens on a localhost WebSocket endpoint that speaks Chrome DevTools Protocol. Selenium’s ChromeDriver can connect to an existing browser instance instead of spawning a new one, via the debuggerAddress option in ChromeOptions. Playwright uses connect_over_cdp. this is the fundamental connection pattern all of these integrations use.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# instead of spawning chrome, connect to existing debug port
options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Chrome(options=options)
the critical detail: when you use debuggerAddress, ChromeDriver does not launch a new browser process, so it cannot inject its own content scripts or set navigator.webdriver. the anti-detect browser’s own fingerprint stack remains intact. you are controlling an already-running browser that the anti-detect vendor has already configured.
the difference between vendors is how they expose that debug port. some use a fixed port per profile. some assign dynamic ports and tell you the port via a local HTTP API. some expose a WebSocket URL directly in the API response. some require authentication headers. Multilogin X requires a bearer token even for the local API call. this matters for automation design because you need to handle the discovery step before you can hand the session to Selenium.
the other thing that matters is what happens to the ChromeDriver binary. when you use debuggerAddress, Selenium still needs a ChromeDriver binary to act as the WebDriver server. that ChromeDriver version needs to match the Chromium version the anti-detect vendor is running. GoLogin and AdsPower both ship with bundled Chromium builds that are not always on the latest stable channel, and version mismatches cause silent failures that look like the browser is unresponsive but are actually a protocol mismatch.
worked examples
adspower via local api
AdsPower exposes a local REST API on http://local.[adspower](https://www.adspower.com/).net:50325 (you need the hosts entry or use localhost:50325 directly). the start endpoint returns the CDP WebSocket address in the response.
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
PROFILE_ID = "jyfgt1h" # your AdsPower profile user_id
resp = requests.get(
"http://local.adspower.net:50325/api/v1/browser/start",
params={"user_id": PROFILE_ID, "open_tabs": 1}
)
resp.raise_for_status()
data = resp.json()
if data["code"] != 0:
raise RuntimeError(f"AdsPower start failed: {data['msg']}")
# AdsPower returns both a selenium and puppeteer WS URL
cdp_address = data["data"]["ws"]["selenium"]
# format: ws://127.0.0.1:PORT/devtools/browser/UUID
# extract host:port for debuggerAddress
debug_address = cdp_address.replace("ws://", "").split("/")[0]
options = Options()
options.add_experimental_option("debuggerAddress", debug_address)
# use the chromedriver path AdsPower tells you about
chromedriver_path = data["data"]["webdriver"]
service = Service(executable_path=chromedriver_path)
driver = webdriver.Chrome(service=service, options=options)
driver.get("https://example.com")
AdsPower’s response includes the chromedriver path for the profile’s Chromium version, which is the cleanest implementation of any vendor i have tested. the data["data"]["webdriver"] field gives you a direct path to the right ChromeDriver binary. i have run this reliably at about 40 concurrent profiles on a 32-core machine before hitting the per-machine license seat limit. AdsPower’s team plan as of early 2026 caps concurrent active browsers at the number of seats you have purchased, not a software concurrency limit, so scaling is a licensing question more than a technical one.
multilogin x cloud profiles
Multilogin X moved away from the old desktop-app model in 2023. the current product runs profiles in the cloud (they call them “cloud profiles”) with a launcher process on your machine. the local launcher listens on https://launcher.mlx.yt:45001 (note: HTTPS, self-signed cert, so you need verify=False in requests or add their cert to your trust store).
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
MLX_TOKEN = "your_bearer_token"
PROFILE_ID = "your_profile_uuid"
start_resp = requests.get(
f"https://launcher.mlx.yt:45001/api/v2/profile/f/{PROFILE_ID}",
headers={"Authorization": f"Bearer {MLX_TOKEN}"},
verify=False # self-signed cert on launcher
)
start_resp.raise_for_status()
ws_url = start_resp.json()["value"]
# ws_url looks like: ws://127.0.0.1:PORT/devtools/browser/UUID
debug_address = ws_url.replace("ws://", "").split("/")[0]
options = Options()
options.add_experimental_option("debuggerAddress", debug_address)
driver = webdriver.Chrome(options=options)
Multilogin X pricing starts at around $99/month for 100 profiles as of Q1 2026. the cloud sync is genuinely useful for team environments where multiple operators share the same profile pool, but it introduces latency on the profile start step that you need to account for in your timeout logic. i have seen profile starts take anywhere from 2 to 15 seconds depending on whether the profile fingerprint needs to be pulled from their servers. build a retry loop around the start call with exponential backoff, do not assume it is instant.
dolphin anty and gologin: local-first approach
Dolphin Anty runs a local API server on http://localhost:3001. GoLogin’s local runner uses http://localhost:36912. both follow the same pattern: POST to start the profile, get back a WebSocket endpoint.
# Dolphin Anty
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
PROFILE_ID = "your_dolphin_profile_id"
resp = requests.get(
f"http://localhost:3001/v1.0/browser_profiles/{PROFILE_ID}/start",
params={"automation": 1}
)
automation_data = resp.json()["automation"]
ws_endpoint = automation_data["wsEndpoint"]
port = automation_data["port"]
debug_address = f"127.0.0.1:{port}"
options = Options()
options.add_experimental_option("debuggerAddress", debug_address)
driver = webdriver.Chrome(options=options)
# GoLogin
resp = requests.post(
"http://localhost:36912/browser/start-profile",
json={"profileId": "your_profile_id"}
)
data = resp.json()
ws_endpoint = data["wsEndpoint"]
debug_address = ws_endpoint.replace("ws://", "").split("/")[0]
options = Options()
options.add_experimental_option("debuggerAddress", debug_address)
driver = webdriver.Chrome(options=options)
GoLogin’s free tier as of 2026 allows 3 profiles, which is not useful for serious work. their professional tier at $49/month gives 100 profiles. Dolphin Anty has a free tier with 10 profiles, which is enough to test the integration before committing. for multi-account operations at scale, see the comparison work at multiaccountops.com/blog/ which has benchmarks for these vendors under concurrent load.
edge cases and failure modes
chromedriver version mismatch. this is the most common failure mode and the hardest to diagnose because the error message is often misleading. if your system ChromeDriver is version 124 and the anti-detect browser’s bundled Chromium is version 120, the connection will appear to succeed but certain commands will return empty results or time out. always use the chromedriver binary that the anti-detect vendor ships or documents, not your system default. AdsPower tells you the path in the API response. for others, check the Chromium version the vendor ships (usually findable in their changelog or by navigating chrome://version in the browser manually) and download the matching ChromeDriver from googlechromelabs.github.io/chrome-for-testing/.
navigator.webdriver leaking through. this happens when you connect Selenium to an anti-detect profile that does not have the WebDriver flag suppression configured in the profile settings. Multilogin X suppresses it automatically in all Mimic (Chromium-based) profiles. AdsPower suppresses it when you launch with the local API. GoLogin suppresses it when you enable “kernel-level masking” in profile settings, which is not the default on older accounts. check this by running driver.execute_script("return navigator.webdriver") immediately after connection. if it returns True, the spoof is not applied. the fix is profile-level, not code-level: go into the profile settings in the vendor’s UI and enable WebDriver masking explicitly.
proxy assignment race conditions. if you assign the proxy after the browser has already started (some workflows do this via the API), there is a window where the browser can make DNS requests using the host machine’s resolver before the proxy is active. always assign the proxy in the profile configuration before calling the start endpoint, not after. the Selenium WebDriver documentation covers proxy configuration at the driver level, but with anti-detect browsers you want proxy config at the profile level so it applies to the anti-detect browser’s network stack, not just the WebDriver layer. for proxy sourcing and rotation patterns that work with these setups, proxyscraping.org/blog/ has vendor-specific notes on residential proxy compatibility.
session persistence after driver.quit(). calling driver.quit() in standard Selenium kills the browser process. when you are using debuggerAddress, it still kills the browser. this destroys any session state the anti-detect browser was maintaining, including localStorage, IndexedDB, and cookies that had not yet synced to the profile store. the correct teardown sequence is: (1) call driver.close() to close the tab without killing the process, (2) call the vendor’s stop API endpoint to flush state back to the profile store, (3) let the browser shut down cleanly. skipping step 2 means any state changes made during the session may not persist.
# correct teardown
driver.close() # close active tab, not the browser
requests.get(
"http://local.adspower.net:50325/api/v1/browser/stop",
params={"user_id": PROFILE_ID}
)
concurrent profile limits and port exhaustion. each running profile occupies a local port. on Linux the default ephemeral port range is 32768-60999, which gives you roughly 28,000 ports. in practice you will hit the vendor’s seat limit long before port exhaustion, but if you are also running other services on the machine, or if profiles are not being stopped cleanly, you can accumulate zombie processes holding ports. write a cleanup routine that calls the vendor’s stop endpoint for any profile that has been running longer than your expected session duration. i run this as a cron job that checks the AdsPower active profiles list every 15 minutes and terminates anything over 45 minutes old.
what we learned in production
the setup that actually works for us at scale is a thin orchestration layer that treats each anti-detect profile as an ephemeral resource: allocate a profile ID from a pool, call the start API, get the CDP endpoint, hand the endpoint to a worker process running a headless Selenium session, do the work, call the stop API, return the profile ID to the pool. the worker processes do not know about the anti-detect layer at all, they just receive a debuggerAddress string. this separation makes it easy to swap vendors without rewriting automation code.
the lesson that took me the longest to learn is that fingerprint quality and automation quality are separate problems. you can have a perfect fingerprint and still get flagged because your click timing is too regular, your scroll behavior is mechanical, or your inter-page navigation happens faster than human reading speed. MDN’s documentation on the Navigator interface is useful background for understanding what properties are exposed at the browser level, but behavioral signals are harder to address and require injecting artificial timing variation into your automation scripts. i now add randomized delays drawn from a log-normal distribution rather than a uniform distribution, because human behavior has long-tail characteristics that uniform random delays do not replicate.
for anyone building airdrop farming or DeFi multi-account flows, the browser automation layer is only one part of the stack. the profile rotation patterns described at airdropfarming.org/blog/ are directly applicable here, and the proxy discipline required for those flows is the same discipline required for any serious Selenium plus anti-detect setup. the browser fingerprint can be perfect and the automation timing can be human-like, but if multiple profiles share an IP at the same time you have linked them regardless.
one thing worth saying plainly: none of the vendors i have tested have a fully reliable solution for WebGL fingerprinting across all GPU types. AdsPower and Multilogin X are the most consistent. Dolphin Anty has improved considerably since version 5. GoLogin’s WebGL spoofing works on their custom Orbita browser but has had intermittent issues with certain NVIDIA driver versions on Linux. test your specific environment before committing to a vendor. write a fingerprinting test suite using an open-source tool like FingerprintJS’s demo page and run it against your setup before going to production. if the canvas hash changes between sessions on the same profile, something is wrong at the profile configuration level.
browse through our anti-detect browser reviews index to compare vendor scores across all the dimensions that matter for automation use cases specifically. the review methodology there weights API reliability and ChromeDriver compatibility more heavily than the UI polish scores, because for automation operators those are the properties that actually matter.
references and further reading
- W3C WebDriver Level 2 Specification - the protocol standard that ChromeDriver and all WebDriver implementations conform to
- Chrome DevTools Protocol documentation - the underlying protocol that CDP-based connections use, essential reading for understanding what is possible beyond standard WebDriver commands
- Selenium WebDriver documentation: remote WebDriver - official docs on connecting to existing browser instances
- MDN Web Docs: Navigator.webdriver - specification of the property that most detection scripts check first
- Google Chrome for Testing downloads - canonical source for matching ChromeDriver versions to specific Chromium builds, necessary when your anti-detect vendor ships a non-current Chromium
for operational context on running multi-account workflows at scale, see our Multilogin X deep-dive and the AdsPower vs Multilogin comparison on this site.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-05-19.