guides · patchright

Patchright and Cloudflare Turnstile

Patchright is Playwright with four specific leaks patched out, and one of them matters more than the rest for Turnstile: it can interact with elements inside closed shadow roots. That is precisely where the Turnstile checkbox lives, and it is why ordinary Playwright selectors cannot reach it. It is also the base that Theyka/Turnstile-Solver, the best-known open-source Turnstile tool, is built on.

What it patches, and why each one matters

The README lists four patches. They are worth going through individually because they are not equally important and the trade-offs are real.

The Runtime.enable leak — the project calls this "the biggest Patch Patchright uses". Enabling the CDP Runtime domain is observable from inside the page, and it is what a lot of automation detection keys on. Patchright avoids it by executing JavaScript in isolated execution contexts instead.

The Console.enable leak — patched by disabling the Console API altogether. Read that carefully, because it has a cost the README states plainly: "This means, console functionality will not work in Patchright." If your test suite asserts on console output, or you debug by reading it, that is gone.

Command flags — --disable-blink-features=AutomationControlled added; --enable-automation, --disable-popup-blocking, --disable-component-update, --disable-default-apps and --disable-extensions removed. The removals are as deliberate as the addition: a browser with extensions disabled and component updates off is itself an unusual browser.

Closed shadow roots — "Patchright is able to interact with elements in Closed Shadow Roots. Just use normal locators and Patchright will do the rest." This is the one that decides whether you can touch a Turnstile widget at all.

Why closed shadow roots are the Turnstile problem

A Turnstile widget is not markup you can select. api.js inserts an iframe served from challenges.cloudflare.com, and the surrounding structure uses a shadow root in closed mode — meaning element.shadowRoot returns null even from your own page's JavaScript. That is the whole point of closed mode.

So the ordinary approaches fail for an ordinary reason. A CSS selector does not descend into a shadow root. page.frame_locator() gets you into the iframe but not past the closed boundary around it. Scripts that try querySelector on the checkbox find nothing, and the usual conclusion — that the widget is defended against clicking — is a level too abstract. It is not reachable in the first place.

Patchright's patch removes that specific obstacle, which is why the drop-in import is often the entire fix people were looking for:

python
# The only change from Playwright is this import.
from patchright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("http://playwright.dev")
    page.screenshot(path=f"example-{p.chromium.name}.png")
    browser.close()
→

Chromium only. The README is explicit: "Patchright only patches CHROMIUM based browsers. Firefox and Webkit are not supported." If you need Firefox, Camoufox is the fork that takes that position.

Theyka/Turnstile-Solver, and what it actually does

It is the best-known open-source Turnstile tool and it is built on Patchright — its requirements.txt is five lines, of which two are patchright and camoufox[geoip], and api_solver.py imports both.

The interesting part is the technique, because it is not what people assume. It does not solve the challenge on the target site. It builds its own local page containing the widget, using the target's sitekey, and harvests the token from that:

python
turnstile_div = (
    f'<div class="cf-turnstile" data-sitekey="{sitekey}"'
    + (f' data-action="{action}"' if action else "")
    + (f' data-cdata="{cdata}"' if cdata else "")
    + "></div>"
)
page_data = self.HTML_TEMPLATE.replace("<!-- cf turnstile -->", turnstile_div)

# …then read the token out of the field the widget fills
turnstile_check = page.input_value("[name=cf-turnstile-response]")

That is the same architecture any hosted solver uses, which is worth understanding whichever route you take: a token is produced by rendering the widget somewhere, and it is the sitekey and page URL that bind it — not where the browser was.

Two things to check before depending on it. The licence badge says CC BY-NC 4.0, which is non-commercial, so it is not a drop-in for a commercial product without resolving that. And the repository's last push is over a year old, which in this ecosystem is a long time.

When to hand the token over instead

Patchright makes the widget reachable. Whether the interaction is accepted is a different question, and Cloudflare's position on the category is that "Browser automation frameworks, such as Selenium, Puppeteer, Playwright, and Cypress, are not supported for solving production challenges." Patchright is Playwright with patches; it sits inside that sentence.

If the target is your own and the widget is only in the way of a test, the dummy sitekeys remove it from the test entirely — Cloudflare's documented recommendation, and the whole reachability problem disappears with it.

If the challenge does have to clear, fetching a token is less fragile than clicking, because it does not depend on the widget rendering somewhere an interaction can land:

python
import json, os, urllib.request
from patchright.sync_api import sync_playwright

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 sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto(PAGE)
    page.eval_on_selector(
        '[name="cf-turnstile-response"]',
        "(el, value) => { el.value = value; "
        "el.dispatchEvent(new Event('input', { bubbles: true })); }",
        token,
    )
    page.click("button[type=submit]")
    browser.close()

Response time varies by target, proxy and retries. 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. Solve only against properties you own or are authorised to test.

Common questions

What does Patchright change that matters for Turnstile?

Four patches, but the decisive one is closed shadow roots: Patchright can interact with elements inside them using normal locators. The Turnstile checkbox lives inside a closed shadow root, which is why ordinary Playwright selectors return nothing — the element is not defended so much as unreachable.

Is Patchright a drop-in replacement for Playwright?

For Chromium, yes — the README calls it that and the only change in the basic example is the import. Firefox and WebKit are explicitly not supported. One behavioural difference to know about: the Console.enable patch works by disabling the Console API entirely, so console functionality does not work.

Should I run Patchright headless?

Its own recommended configuration uses headless=False, on real Chrome via channel="chrome", with a persistent user data directory and no custom user agent or headers. That is the maintainers' best-practice snippet rather than a hard requirement, but it is a clear signal about where the tool is strongest.

Is Theyka/Turnstile-Solver safe to use commercially?

Check the licence first — the badge says CC BY-NC 4.0, which is non-commercial. The repository has also not been pushed to in over a year, which is a long time in this ecosystem. Its technique is worth understanding regardless: it renders the widget on its own local page using the target's sitekey and reads the token out.

Does Patchright make automation supported by Cloudflare?

No. Cloudflare states that browser automation frameworks including Playwright are not supported for solving production challenges, and Patchright is Playwright with patches. What it changes is reachability and a set of specific leaks — a different thing from being an accepted client.

Sources

Next step

Take the next integration step

Start with a sandbox request, then use a live key for targets you own or are authorized to test.

More in Guides