Selenium-Driverless: setup, proxies, licence and hCaptcha.
selenium-driverless is a Python library that drives Chrome
with a Selenium-style API and no ChromeDriver. How to install it so it
still runs in 2026, an async quick start, how it differs from Selenium,
proxies, its licence and maintenance status, and what to do when a site
shows hCaptcha.
What Selenium-Driverless is
Selenium-Driverless was written by Aurin Aegerter (“Steve”), known on
GitHub as kaliiiiiiiiii; the repository now lives at
ttlns/Selenium-Driverless.
Its PyPI page
sums it up as “Undetected selenium without chromedriver usage
(Non-commercial use only!)”. Instead of sending WebDriver commands to a
ChromeDriver binary, it starts Chrome itself and drives it over the
Chrome DevTools Protocol
(CDP), the channel Chrome’s developer tools use. Method names follow
Selenium (get, find_element, By,
execute_script), but the calls are async.
| PyPI release | GitHub master | |
|---|---|---|
| Version | 1.9.4, 22 October 2024 | Unreleased, last commit 30 October 2026 |
| Licence | CC BY-NC-SA 4.0 with a changed definition of “commercial” | MIT, since 22 October 2026 |
| With numpy 2.5 or newer | Clicks and typing fail | Fixed |
| With websockets 14 or newer | Closing a tab fails | Closing a tab fails |
The README
lists what it does: “Use Selenium without chromedriver”,
“Currently passes Cloudflare, Bet365,
Turnstile, and others”, multiple tabs and incognito contexts, proxy
authentication and network interception. It also says its isolated
script contexts let you “execute javascript without getting
detected”. Those are the project’s claims; we did not test them
against any anti-bot service.
Is Selenium-Driverless still maintained?
No. On 20 October 2026 the author added a note at the top of the README: “Out of maintenance. Unfortunately, I am no more able to maintain this project due to uni.” It goes on: “I belive that https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/ has surpassed in terms of capabilities and stability.” Our Patchright guide covers that project. When we read it on 2 November 2026 the repository was archived, so it takes no new issues or pull requests.
The last release on PyPI is 1.9.4, from 22 October 2024, and PyPI classifies it as “Development Status :: 2 - Pre-Alpha”. The GitHub master branch moved on after that: a fix for numpy 2 and a licence change were merged on 22 October 2026, but neither was released to PyPI.
Selenium-Driverless licence
The two copies carry different licences, so check which one you have.
The 1.9.4 package on PyPI includes LICENSE.md, which says the
work “is licensed under a Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International License with an
addition for Section 1(k)”. That addition reads:
Commercial means primarily intended for or directed towards commercial advantage or monetary compensation. A business, project or public agreement with a commercial intent of any kind which profits more than, or equal to 7’000 US-Dollar per month, or any monetary equivalent to that, is not subject to this definition of NonCommercial.
The file goes on: “If you wish to use this project commercially, you can contact the Author for a custom License. This usually includes a fee of around 5-6% based on your current profit.” On its first run, 1.9.4 prints “This project is licenced under "Attribution-NonCommercial-ShareAlike"” to stderr.
On 22 October 2026 the author merged a pull request titled “change
license to MIT license”, after offering in the repository to “change the
license to some OSS license”. The master branch now has an MIT
LICENSE.txt and no longer prints the notice. No release on
PyPI carries that change. If your use is commercial, read both texts and
the repository history, and decide with your own counsel which terms
cover the copy you install. This is not legal advice.
Install Selenium-Driverless
It needs Python 3.8 or newer and Google Chrome installed; the README says
“Chromium not tested”. It pulls in Selenium itself, plus
cdp-socket, numpy and scipy. A
plain pip install selenium-driverless
in November 2026 gets numpy 2.5 and websockets 17, and both break it, so
pin them:
pip install selenium-driverless "numpy<2.5" "websockets<14" - numpy below 2.5. With numpy 2.5.4, every
click()andsend_keys()in our run failed with “ValueError: Both input arrays must be (arrays of) 3-dimensional vectors, but they are 2 and 2 dimensional instead.” numpy 2.4.6 works. - websockets below 14. With websockets 17.4, closing a
tab failed with “AttributeError: 'ClientConnection' object has no
attribute 'open'” inside
cdp-socket. websockets 13.1 works.
To get the numpy fix and the MIT licence, install the master branch instead; it still needs the websockets pin:
pip install "websockets<14" \
https://github.com/ttlns/Selenium-Driverless/archive/refs/heads/master.zip Quick start in Python
import asyncio
from selenium_driverless import webdriver
from selenium_driverless.types.by import By
async def main():
options = webdriver.ChromeOptions()
options.headless = True
async with webdriver.Chrome(options=options) as driver:
await driver.get("https://your-site.example/", wait_load=True)
print(await driver.title)
field = await driver.find_element(
By.CSS_SELECTOR, "input[name=username]", timeout=10
)
await field.send_keys("my-username")
asyncio.run(main())
We ran this on 2 November 2026 with 1.9.4 and the pins above, Python
3.13 and Chrome 156, headless. navigator.webdriver read
False. options.headless = True
adds --headless=new. On the very
first run, the library warned “headless is detectable at first run”, and
the user agent said HeadlessChrome. It then saves a user
agent without “Headless” in its data folder, and later runs used that.
The README also has a synchronous API,
from selenium_driverless.sync import webdriver,
which it describes as “asyncified, bugs are to expect”.
How it differs from Selenium
- No ChromeDriver. Nothing to download or keep in step
with your Chrome version. It finds the installed Chrome itself, or you
set
options.binary_location. - Async. You await calls, and properties too:
await driver.title,await driver.current_url. Useasync with webdriver.Chrome(...)to close the browser. - Waits built in.
find_elementtakes atimeoutand waits for the element, so mostWebDriverWaitcode goes away. - Scripts run in an isolated world.
execute_scriptdefaults tounique_context=True: it sees the DOM, but not the page’s own JavaScript variables and functions, and the page cannot see what your script sets. Passunique_context=Falseto run in the page’s world. - Frames.
switch_to.frame()is deprecated; the README reads an iframe withawait iframe.content_documentand searches inside that. -
get_attributereads properties. It is an alias forget_property, soget_attribute("data-sitekey")returnedNonein our run. For an HTML attribute, callget_dom_attribute(). - Clicks move the pointer.
click()moves the mouse to the element along a path before clicking, unless you passmove_to=False.
The isolated world catches out code ported from Selenium. On our local
test page, which defines a function onSolved:
await driver.execute_script("return typeof onSolved")
# 'undefined'
await driver.execute_script("return typeof onSolved", unique_context=False)
# 'function' Use a proxy with Selenium-Driverless
For a proxy without a login, give Chrome its own flag. In our run every request went through the proxy, with Google Chrome and with Chrome for Testing:
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://203.0.113.10:8080")
For a proxy with a username and password, the library has
driver.set_single_proxy() and
driver.set_auth(). Both work
through a small extension it loads into Chrome with
--load-extension, and Google
announced
that branded Chrome builds ignore that flag from Chrome 137 on; Chromium
and Chrome for Testing still honour it. In our run with Google Chrome
156, set_single_proxy failed with “Couldn’t find mv3
extension within 10 seconds”. The workaround suggested in the
project’s issue #333, this Chrome flag, did not change that:
--disable-features=DisableLoadExtensionCommandLineSwitch Pointed at Chrome for Testing 156 instead, it worked: the proxy asked for a login and the next request carried it.
options = webdriver.ChromeOptions()
options.binary_location = "/path/to/chrome-for-testing/chrome"
async with webdriver.Chrome(options=options) as driver:
await driver.set_single_proxy("http://user:pass@host:port/")
await driver.get("https://your-site.example/") The README also shows a different proxy for each incognito context:
await driver.set_auth("user", "pass", "host:port")
context = await driver.new_context(proxy_server="http://host:port")
await context.get("https://your-site.example/")
Its docstring warns that the context proxy “doesn’t work on
Windows”. In our headless runs new_context() did not finish
with either browser: with Google Chrome it could not find the extension;
with Chrome for Testing it timed out with a proxy and, without one,
could not find the extension after switching it on for incognito. Issue #325, still open when the
repository was archived, reports the same extension error from
new_context on Chrome 135. We did not run it with a visible
window.
What Selenium-Driverless does when a site shows hCaptcha
The README’s list of what it “passes” names Cloudflare, Bet365 and Turnstile; the Turnstile entry links to the project’s dev notes, which hold a single media link under “bypass turnstile” and no code. hCaptcha is not on the list, and the package has no captcha code at all.
How a browser looks is one of several things that decide whether
hCaptcha challenges you;
why am I getting hCaptcha
covers the others, starting with the reputation of your IP address. Once
hCaptcha is on the page, the site accepts the form only with a valid token
in the h-captcha-response field, a
P1_ string its server checks with hCaptcha. Invisible
hCaptcha needs that token too. Selenium-Driverless does not produce one.
Get an hCaptcha token into a Selenium-Driverless session
NoneCap is an hCaptcha solving API. Keep Selenium-Driverless for the rest
of the run, and when a page has hCaptcha, send its sitekey
and url to NoneCap, get a P1_ token back, put
it in the form and submit. The Python SDK
(pip install nonecap) has an async
client, AsyncNoneCap, that fits the same event loop, and
solve() waits for the result:
import os
from nonecap import AsyncNoneCap
from selenium_driverless import webdriver
from selenium_driverless.types.by import By
async def main():
async with webdriver.Chrome() as driver:
await driver.get("https://your-site.example/signup", wait_load=True)
widget = await driver.find_element(By.CSS_SELECTOR, "[data-sitekey]")
async with AsyncNoneCap(api_key=os.environ["NONECAP_KEY"]) as nc:
solve = await nc.solve(
type="hcaptcha",
sitekey=await widget.get_dom_attribute("data-sitekey"),
url=await driver.current_url,
user_agent=await driver.execute_script("return navigator.userAgent"),
)
await fill_and_submit(driver, solve.token)
Then put the token in both response fields, call the widget’s
callback if the page set one with
data-callback, and submit. Run
that script with unique_context=False:
the callback is a function in the page’s own world, which the
default isolated world cannot see.
import asyncio
FILL = """
const token = arguments[0];
for (const name of ["h-captcha-response", "g-recaptcha-response"]) {
const el = document.querySelector(`textarea[name="${name}"]`);
if (el) el.value = token;
}
const box = document.querySelector("[data-callback]");
const cb = box && box.getAttribute("data-callback");
if (cb && typeof window[cb] === "function") window[cb](token);
"""
async def fill_and_submit(driver, token):
await driver.execute_script(FILL, token, unique_context=False)
button = await driver.find_element(By.CSS_SELECTOR, "button[type=submit]")
await button.click()
if __name__ == "__main__":
asyncio.run(main())
We tested both steps against a local test page with an hCaptcha-style
form, a fake token and a stand-in for the SDK call: both fields held the
token, the page’s callback received it, and the form posted it as
h-captcha-response and
g-recaptcha-response. With the
default isolated world, the fields still filled and the form still
posted, but the callback was never called, which breaks pages that act
on it.
Sending the browser’s own user_agent is optional; some
sites need it for the token to be accepted. If the browser goes through a
proxy, you can pass the same one to NoneCap as proxy
(http://user:pass@host:port) so the
token and the form come from the same IP address. A solve that returns no
token is never charged.
Every field, status and error is in the API reference, and the Selenium guide shows the same flow with plain Selenium. NoneCap solves hCaptcha only; it does not help with reCAPTCHA, Turnstile or other CAPTCHAs.
Last updated November 2026.
Frequently asked
What is Selenium-Driverless?
selenium-driverless on PyPI, that drives Google Chrome with a Selenium-like API but without ChromeDriver. It starts Chrome itself and talks to it over the Chrome DevTools Protocol, and its API is async. It was written by Aurin Aegerter (“Steve”, kaliiiiiiiiii on GitHub, now ttlns).Is Selenium-Driverless still maintained?
Can I use Selenium-Driverless commercially?
Why does click() fail with “Both input arrays must be (arrays of) 3-dimensional vectors”?
np.cross on 2D vectors, which numpy 2.5 no longer accepts. In our run it broke click() and send_keys(). Install "numpy<2.5", or the GitHub master branch, which has the fix.How do I use a proxy with Selenium-Driverless?
options.add_argument("--proxy-server=http://host:port"). For a proxy with a login, driver.set_single_proxy("http://user:pass@host:port/") needs the extension the library loads into Chrome. Google Chrome 137 and newer refuse to load it; in our run it worked with Chrome for Testing.Does Selenium-Driverless bypass hCaptcha?
h-captcha-response. NoneCap returns that token.Why can’t execute_script see the page’s functions?
execute_script in an isolated world by default, so the DOM is shared but the page’s own JavaScript globals are not. Pass unique_context=False to run in the page’s world, for example to call an hCaptcha data-callback.