ScreenshotNeo

BlogHow-to

How to Bypass Cloudflare Turnstile with Browser Automation

Test your own Turnstile integration reliably with Cloudflare’s test keys, without trying to solve production challenges in browser automation.

By the ScreenshotNeo team4 October 202611 min read

Short answer: If you own the application, use Cloudflare Turnstile’s test sitekey and matching test secret in a non-production environment. Exercise success, failure, and interactive flows through your normal form and server-side validation. Do not make automated tests solve production challenges: Cloudflare says Selenium, Puppeteer, Playwright, and Cypress are not supported for solving them. Cloudflare’s supported browsers guidance explains that limit.

This guide is for authorized testing of an application you control. It covers deterministic end-to-end tests, server validation, configuration safeguards, and what to do when Turnstile changes a test outcome.

1. Use test credentials instead of solving production challenges

Browser automation can be detected as bot traffic, so a test that depends on a production challenge behaving a particular way can be unreliable. Cloudflare provides test credentials for controlled outcomes. Use a test sitekey in the browser and its matching test secret on the server; do not mix test tokens with production secrets. Cloudflare notes that production secrets reject dummy test tokens.

The sitekey is a public identifier used to render the widget. The secret key is private and belongs on the server, where it is used to validate tokens. Never put the secret in browser JavaScript, HTML, a mobile client, or a checked-in test fixture.

Cloudflare’s testing documentation lists these test sitekeys. Recheck that page when publishing or updating a test suite, since credentials and supported behavior can change:

Scenario Test sitekey Widget behavior
Visible, passes 1x00000000000000000000AA Visible widget; always passes
Visible, fails 2x00000000000000000000AB Visible widget; always fails
Invisible, passes 1x00000000000000000000BB Invisible widget; always passes
Invisible, fails 2x00000000000000000000BB Invisible widget; always fails
Interactive challenge 3x00000000000000000000FF Visible widget; forces an interactive challenge

The testing page also provides matching test secret keys for pass, failure, and already-spent-token validation behavior. Select the matching secret for the scenario and consult the current Cloudflare page for its exact value; this article intentionally does not reproduce secrets.

2. Separate test and production configuration

Use environment-specific configuration so the test sitekey and secret are selected only in a test deployment. Keep production credentials in the production secret manager or environment settings. Cloudflare’s E2E testing tutorial describes environment detection, separate credentials, and deployment checks.

  1. Create a non-production Turnstile configuration and record its test sitekey and corresponding test secret.
  2. Set the test sitekey in the test application’s server-provided configuration so the page renders the intended widget.
  3. Set the corresponding secret only in the test server environment.
  4. Set the production sitekey and secret separately in production configuration.
  5. Add a deployment check that fails if a test sitekey or test secret is selected for a production release.
  6. Run the end-to-end suite against the non-production deployment and verify the effective key pair in deployment configuration, without printing secret values to logs.

For example, expose the public sitekey to the page through a server-rendered setting such as TURNSTILE_SITEKEY, and read TURNSTILE_SECRET only in server code. The variable names are application choices; their separation is the important part.

# Test deployment only
TURNSTILE_SITEKEY=1x00000000000000000000AA
TURNSTILE_SECRET=YOUR_MATCHING_CLOUDFLARE_TEST_SECRET

# Production deployment: configure the actual production pair in the
# deployment's secret manager. Do not copy the test values here.

A simple release guard can check the public sitekey without exposing the secret:

if [ "$APP_ENV" = "production" ] && [[ "$TURNSTILE_SITEKEY" == 1x00000000000000000000AA || "$TURNSTILE_SITEKEY" == 2x00000000000000000000AB || "$TURNSTILE_SITEKEY" == 1x00000000000000000000BB || "$TURNSTILE_SITEKEY" == 2x00000000000000000000BB || "$TURNSTILE_SITEKEY" == 3x00000000000000000000FF ]]; then
  echo "Refusing production deployment with a Turnstile test sitekey" >&2
  exit 1
fi

Maintain this guard against the current official test-key list. A deployment review should also confirm that the production server secret is set from the production secret store and is not the test secret.

3. Render the widget in the application’s normal form

The test should follow the same application path as a real submission: render the widget, submit the resulting token with the form, validate it on the server, then accept or reject the form according to validation. The markup below shows the essential relationship; adapt action URLs and framework details to your application.

<form method="post" action="/signup">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <div class="cf-turnstile" data-sitekey="YOUR_TEST_SITEKEY"></div>
  <button type="submit">Create account</button>
