guides · seleniumbase

SeleniumBase and Cloudflare Turnstile

SeleniumBase ships two different ways to click a Turnstile checkbox and they have opposite deployment requirements. The uc_gui_* family drives real keyboard and mouse input through PyAutoGUI and raises an exception in headless mode — the check is a named function in the source. The newer solve_captcha() dispatches a CDP mouse event instead and has no such constraint. Most guides describe only the first, which is why so many people conclude it cannot run on a server.

The headless constraint, in the source

This is not folklore. browser_launcher.py contains a function whose only job is to enforce it:

python
def verify_pyautogui_has_a_headed_browser(driver):
    """PyAutoGUI requires a headed browser so that it can
    focus on the correct element when performing actions."""
    if getattr(driver, "_is_hidden", None):
        raise Exception(
            "PyAutoGUI can't be used in headless mode!"
        )

Every uc_gui_* method routes through the installer that calls it first, and _is_hidden is set from headless or headless2. So the rule is precise: true headless raises; a virtual display does not. Xvfb is a headed browser as far as this check is concerned.

The documentation says the same in prose, and adds a second reason worth knowing: "UC Mode is detectable in headless mode. Two: pyautogui doesn't work in headless mode." Running headless is both blocked here and a worse idea generally.

On Linux the documented answer is xvfb=True / --xvfb, which starts a virtual display. The docs also note that on headless Linux you need the SB manager rather than the Driver manager, because SB is what includes that display.

Three methods, three mechanisms

SeleniumBase's own example file annotates the difference, which is the clearest summary available:

python
sb.uc_gui_handle_captcha()  # PyAutoGUI Tabs + Spacebar
sb.uc_gui_click_captcha()   # PyAutoGUI mouse click
sb.solve_captcha()          # CDP Input.dispatchMouseEvent
MethodHow it actsWorks truly headless?
uc_gui_handle_captcha(frame="iframe")PyAutoGUI presses Tab, then SpaceNo — raises
uc_gui_click_captcha(frame="iframe", retry=False, blind=False)PyAutoGUI moves the real mouse and clicksNo — raises
solve_captcha()CDP Input.dispatchMouseEventYes — no PyAutoGUI involved

Note the signature asymmetry, which trips people: uc_gui_click_captcha accepts retry and blind; uc_gui_handle_captcha accepts only frame. And solve_captcha() belongs to CDP Mode — its documented description is "Checks to see if a CAPTCHA is on the current page" and, if found, clicks it with an offset.

The project's own Turnstile examples have been rewritten to call solve_captcha(). If you are following a guide that only mentions uc_gui_click_captcha, it predates that.

What each one actually looks like

python
# The PyAutoGUI path. Needs a headed browser or a virtual display.
from seleniumbase import SB

with SB(uc=True, test=True) as sb:
    url = "https://seleniumbase.io/apps/turnstile"
    sb.uc_open_with_reconnect(url, reconnect_time=2)
    sb.uc_gui_handle_captcha()
    sb.assert_element("img#captcha-success", timeout=3)
python
# The CDP path. No PyAutoGUI, so no headed requirement from that check.
from seleniumbase import SB

with SB(uc=True, test=True, locale="en") as sb:
    sb.activate_cdp_mode()
    sb.goto("https://gitlab.com/users/sign_in")
    sb.sleep(2)
    sb.solve_captcha()
    sb.sleep(2)

uc_open_with_reconnect(url, reconnect_time=…) is the pattern the docs use throughout: it disconnects the driver during page load so the automation is not observable at the moment that matters, then reconnects. That reconnect window is why so many examples carry a sleep — it is not superstition, it is the driver being deliberately absent.

What discussion #2496 actually says

It is the top result for this subject and it is worth reading correctly, because its title is misleading about its own contents. The real title is *"Does cloudflare turnstile can't be bypass from seleniumbase for now . the cloudflare turnstile is sucess but error to find element the code is below"* — and the substance is in the middle of that sentence.

Turnstile passed. The asker's problem was finding a subsequent element afterwards. The maintainer's accepted answer points at the UC Mode examples and adds the operative detail: "Those checkboxes are located in iframes, so you need to switch into them first, carefully." A later reply notes the examples were updated to click through Shadow DOM.

So the thread is not evidence that SeleniumBase cannot pass Turnstile. It is evidence that the widget lives in an iframe inside a shadow root, and that ordinary selectors do not reach into it. That is a real and recurring problem, and it is a different one.

A third party in the same thread asserts that UC mode does not automatically solve Turnstile in web forms. That is a user's claim from February 2024, well before solve_captcha() existed, and it is worth treating as such rather than as documentation.

When to hand the token over instead

All three methods above are automation clicking a real challenge, and Cloudflare states that "Browser automation frameworks, such as Selenium, Puppeteer, Playwright, and Cypress, are not supported for solving production challenges. For automated Turnstile testing, use Turnstile test keys." SeleniumBase is a Selenium framework; it is inside that sentence.

So the decision is really about what you are testing. If the site is yours and the widget is only in the way, the dummy sitekeys remove the challenge from the test entirely and behave identically every run — Cloudflare's own recommendation, and it makes the whole uc_gui versus solve_captcha question moot.

