SeleniumBase: UC Mode, CDP Mode, proxies and hCaptcha.
SeleniumBase is a Python framework built on Selenium, with a pytest plugin and two modes aimed at bot detection. How to install it, write a first script, use UC Mode, CDP Mode and a proxy with a login, what its CAPTCHA helpers cover, and how to get an hCaptcha token into a SeleniumBase run.
What SeleniumBase is
Its PyPI
summary reads: “SeleniumBase is a framework for web crawling, scraping, and
testing. Supports pytest. CDP Mode adds stealth. Includes many tools.” In
practice it wraps Selenium in shorter calls such as sb.type()
and sb.assert_text() (the README says the assert methods “wait
up to 7 seconds for the expected condition”), downloads the driver for you,
and runs scripts either on their own or as pytest tests.
The GitHub repository
also ships a CLI and an MCP server.
| Package | Maintainer | Latest version | Licence and requirements |
|---|---|---|---|
seleniumbase (PyPI) | Michael Mintz, mdmintz on GitHub | 4.56.5, released 29 October 2026 | MIT. Python 3.10+. Installs Selenium 4.51.0 and pytest |
On GitHub since 2014, it is actively maintained: 98 releases so far in 2026, nine of them in October. Its issue tracker is open (14 open issues and 13,222 stars when we looked). Nearly every commit is Michael Mintz’s: 10,394 of them, against five for the next contributor.
Install it
pip install seleniumbase
The install pulls in a pinned Selenium (4.51.0 for SeleniumBase 4.56.5) and
pytest. The browser is not included. Google Chrome is the default, and the
README says “Only unbranded Chromium and Chrome-for-Testing get downloaded
automatically if not already present on the system.” The driver is
downloaded for you: on our first run with Chrome 156.0.8078.79 it printed
Getting chromedriver 156.0.8078.80
and saved it inside the package folder.
Quick start: the SB() context manager
SB() opens a browser, hands you an sb object with
every SeleniumBase method, and closes the browser when the block ends:
from seleniumbase import SB
with SB(headless=True) as sb:
sb.open("https://seleniumbase.io/demo_page")
sb.assert_text("Demo Page", "h1")
print(sb.get_title())
We ran it on 2 November 2026 with SeleniumBase 4.56.5, Python 3.13 and
Chrome 156 on macOS; it printed Web Testing Page. Drop
headless=True to watch the browser.
In this default mode SeleniumBase is ordinary Selenium underneath:
navigator.webdriver read true and the headless
user agent said HeadlessChrome.
The same test with BaseCase and pytest
For test suites, subclass BaseCase and run the file with
pytest. The BaseCase.main() line also lets you run it with
plain python:
from seleniumbase import BaseCase
BaseCase.main(__name__, __file__)
class ExampleTest(BaseCase):
def test_example(self):
self.open("https://seleniumbase.io/demo_page")
self.assert_text("Demo Page", "h1") pytest test_example.py --headless
It passed in our run. Options such as --headless,
--uc and --proxy= go on the pytest command line;
in SB() they are keyword arguments.
UC Mode
The UC Mode docs
say it “makes bots appear human, which lets them evade detection from
anti-bot services that try to block them or trigger CAPTCHAs on various
websites”. It “was originally based on undetected-chromedriver” and adds
“improved stealth, better reliability, automatic driver downloads, and
special methods for bypassing CAPTCHAs.” You turn it on with
uc=True, or --uc under
pytest:
from seleniumbase import SB
with SB(uc=True) as sb:
sb.goto("https://seleniumbase.io/demo_page")
print(sb.execute_script("return navigator.webdriver")) # False
sb.solve_captcha() # does nothing if there is no CAPTCHA
Run headless, this printed False, and the user agent had no
HeadlessChrome in it. One behaviour to know about: in 4.56.5,
opening a URL from UC Mode switches the session into CDP Mode, and
WebDriver is disconnected afterwards (sb.is_connected()
returned False). The source logs “Opening a URL from UC Mode now
activates CDP Mode. To prevent this, call sb.driver.get(url)
instead.”
The docs also warn that “UC Mode may be detectable in headless mode” and
that “pyautogui doesn’t work in headless mode”, which
rules out the uc_gui_* helpers there. On Linux servers they
recommend xvfb=True, a virtual
display, instead of headless.
CDP Mode
The CDP Mode docs
describe it as “a stealth mode that uses the Chrome Devtools Protocol” and
set out the difference: UC Mode’s stealth “is based on a modified
chromedriver” that disconnects and reconnects WebDriver “at
strategic times”, while CDP Mode “uses the Chrome DevTools Protocol
directly, which is much better at avoiding bot-detection.” The UC Mode page
points to CDP Mode as its successor. You reach it from UC Mode (as above, or
with sb.activate_cdp_mode(url)), or start it on its own as
Pure CDP Mode, with no WebDriver at all:
from seleniumbase import sb_cdp
sb = sb_cdp.Chrome()
sb.goto("https://seleniumbase.io/demo_page")
print(sb.get_title())
print(sb.evaluate("navigator.webdriver")) # False
sb.quit()
Headless, this printed the page title and False. Pure CDP Mode
has its own, smaller set of methods (goto, click,
type, evaluate, solve_captcha and
more), listed in the CDP Mode docs.
Use a proxy, with or without a login
SeleniumBase takes the proxy as one string, host:port or
user:pass@host:port, and its docs list SOCKS4 and SOCKS5
proxies as well:
from seleniumbase import SB
with SB(uc=True, proxy="user:pass@host:port") as sb:
sb.goto("https://your-site.example/")
Pure CDP Mode takes the same argument
(sb_cdp.Chrome(proxy=...)), and
under pytest it goes on the command line:
pytest test_example.py --proxy=user:pass@host:port
pytest test_example.py --proxy="socks5://host:port"
We checked the login part against a local test proxy that answered
407 to any request without credentials. In plain mode, in UC
Mode and in Pure CDP Mode, Chrome sent the username and password on the
retry and the page loaded through the proxy. That is the main practical
difference from undetected-chromedriver,
whose Chrome flag has no place for a login. The docs mark authenticated
proxies as “Chromium only”.
What its CAPTCHA helpers cover
SeleniumBase advertises CAPTCHA handling. Its README says CDP Mode “makes browser automation undetectable to anti-bot services” and “includes code for bypassing CAPTCHAs”, and that one example script “bypasses BrowserScan, Sannysoft, and Cloudflare bot-detection”. The screenshots under those examples show Cloudflare Turnstile, and the docs also include an invisible reCAPTCHA example. These are SeleniumBase’s claims; we did not test them against Cloudflare or reCAPTCHA.
The helpers themselves are described like this:
-
sb.solve_captcha()“handles CAPTCHAs that aren’t bypassed automatically. (If called when there’s no CAPTCHA on the page, then nothing happens / no-op.)” The CDP Mode method list adds “Uses CDP to click a CAPTCHA”. -
UC Mode’s
uc_gui_click_captcha()anduc_gui_handle_captcha()come with Cloudflare and reCAPTCHA variants (uc_gui_click_cf,uc_gui_click_rc). The docs say they “requirepyautogui”, which needs a display; we did not run them, because we run browsers headless only.
None of the docs we read names hCaptcha. The 4.56.5 source goes slightly
further than the docs: solve_captcha() also looks for an
hCaptcha iframe and clicks its checkbox. On hCaptcha’s
test sitekey, which passes any
click by design, that click filled the response field with the test
key’s dummy token in our headless run. That shows the click lands. It
says nothing about a live sitekey, where hCaptcha decides after the click
whether to show an image challenge. solve_captcha() stops at
the click, has no code for an image challenge, and returns nothing you
could pass on as a token.
Where NoneCap fits
When a page in your SeleniumBase run has hCaptcha, its form only goes
through with a valid token in the
h-captcha-response field, a
P1_ string the site’s server checks with hCaptcha.
NoneCap is an hCaptcha solving API that returns that token. Send it the
page’s sitekey and url; with the
Python SDK
(pip install nonecap),
nc.solve() waits for the result, so there is no polling loop:
import json
import os
from nonecap import NoneCap
nc = NoneCap(api_key=os.environ["NONECAP_KEY"])
def get_token(sb):
solve = nc.solve(
type="hcaptcha",
sitekey=sb.get_attribute("[data-sitekey]", "data-sitekey"),
url=sb.get_current_url(),
user_agent=sb.get_user_agent(),
)
return solve.token Then put the token in the form, call the widget’s callback if the page set one, and submit:
from seleniumbase import SB
FILL_TOKEN = """(() => {
const token = %s;
for (const name of ["h-captcha-response", "g-recaptcha-response"]) {
const el = document.querySelector(`textarea[name="${name}"]`);
if (el) el.value = token;
}
const cb = document.querySelector("[data-callback]")?.getAttribute("data-callback");
if (cb && typeof window[cb] === "function") window[cb](token);
})()"""
with SB(uc=True) as sb:
sb.goto("https://your-site.example/signup")
token = get_token(sb)
sb.execute_script(FILL_TOKEN % json.dumps(token))
sb.click("button[type=submit]")
The token goes into the script with json.dumps because, once
UC Mode has switched to CDP Mode, execute_script takes a
single expression: passing the token as an extra argument raised an error
in our run. The same call works in plain mode, UC Mode and Pure CDP Mode
(there, sb.evaluate). We ran both blocks headless against a
local test page with a fake token in place of the NoneCap call: the
callback fired and the form posted the token in both
h-captcha-response and
g-recaptcha-response.
Sending the browser’s own user_agent is optional; some
sites need it for the token to be accepted. If your browser goes through a
proxy, you can pass the same one to nc.solve() as
proxy
(http://user:pass@host:port) so the
token and the form come from the same IP address. Failed, cancelled and
expired solves are never charged.
The Selenium guide covers sitekeys that
are not in the DOM, invisible and enterprise (rqdata) sitekeys,
and the error types the SDK raises. Every field and status is in the
API reference. NoneCap solves
hCaptcha only; it does not help with reCAPTCHA, Turnstile
or other CAPTCHAs.
Last updated November 2026.
Frequently asked
What is SeleniumBase?
How do I install SeleniumBase?
pip install seleniumbase on Python 3.10 or newer. It brings Selenium and pytest with it and downloads a matching ChromeDriver the first time you run a script. It uses the Google Chrome already on your machine.What is the difference between UC Mode and CDP Mode?
chromedriver” that disconnects and reconnects WebDriver “at strategic times”, while CDP Mode “uses the Chrome DevTools Protocol directly”. The UC Mode docs call CDP Mode its successor. In 4.56.5, opening a URL from UC Mode switches the session into CDP Mode. Pure CDP Mode (sb_cdp.Chrome()) skips WebDriver altogether.How do I use a proxy with SeleniumBase?
SB(uc=True, proxy="user:pass@host:port"), sb_cdp.Chrome(proxy="user:pass@host:port"), or --proxy= on the pytest command line. A username and password are supported. In our test against a local proxy that demanded a login, all three sent the credentials and loaded the page.Does SeleniumBase solve hCaptcha?
solve_captcha() as handling “CAPTCHAs that aren’t bypassed automatically”, and their examples are Cloudflare Turnstile and reCAPTCHA. The 4.56.5 source also clicks an hCaptcha checkbox. It has nothing for the image challenge hCaptcha can show after that click, and it does not hand you a token. When a site asks for one, NoneCap returns it.Does SeleniumBase bypass Cloudflare?
Does UC Mode work headless?
navigator.webdriver read false and the user agent had no HeadlessChrome. The docs warn that “UC Mode may be detectable in headless mode” and that the PyAutoGUI-based methods, such as uc_gui_click_captcha(), need a display. On Linux servers they recommend xvfb=True instead of headless.