</form>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

In a real application, substitute the sitekey from environment-specific configuration for YOUR_TEST_SITEKEY. The widget provides a token in the browser. The server must send that token to Cloudflare’s Siteverify API; displaying the widget alone does not complete server-side protection.

4. Validate the token on the server

Use your server framework’s HTTP client to post the secret and token to Cloudflare Siteverify. Keep the secret server-side and treat a missing token, an unsuccessful response, or a verification request error as a rejected submission. Do not trust a client-side success state alone.

Here is a runnable Python example using Flask and Requests. Install dependencies with python -m pip install Flask requests, set TURNSTILE_SECRET in the server environment, then run it with flask --app app run. The form page can render the widget markup from the previous section with its configured sitekey.

import os

import requests
from flask import Flask, request

app = Flask(__name__)
SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"

@app.post("/signup")
def signup():
    token = request.form.get("cf-turnstile-response", "")
    secret = os.environ.get("TURNSTILE_SECRET")
    if not secret:
        return "Turnstile server configuration is missing", 500
    if not token:
        return "Turnstile verification is required", 400

    try:
        response = requests.post(
            SITEVERIFY_URL,
            data={"secret": secret, "response": token},
            timeout=10,
        )
        response.raise_for_status()
        result = response.json()
    except (requests.RequestException, ValueError):
        app.logger.exception("Turnstile Siteverify request failed")
        return "Verification is temporarily unavailable; please retry", 502

    if not result.get("success"):
        # Log error codes for diagnosis, but never log the secret or token.
        app.logger.info("Turnstile rejected submission: %s", result.get("error-codes", []))
        return "Turnstile verification failed; please try again", 400

    # Continue the normal account-creation flow here.
    return "Signup accepted", 200

The Siteverify request and response fields are documented in Cloudflare’s server-side validation guide. Add your application’s normal CSRF protection, rate limiting, input validation, and account-creation logic; Turnstile does not replace those controls.

5. Write deterministic browser automation tests

Use the pass test key to cover a successful form flow. Use a failure key to assert that the application does not perform the protected action. Add server-level tests for missing, invalid, expired, and already-used tokens. For the interactive key, verify the user-facing challenge path in a supported test context instead of making a production challenge part of CI.

For Playwright, a test can submit the ordinary form and check the application result. The following JavaScript test assumes the non-production app is available at BASE_URL and is configured with the always-pass test key and matching test secret:

import { test, expect } from "@playwright/test";

test("accepts a form with Turnstile test pass credentials", async ({ page }) => {
  await page.goto(`${process.env.BASE_URL}/signup`);
  await page.getByLabel("Email").fill("test@example.com");
  await page.getByRole("button", { name: "Create account" }).click();
  await expect(page.getByText("Signup accepted")).toBeVisible();
});

For an always-fail configuration, use a separate test deployment or environment setting, then assert that the protected action is rejected:

import { test, expect } from "@playwright/test";

test("rejects a form with Turnstile test failure credentials", async ({ page }) => {
  await page.goto(`${process.env.BASE_URL}/signup`);
  await page.getByLabel("Email").fill("test@example.com");
  await page.getByRole("button", { name: "Create account" }).click();
  await expect(page.getByText("Turnstile verification failed; please try again")).toBeVisible();
});

These are application-flow examples, not instructions for defeating a production challenge. Ensure the relevant test deployment actually uses the key pair for the expected outcome; a test assertion cannot make mismatched credentials deterministic.

6. Choose widget modes and test the paths they create

Cloudflare documents Managed, Non-interactive, and Invisible widget types. They differ in how interaction is presented. The right choice depends on the application’s user experience and configuration; no type is universally best. The test suite should cover the visible result and the server’s response in the modes you deploy.

Widget type What to verify Useful test scenario
Managed The managed experience renders and the form handles completion or failure. Pass and fail keys; check that the form’s normal submit behavior is preserved.
Non-interactive The verification presentation does not require the same explicit interaction as a challenge flow, while server validation still gates the action. Pass and fail keys; assert both accepted and rejected server outcomes.
Invisible The form works when no visible widget occupies the page, including its loading and error states. Invisible pass and fail test keys.

For any mode, test that a failed verification leaves the user with a recoverable message and a way to retry. Avoid assertions that depend on a particular production challenge appearance or on automation being classified as a human.