If the challenge genuinely has to clear — staging behind a real widget, synthetic monitoring of your own production — fetching a token and writing it into the field is both simpler and less brittle than clicking, because it does not depend on the widget rendering where a mouse can reach it:

python
import json, os, urllib.request
from seleniumbase import SB

SITEKEY = "0x4AAAAAAA_target"
PAGE = "https://app.example.com/login"

req = urllib.request.Request(
    "https://api.solvegate.io/v1/solve",
    data=json.dumps({"gate": "turnstile", "sitekey": SITEKEY, "url": PAGE}).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['SOLVEGATE_KEY']}",
        "Content-Type": "application/json",
    },
)
with urllib.request.urlopen(req, timeout=30) as res:
    token = json.load(res)["token"]

with SB(uc=True) as sb:
    sb.open(PAGE)
    sb.execute_script(
        'document.querySelector(\'[name="cf-turnstile-response"]\').value = arguments[0];',
        token,
    )
    sb.click("button[type=submit]")

A token typically returns in under 1.5 seconds. Prepaid credits run from $0.40 per 1,000 down to $0.075 at volume; failed solves are never billed; the first 1,000 are free.

Common questions

Because it drives a real mouse through PyAutoGUI, and SeleniumBase enforces that with a function called verify_pyautogui_has_a_headed_browser that raises "PyAutoGUI can't be used in headless mode!". A virtual display such as Xvfb satisfies it; true headless does not.

Yes, two ways. Use xvfb=True for a virtual display, which makes the PyAutoGUI methods work, or use CDP Mode's solve_captcha(), which dispatches a CDP mouse event and never touches PyAutoGUI. Guides that say it is impossible headless are describing only the uc_gui family.

handle presses Tab then Space through PyAutoGUI; click moves the real mouse to the coordinates and clicks. Their signatures differ too — click takes retry and blind, handle takes only frame. Both require a headed browser or a virtual display.

No, and it is worth reading past the title. The asker states Turnstile succeeded and they then could not find a later element. The maintainer's answer is about iframes and Shadow DOM: the checkbox lives inside an iframe in a shadow root, so ordinary selectors do not reach it.

It depends what you are testing. Clicking depends on the widget rendering where an input can reach it, which is the fragile part in CI. Injecting a token skips that, but the token has to come from somewhere with a browser. If the site is yours and the widget is only in the way, Cloudflare's test keys are better than either.

Related

Sources

More in Guides

Playwright and Cloudflare TurnstileStop Cloudflare Turnstile breaking Playwright tests: use Cloudflare's official test sitekeys on staging, or solve a real widget and inject the token.Python Cloudflare Turnstile SolverSolve Cloudflare Turnstile from Python with plain requests or the solvegate SDK. Complete runnable code, async usage, retries, and error handling.Node.js Turnstile solverSolve Cloudflare Turnstile from Node.js: a zero-dependency global fetch version, the solvegate npm SDK, async/await error handling and TypeScript types.How to find a Turnstile sitekey on a pageFind a Cloudflare Turnstile sitekey in seconds: the data-sitekey attribute, keys starting 0x4, explicit-render calls, and Playwright/Node code that extracts it.How to get the cf-turnstile-response tokenWhat the cf-turnstile-response token is, the hidden input it lives in, how siteverify validates it, and how to get one in your own automated tests.Puppeteer Turnstile bypassHow to handle Cloudflare Turnstile in Puppeteer: Cloudflare's official test sitekeys for pages you own, plus a runnable token-injection script.Selenium and Cloudflare Turnstile in PythonHandle Cloudflare Turnstile in Selenium 4 and Python: test sitekeys, explicit waits, execute_script token injection, and what changes in headless.Turnstile fails on a datacenter IPThe same code passes on your laptop and loops forever on a server. What Cloudflare documents about IP reputation, what it does not, and what actually changes.curl_cffi and Turnstilecurl_cffi impersonates TLS and HTTP/2 fingerprints and has no JavaScript runtime. Its own FAQ says so. What that solves, what it cannot, and where the line is.Scrapy and TurnstileScrapy has no JavaScript engine, so a Turnstile page returns markup and no data. The three documented routes, and which one Scrapy's own docs recommend first.undetected-chromedriver and TurnstileIt binary-patches one string out of chromedriver. Its own README says it does not hide your IP and that headless is unfinished. What that means for Turnstile.Camoufox and TurnstileCamoufox is a Firefox fork that patches fingerprints at the C++ level. It ships no Turnstile solver, and the issue asking for one was closed as not planned.nodriver and Turnstilenodriver's README documents a tab.cf_verify() that clicks the Cloudflare checkbox. It is not in the shipped code. What the library does do instead.Patchright and TurnstilePatchright is a drop-in Playwright that closes the Runtime.enable leak and reaches into closed shadow roots — which is exactly where a Turnstile checkbox lives.DrissionPage and TurnstileDrissionPage reaches into non-open shadow roots and switches between browser and HTTP mode. It also forbids commercial use, and its docs are Chinese-only.Go, Colly and TurnstileColly has no JavaScript engine — its dependency graph proves it. What that means for Turnstile in Go, and how chromedp and rod compare when you need a browser.

Automating a gate you own or are authorised to test?

// SolveGate clears Cloudflare Turnstile and WAF challenges through one REST call · first 1,000 solves free