undetected-chromedriver (uc) takes a proxy the way Chrome does, through the --proxy-server switch on its ChromeOptions. Pin version_main to the major version of your installed Chrome, and the whole setup is seven lines.
import undetected_chromedriver as uc
options = uc.ChromeOptions()
options.add_argument("--proxy-server=http://203.0.113.10:8080") # host:port, no credentials
driver = uc.Chrome(options=options, version_main=151) # your Chrome major version
driver.get("https://httpbin.org/ip")
print(driver.find_element("tag name", "body").text)
driver.quit()
We ran this on 2 September 2026 with undetected-chromedriver 3.5.5, Selenium 4.48.0 and Chrome 151 on Windows 11. Through a public entry from our free proxy list, httpbin.org answered with the address of the proxy 0.42 seconds after driver.get. The figure shows what the same flag does once the proxy wants a password, and the fix that got through.

A username and password inside that URL do not work, and that is the reason most searches on this topic exist. Chrome refuses the address outright. Without credentials it opens a sign-in dialog that no script can fill. This page measures the flag, the failure and every fix people recommend, on the current versions. Three fixes work today. Four do not, and the page shows the exact error for each.
What you need before you start
- Python 3.10 or newer, because Selenium 4.48.0 lists 3.10 as its floor on PyPI (August 2026).
- Google Chrome installed. uc drives the installed browser and downloads a chromedriver for it.
- The packages:
pip install undetected-chromedriver setuptools. The second one matters on Python 3.12 and newer, and the next section says why. - The major version of your Chrome, from
chrome://version. It goes intoversion_main. - A proxy address without credentials for the first test. Any HTTP entry from our free proxy list will do.
| component | version measured | source |
|---|---|---|
| undetected-chromedriver | 3.5.5 | PyPI, released 17 February 2024, still the latest. |
| selenium | 4.48.0 | PyPI, released 27 August 2026. |
| Google Chrome | 151.0.7922.174 | installed on the test machine. |
| chromedriver | 151.0.7922.138 | downloaded and patched by uc after the version pin. |
| Python | 3.13.7 | Windows 11. |
Why does the first launch fail before any proxy is involved?
A fresh install produced two errors before the proxy flag was even read. Both have one-line fixes.
Error 1: No module named 'distutils'
File "...\undetected_chromedriver\patcher.py", line 4, in <module>
from distutils.version import LooseVersion
ModuleNotFoundError: No module named 'distutils'
Python 3.12 removed the distutils package. The release notes (October 2023) say that "the third-party Setuptools package continues to provide distutils". uc 3.5.5 still imports it, so install setuptools next to uc and the import works. The fix on the master branch of the repository dates from July 2025 and never reached PyPI, where the last release is from February 2024. Installing from git is the other option, and the README documents it.
Error 2: This version of ChromeDriver only supports Chrome version 152
selenium.common.exceptions.SessionNotCreatedException: Message: session not created: cannot connect to chrome at 127.0.0.1:54611
from session not created: This version of ChromeDriver only supports Chrome version 152
Current browser version is 151.0.7922.174
Without a pin, uc downloaded the newest chromedriver, one major version ahead of the installed browser. Pass version_main=151, or whatever chrome://version shows, and uc fetches the matching driver. Our first pinned launch took 6.47 seconds, including the download and the patch. Later launches took 3.6 to 4.2 seconds.
The message at exit
On Windows, uc prints OSError: [WinError 6] from its __del__ method after quit() has already closed the browser. That line is noise. The browser is gone and the temporary profile is deleted.
How do I set an HTTP proxy in undetected-chromedriver?
Step 1: get an address that is alive right now
Public entries die fast, so pull a fresh one at run time instead of pasting one into the script. Our free proxy API returns the current list as plain text.
import requests
pool = requests.get(
"https://hproxy.com/api/proxy-list",
params={"format": "txt", "protocol": "http", "recent": "true", "limit": 40},
timeout=15,
).text.split()
We scanned that list three times on 2 September 2026 by sending one request through each entry. Alive were 22 of 60 entries at 14:04 UTC, 11 of 40 at 14:12, and 3 of 40 at 20:08. Test each entry with a plain HTTP client first. A browser launch costs about four seconds, and a dead entry wastes all of them.
Step 2: pass it with --proxy-server
options = uc.ChromeOptions()
options.add_argument(f"--proxy-server=http://{pool[0]}")
driver = uc.Chrome(options=options, version_main=151)
The Chromium proxy documentation defines the switch. Three rules follow from it and from our runs.
| rule | what it means for uc |
|---|---|
| One value for the whole browser | Every tab and every request of that Chrome uses the proxy, including the background requests Chrome makes on its own. |
| Read once at launch | Changing the proxy means quitting and starting a new driver. |
| Host and port only | Credentials in the value are refused, as the next section shows. |
Accepted schemes are http://, https:// (a proxy that itself speaks TLS), socks4:// and socks5://. A bare host:port means HTTP.
Step 3: check what the target sees
Never trust the flag. Compare the address the target reports with and without the proxy.
import requests
import undetected_chromedriver as uc
direct = requests.get("https://httpbin.org/ip", timeout=15).json()["origin"]
options = uc.ChromeOptions()
options.add_argument("--proxy-server=http://203.0.113.10:8080")
driver = uc.Chrome(options=options, version_main=151)
driver.get("https://httpbin.org/ip")
via = driver.find_element("tag name", "body").text
driver.quit()
print("direct: ", direct)
print("browser:", via)
assert direct not in via, "the browser is not using the proxy"
In our run through the entry 91.134.141.4:3128, the browser reported exactly that address. The first load took 0.42 seconds and the second 0.13 seconds. Our own free proxy list page loaded through it in 0.62 seconds. If the browser shows your own address, the flag did not take. A Stack Overflow question from 2022 shows that outcome, and the check above catches it before the run. Before you trust an entry, drop it into our proxy checker to see its exit address, anonymity and speed from the outside.
Why does a username and password in --proxy-server not work?
Because Chrome ignores them by design. The Chromium proxy documentation says: "Chrome does not implement this, and will not use any credentials embedded in the proxy settings." We measured the three ways of writing the flag against a local proxy that requires Basic authentication.
| what you pass | what Chrome 151 did | measured |
|---|---|---|
http://user:pass@host:port, headed or headless | The error page ERR_NO_SUPPORTED_PROXIES. No request reached the proxy at all. | 0.10 s. |
http://host:port, headed | The proxy answered 407 and Chrome opened the dialog "Sign in. The proxy http://127.0.0.1:18898 requires a username and password." driver.get returned with an empty page. | 0.09 s. |
http://host:port, headless | No dialog and no error page, only a blank document, still blank two seconds later. | 0.08 s. |
The Chromium error list defines ERR_NO_SUPPORTED_PROXIES as "There are no supported proxies in the provided list". A value with credentials is not a proxy Chrome knows how to use, so it never connects. The 407 comes from RFC 9110, which requires the client to "authenticate itself in order to use a proxy for this request". Chrome supports Basic, Digest, Negotiate and NTLM for proxies, and for Basic it asks a person through that dialog. Selenium cannot type into the dialog, so the script waits.
What works for an authenticated proxy today?
We measured every approach the ranking tutorials and the forum threads recommend, on 2 September 2026.
| approach | result | where |
|---|---|---|
Credentials in --proxy-server | ERR_NO_SUPPORTED_PROXIES. | previous section. |
| Local forwarding helper, 60 lines of Python | Works, 0.49 s first load, no dialog. | fix 1. |
| proxy.py as that helper, no code | Works, 0.54 s. | fix 2. |
Second DevTools connection answering Fetch.authRequired | Works headed and headless, 0.46 s. | fix 3. |
uc enable_cdp_events with add_cdp_listener | Never receives the event; our runs hung and were killed. | below. |
Selenium BiDi network.add_auth_handler | Invalid InterceptionId, timeout after 20 s. | below. |
Manifest V3 extension via --load-extension | Ignored on branded Chrome 151; nothing loaded. | below. |
| selenium-wire | Four version pins to start, then NET::ERR_CERT_AUTHORITY_INVALID. | below. |
| IP allowlisting at the provider | No code needed; not measured here. | your provider. |
Fix 1: a local forwarding helper
Chrome talks to a proxy on 127.0.0.1 that needs no password. That helper adds the Proxy-Authorization header of RFC 9110 on the way to the real proxy and pipes bytes in both directions. Save this as forwarder.py.
import asyncio, base64, sys
def parse(target):
creds, _, hostport = target.rpartition("@")
host, _, port = hostport.rpartition(":")
return creds, host, int(port)
async def pipe(reader, writer):
try:
while True:
data = await reader.read(65536)
if not data:
break
writer.write(data)
await writer.drain()
except (ConnectionError, asyncio.CancelledError):
pass
finally:
try:
writer.close()
except Exception:
pass
async def handle(client_reader, client_writer, upstream, auth_header):
head = await client_reader.readuntil(b"\r\n\r\n")
lines = head.decode("latin-1").split("\r\n")
request_line = lines[0]
headers = [l for l in lines[1:] if l and not l.lower().startswith("proxy-authorization:")]
headers.append(auth_header)
new_head = "\r\n".join([request_line, *headers]) + "\r\n\r\n"
try:
up_reader, up_writer = await asyncio.open_connection(*upstream)
except OSError:
client_writer.write(b"HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\n\r\n")
await client_writer.drain()
client_writer.close()
return
up_writer.write(new_head.encode("latin-1"))
await up_writer.drain()
await asyncio.gather(pipe(client_reader, up_writer), pipe(up_reader, client_writer))
async def main():
creds, host, port = parse(sys.argv[1])
listen_port = int(sys.argv[2]) if len(sys.argv) > 2 else 8899
auth_header = "Proxy-Authorization: Basic " + base64.b64encode(creds.encode()).decode()
server = await asyncio.start_server(
lambda r, w: handle(r, w, (host, port), auth_header), "127.0.0.1", listen_port)
print(f"forwarding 127.0.0.1:{listen_port} -> {host}:{port} with Basic auth", flush=True)
async with server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())
Run it with the real proxy, then point uc at the helper.
python forwarder.py user:pass@proxy.example.net:8080 8899
options = uc.ChromeOptions()
options.add_argument("--proxy-server=http://127.0.0.1:8899")
driver = uc.Chrome(options=options, version_main=151)
Measured: httpbin.org/ip loaded in 0.49 seconds, then 0.20 seconds, and a plain http:// target in 0.31 seconds. The authenticating proxy logged 40 requests in that one session, every one with valid credentials and none answered 407. Most of them were not ours. Chrome contacted 24 hosts in a session of about ten seconds, and the update, web store and telemetry hosts of the browser itself made up the bulk. The helper handles CONNECT tunnels for https targets and plain-http requests alike. It does not speak SOCKS.
Fix 2: proxy.py as the helper
pip install proxy.py gives you the same helper as one command. Its pool plugin takes the upstream proxy with credentials.
proxy --port 8899 --plugins proxy.plugin.ProxyPoolPlugin --proxy-pool user:pass@proxy.example.net:8080
Point uc at http://127.0.0.1:8899 as above. In our run a plain request through it came back 200, and the upstream proxy logged the credentials as valid. uc then loaded httpbin.org/ip in 0.54 seconds. The version measured is proxy.py 2.4.10 (PyPI, February 2025).
Fix 3: answer the challenge over the DevTools protocol
The Chrome DevTools Protocol has a Fetch domain built for this. Fetch.enable with handleAuthRequests pauses every request. Fetch.authRequired carries the challenge, and Fetch.continueWithAuth sends the credentials. uc exposes the debugger address, so a second websocket to the page target can run that loop while Selenium drives the page. Save this as cdp_proxy_auth.py; it needs only websocket-client, which Selenium already installs.
import json, threading
import requests
import websocket
class CdpProxyAuth(threading.Thread):
def __init__(self, driver, username, password):
super().__init__(daemon=True)
self.username, self.password = username, password
addr = driver.capabilities["goog:chromeOptions"]["debuggerAddress"]
pages = requests.get(f"http://{addr}/json", timeout=5).json()
self.ws_url = next(p["webSocketDebuggerUrl"] for p in pages if p["type"] == "page")
self.ws = websocket.create_connection(self.ws_url, suppress_origin=True)
self._id = 0
self.seen = []
def send(self, method, params=None):
self._id += 1
self.ws.send(json.dumps({"id": self._id, "method": method, "params": params or {}}))
def start(self):
self.send("Fetch.enable", {"handleAuthRequests": True, "patterns": [{"urlPattern": "*"}]})
super().start()
def run(self):
while True:
try:
msg = json.loads(self.ws.recv())
except Exception:
return
method = msg.get("method")
if method == "Fetch.requestPaused":
self.send("Fetch.continueRequest", {"requestId": msg["params"]["requestId"]})
elif method == "Fetch.authRequired":
challenge = msg["params"]["authChallenge"]
self.seen.append(challenge)
self.send("Fetch.continueWithAuth", {
"requestId": msg["params"]["requestId"],
"authChallengeResponse": {"response": "ProvideCredentials",
"username": self.username, "password": self.password}})
def stop(self):
try:
self.send("Fetch.disable")
self.ws.close()
except Exception:
pass
from cdp_proxy_auth import CdpProxyAuth
driver = uc.Chrome(options=options, version_main=151) # options carry --proxy-server=http://host:port
auth = CdpProxyAuth(driver, "user", "pass")
auth.start()
driver.get("https://httpbin.org/ip")
auth.stop()
Measured: the challenge arrived once per run as source: Proxy, scheme: basic, and httpbin.org/ip loaded in 0.46 seconds headed and 0.44 seconds headless. Two limits apply. Fetch is enabled per page target, so the requests of the browser outside that tab still get 407, which is harmless. A new tab needs its own session.
Why not enable_cdp_events? uc offers uc.Chrome(enable_cdp_events=True) and driver.add_cdp_listener, and several answers use them for authentication. They cannot work here. The listener reads the performance log of chromedriver, and chromedriver only writes Network. and Page. events into that log; the file is performance_logger.cc. A Fetch.authRequired event never arrives, the paused requests are never continued, and our two runs never returned. We killed them after 150 seconds and after ten minutes.
What does not work anymore
The proxy-auth extension. For years the answer was a small extension: chrome.proxy.settings.set for the address and chrome.webRequest.onAuthRequired for the credentials. Both APIs still exist (webRequest, proxy), and Manifest V3 has the webRequestAuthProvider permission for exactly this. The way in is gone. Since Chrome 137 the --load-extension switch is removed in official Chrome builds. The announcement (Chrome Counter Abuse, 4 April 2025) keeps it for Chromium and Chrome for Testing. We built a Manifest V3 version and loaded it three ways on Chrome 151: plain, with --disable-features=DisableLoadExtensionCommandLineSwitch, and headless. In every case the browser reported our own address and the proxy saw no request. The add_extension call of Selenium rides on the same switch. chromedriver unpacks the crx and passes --load-extension itself, per chrome_launcher.cc.
selenium-wire. The library that every 2022 to 2024 tutorial recommends is archived, and its README says it "is no longer being maintained". Its last release, 5.1.0, is from October 2022. On current dependencies the import fails with ModuleNotFoundError: No module named 'blinker._saferef', on Python 3.13 and 3.10 alike. With blinker==1.7.0 it imports on 3.10, but its bundled mitmproxy crashes with AttributeError: 'X509' object has no attribute 'get_extension' and the page shows ERR_CONNECTION_CLOSED. Python 3.13 also needs a setuptools older than 81 for pkg_resources. With pyOpenSSL 23.3.0 and cryptography 41.0.7 as well, it started on both Pythons and sent the credentials. Chrome then answered with NET::ERR_CERT_AUTHORITY_INVALID, because selenium-wire terminates TLS with its own certificate. Four pins for a browser that then has to ignore certificate errors. Use fix 1 instead.
The BiDi authentication handler of Selenium. Selenium 4.48 has options.enable_bidi = True and driver.network.add_auth_handler(user, password). Against our proxy the navigation timed out after 20 seconds, and the handler thread raised WebDriverException: unknown error: Invalid InterceptionId., with uc and with plain Selenium. A control run against the 401 of a site (httpbin.org/basic-auth) failed the same way. The handler did not work on this Selenium and Chrome pair at all.
The successors, for completeness. The author of uc moved on to nodriver, which its documentation calls "the official successor" and which lists authenticated SOCKS5 proxies as a feature. SeleniumBase takes a proxy with credentials as an argument in its UC Mode, and its documentation warns that UC Mode is detectable in headless mode. We did not measure either.
Does SOCKS5 work with undetected-chromedriver?
Yes, without authentication. We measured it against our own SOCKS5 server on 127.0.0.1, because 0 of 40 public socks5 entries on our list were alive that day.
| HTTP proxy | SOCKS5 proxy | |
|---|---|---|
| flag | --proxy-server=http://host:port | --proxy-server=socks5://host:port |
| authentication in Chrome | Basic, Digest, Negotiate, NTLM, through a challenge | none; Chrome offers only the no-authentication method |
| who resolves the hostname | the proxy, from the CONNECT line | the proxy; Chrome sent httpbin.org as a domain name |
| credentials in the URL | ERR_NO_SUPPORTED_PROXIES | ERR_NO_SUPPORTED_PROXIES |
| proxy demands a password | sign-in dialog headed, blank page headless | ERR_SOCKS_CONNECTION_FAILED, raised by driver.get after 0.13 s |
| public entries alive on our list, 2 September 2026 | 22 of 60, 11 of 40, 3 of 40 | 0 of 40, twice |
The no-auth run loaded httpbin.org/ip in 0.44 seconds. The server log showed the browser offering method 0 only and sending the target as a domain name, address type 3. That matches the Chromium document: with SOCKSv5 "name resolution is always done proxy side" and "No authentication methods are supported for SOCKSv5 in Chrome". A SOCKS5 proxy with a username and password therefore needs a helper as well, and the HTTP forwarder above does not speak SOCKS. Ask the provider for its HTTP port, or allowlist your IP on the SOCKS port. Of the 40 dead public entries, 34 failed the TLS certificate check rather than staying silent; our SOCKS5 error guide explains what that looks like.
Does the proxy still work in headless mode?
Yes. uc.Chrome(headless=True) passes --headless=new on Chrome 108 and newer, and through the public entry the headless browser reported the address of the proxy in 0.39 seconds. Two things to know. First, navigator.userAgent still read HeadlessChrome/151.0.0.0 in our run, so the mode is visible to a page that looks. Second, there is no dialog in headless mode, so an authenticating proxy leaves a blank page with no error text at all. Use fix 1, 2 or 3; fix 3 was measured headless. The Chrome headless documentation (October 2024) describes the new mode as the full browser without a window.
How do I keep some hosts off the proxy?
--proxy-bypass-list takes "a semicolon or comma separated list of bypass rules", per the Chromium document. We combined it with the helper chain.
options.add_argument("--proxy-server=http://127.0.0.1:8899")
options.add_argument("--proxy-bypass-list=httpbin.org;localhost")
httpbin.org then reported our own address in 0.41 seconds. The proxy log recorded every other host: our checker page and the Google hosts Chrome contacts at start. Budget for that background traffic when a proxy bills by the megabyte. A fresh uc profile made 40 requests to 24 hosts in one short session in our measurement.
Should proxies rotate per request or per session?
Per session, because the browser cannot do anything else. The flag is read once at launch, and every new proxy means a new driver. Our launches cost 3.6 to 4.2 seconds each, so rotating on every request is a bad trade. Rotate per task instead: build the driver, do the work of one identity, quit, next entry.
for entry in pool:
options = uc.ChromeOptions()
options.add_argument(f"--proxy-server=http://{entry}")
driver = uc.Chrome(options=options, version_main=151)
try:
driver.get("https://httpbin.org/ip")
# the work of this identity
finally:
driver.quit()
Switching the exit in the middle of a page would also split one page across two addresses, which our proxies for Selenium page covers. The alternative is one endpoint that rotates on its side. Our residential pool works that way: a single host and port in the flag, and a different exit per session. Put the helper from fix 1 in front of it, because the pool authenticates with a username and password. The code on this page does not change.
What do the errors mean?
Every measured line below comes from our runs on 2 September 2026. The two tunnel errors are quoted from the Chromium error list, because they did not occur in our runs.
| what you see | why | fix |
|---|---|---|
ModuleNotFoundError: No module named 'distutils' | Python 3.12 and newer removed it; uc 3.5.5 imports it | pip install setuptools. |
SessionNotCreatedException: ... only supports Chrome version 152 | uc fetched the newest driver | version_main=<your major>. |
ERR_NO_SUPPORTED_PROXIES | credentials inside the flag, http or socks5 | host:port only, then fix 1, 2 or 3. |
| sign-in dialog, page empty | the proxy answered 407, headed | fix 1, 2 or 3. |
| blank page, no error, headless | the proxy answered 407, no dialog possible | fix 1, 2 or 3. |
ERR_SOCKS_CONNECTION_FAILED | the SOCKS5 proxy wants a password Chrome cannot send | HTTP port with a helper, or IP allowlisting. |
ERR_PROXY_CONNECTION_FAILED | Chrome could not reach the proxy at all | dead entry or wrong port, next entry. |
ERR_TUNNEL_CONNECTION_FAILED | the proxy refused the CONNECT tunnel | the entry does not carry HTTPS, next entry. |
TimeoutException after 20 s, Invalid InterceptionId in a thread | the BiDi auth handler on Selenium 4.48 and Chrome 151 | fix 3. |
the script never returns after Fetch.enable | the uc listener cannot see Fetch events | fix 3. |
No module named 'blinker._saferef' or 'pkg_resources' | selenium-wire on current blinker and setuptools | fix 1. |
NET::ERR_CERT_AUTHORITY_INVALID | selenium-wire re-signs TLS | fix 1. |
OSError: [WinError 6] at exit | uc __del__ on Windows after quit() | ignore. |
| the browser shows your own address | the flag did not apply, or the bypass list covers the host | Step 3. |
The net:: family in headless frameworks has its own page, ERR_PROXY_CONNECTION_FAILED in Puppeteer, Playwright and Selenium. The challenge itself is covered in how to fix 407 Proxy Authentication Required.
How does this differ from plain Selenium?
On the proxy path, barely. We ran plain Selenium 4.48.0 with the same flag and the same public entry in the same minute.
| undetected-chromedriver 3.5.5 | Selenium 4.48.0 | |
|---|---|---|
--proxy-server | works, exit equal to the proxy | works, exit equal to the proxy |
| credentials in the flag | refused by Chrome, before any driver is involved | not measured separately; the switch belongs to Chrome |
navigator.webdriver | false | true |
| driver binary | downloaded and patched by uc, needs version_main | Selenium Manager, automatic |
| start-up in our run | 3.75 s | 2.18 s |
| headless user agent | HeadlessChrome/151.0.0.0 | not measured |
BiDi add_auth_handler | fails, Invalid InterceptionId | fails, the same error |
extension via --load-extension | ignored on branded Chrome 137 and newer | the same switch, per chromedriver source |
A proxy changes the address a site sees and nothing else. The README of uc states it in capitals: the package does not hide your IP address, and a datacenter address is likely to fail. Fingerprint and behaviour are outside this page. Our proxies for Selenium page covers which proxy type fits which target. The Selenium proxy tutorial is the sibling of this page for plain Chrome and Firefox.
Where to go from here
Free proxies for Selenium covers what a public entry can and cannot carry in a browser. Pull entries from the free proxy list at run time and let the loop discard the dead ones. For production, our residential proxies give one endpoint and fresh exits, and the API documentation covers key creation and plan generation without a dashboard.
Sources
- undetected-chromedriver README (ultrafunkamsterdam, GitHub; README last changed 12 June 2023, repository last commit 5 July 2025). What the package does, the IP statement, the git install line, the issue tracker notice.
- Release histories of undetected-chromedriver, selenium, selenium-wire, proxy.py and nodriver (PyPI, read 2 September 2026).
- Proxy support in Chrome, net/docs/proxy.md (The Chromium Authors, read at HEAD on 2 September 2026). --proxy-server, --proxy-bypass-list, embedded credentials, SOCKSv5.
- net/base/net_error_list.h (The Chromium Authors, read at HEAD on 2 September 2026). Error definitions.
- chromedriver chrome_launcher.cc and performance_logger.cc (The Chromium Authors, read at HEAD on 2 September 2026). Extension loading; performance log domains.
- PSA: Removing --load-extension flag in Chrome branded builds (Richard Chen, Chrome Counter Abuse, chromium-extensions group, 4 April 2025).
- chrome.webRequest and chrome.proxy API references (Chrome for Developers, read 2 September 2026).
- Chrome DevTools Protocol, Fetch domain (tip-of-tree, read 2 September 2026).
- RFC 9110, HTTP Semantics (IETF, June 2022). Sections 11.7 and 15.5.8.
- Selenium documentation, Browser options and Chrome-specific functionality (Selenium project, read 2 September 2026); selenium 4.48.0 source, bidi/network.py.
- selenium-wire README (Will Keeling, GitHub, repository archived; last release October 2022).
- What's New In Python 3.12 (Python Software Foundation, October 2023). The distutils removal.
- blinker CHANGES, version 1.8.0 (Pallets Eco, 27 April 2024).
- Chrome Headless mode (Chrome for Developers, updated 21 October 2024).
- nodriver documentation (ultrafunkamsterdam, read 2 September 2026) and SeleniumBase UC Mode documentation (SeleniumBase, read 2 September 2026).
- Our own measurements on 2 September 2026: undetected-chromedriver 3.5.5, Selenium 4.48.0, Chrome 151.0.7922.174, Python 3.13.7 and 3.10.11 on Windows 11, against public entries from hproxy.com/free-proxy-list and our own proxies on 127.0.0.1.