7. Account for token expiry, replay, and validation edge cases

  • Tokens expire after 300 seconds. Cloudflare says a token is valid for five minutes after generation.
  • Tokens can be validated only once. Reusing a token is a replay and is rejected.
  • Issue a fresh token for a retry. If form submission or the Siteverify request fails, do not blindly resubmit the same token; let the widget generate a new one through its supported flow.
  • Validate server-side on every protected submission. Do not accept a token based only on its presence or on a browser event.
  • Handle upstream errors separately from a user’s failed challenge. A Siteverify network timeout is an availability error; a response with unsuccessful validation is a rejected verification. Keep both from reaching the protected action.
  • Do not log secrets or full tokens. Log a request correlation ID and relevant error codes instead.

Cloudflare’s validation documentation describes token verification and its constraints. Treat its current details as authoritative if they differ from this summary.

8. Troubleshoot common failures

Symptom Likely cause Fix
Automation hangs waiting for a production challenge The browser automation framework is not supported for solving production challenges, or bot handling changes the page flow. Run the test against a controlled environment with Cloudflare test credentials. Assert your application’s normal success and error paths.
Test token is rejected by Siteverify The test sitekey and secret do not form the matching test pair, or the server is using a production secret. Check the non-production configuration and use the matching test secret documented by Cloudflare.
A test expected to pass, but fails The page rendered a different sitekey than expected, the test server has stale configuration, or the backend uses a different secret. Inspect the rendered public sitekey and deployment settings. Restart or redeploy after changing environment variables; never print the secret to logs.
A second submission fails unexpectedly The token was already validated, expired, or reused after a retry. Obtain a fresh token for each submission and test duplicate-token behavior deliberately with the corresponding test setup.
Widget does not render The script did not load, the sitekey is missing or malformed, or the browser cannot reach the widget resource. Check browser network errors, script inclusion, and the sitekey rendered into the page. Confirm the test sitekey matches the chosen scenario.
Form is accepted even when verification fails The server trusts client-side state or does not gate the protected action on Siteverify success. Move the decision to server code and stop the action unless Siteverify returns success.
Test credentials appear in a production build Environment configuration or build-time substitution selected test values. Fail the release with a deployment guard, audit production settings, and rotate or correct credentials if exposed.
Siteverify request times out Transient network trouble, DNS or egress restrictions, or an overly short client timeout. Check outbound connectivity and server logs. Return a retryable error without accepting the protected action; retry with a fresh widget token where needed.

9. Performance, reliability, and cost

Using test credentials makes end-to-end outcomes deterministic and avoids coupling every CI run to production challenge behavior. Keep the test suite focused: cover the essential pass and fail paths in browser tests, and exercise token edge cases in server-side tests where you can control inputs.

For production reliability, set a reasonable timeout on the Siteverify HTTP request, distinguish upstream unavailability from invalid verification, and fail closed for the protected action. Give the user a clear retry path. Do not retry the same token as though it were reusable: tokens are single-use and expire after five minutes.

Cloudflare’s cited guidance provides no performance benchmark or pricing figure for this testing workflow, so none is stated here. Test credentials are for controlled testing; production traffic still needs its production sitekey and secret and server-side verification.

10. Or skip the browser setup

If the task is to capture a page screenshot rather than test your own Turnstile integration, ScreenshotNeo offers a screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. It is not a way to solve or bypass Turnstile challenges; use Cloudflare’s test credentials for authorized integration tests.

For a screenshot call, see the ScreenshotNeo API documentation. Example using cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Equivalent Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Equivalent Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Responses identify the page verdict and billing status in headers.
  • An MCP server gives AI agents, including Claude and Cursor, tools to take screenshots, get page information, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo: get 1,000 free screenshots a month with no card.

11. Frequently asked questions

Can Playwright solve a production Turnstile challenge?

Cloudflare says browser automation frameworks including Playwright are not supported for solving production challenges. Use documented test credentials for your own integration tests.

Can I use a test sitekey with my production secret?

No. Use the corresponding test sitekey and test secret together in a non-production environment. Cloudflare states that production secrets reject dummy test tokens.

Does a visible widget mean the server has verified the visitor?

No. The application server must send the browser token to Siteverify and make the protected-action decision from the validation result.

How long can I wait before validating a token?

Cloudflare documents a five-minute lifetime, and each token can be validated only once. Validate promptly and obtain a fresh token when retrying.

Do test keys protect a production form?

No. Test keys are for controlled test scenarios. Production must use its own credentials and server-side validation.

Sources