NEWScrapingAnt MCP for Claude Code, Cursor & Windsurf — try it free →
Skip to main content

Playwright CAPTCHA: Tested Libraries and 1,200 Provider Requests

· 14 min read
Oleg Kulyk
Co-Founder @ ScrapingAnt

Playwright CAPTCHA: Tested Libraries and 1,200 Provider Requests

Updated 2026-09-23

Replaced the old mixed-framework examples, invented solver endpoint and unsupported bypass claims with tested integrations and a reproducible evidence packet. The comparison contains 100 requests per provider/CAPTCHA pair, across six cases and two funded services. Playwright has no built-in stealth mode, and a returned CAPTCHA token is not proof that a protected submission succeeded.

If a CAPTCHA stops your Playwright script, first decide what you need to test. For your own application's automated tests, use provider test keys and verify the acceptance and rejection paths. For a live integration, record both the solver's response and the target server's decision. Clicking a checkbox, receiving HTTP 200, or obtaining a token is not enough.

This guide compares five library approaches and shows how to run the tested examples. The paid comparison contains 1,200 provider requests, including the original 120 observations and 1,080 additional requests. All paid failures remain in the results. The separate introductory four-task experiment is excluded from that denominator.

A CAPTCHA token is an intermediate result​

The measured workflow has four stages: load the fixture, obtain a solution, submit it, and check the server's answer. Record which stage failed so a browser setup problem does not become a solver failure, and a token does not become a claimed successful submission.

Our plain Playwright checkbox controls opened Google's public reCAPTCHA demo in one headed and one headless browser. Both encountered image challenges; neither produced a token or an accepted submission. See the headless capture and headed capture.

The invalid-token control demonstrates why checking only the HTTP status is insufficient. These are selected fields from its captured output:

{
"submission_status": 200,
"server_accepted": false,
"success_message_present": false,
"failure_message_present": true,
"assertion_passed": true
}

The demo returned HTTP 200 and rejected the submission. Later, Anti-Captcha returned a token in one paid v2 trial, but the same demo rejected it. That request remains a failure in the comparison below.

Browser diagnostics answer a different question. The Playwright stealth comparison measures observable browser signals; passing those checks does not establish CAPTCHA acceptance.

Install the tested examples and run without paid credentials​

Use the immutable example directory at commit 17cc17294d21b31a957f42ff75b6dcac8b6fbb1b. Download or check out that revision, then open examples/bypass-captcha-playwright. Run the commands from its README:

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-lock.txt
python -m playwright install chromium
npm ci
./run.sh

The measured environment used Python 3.12.11, Node 22.20.0, Playwright 1.63.0 and Chromium 153.0.8010.12 on macOS 26.6.2 arm64. Default checks also passed in Linux CI. On Linux, the packet documents python -m playwright install --with-deps chromium for browser system dependencies. Windows was not tested; the live runners use POSIX signals.

The default command creates no paid tasks, even if solver keys are present. Its recorded checks passed 21 offline tests, five test-key fixture paths and three SDK preflights. Output goes to run_output/; the dated captures stay in expected_output/. Running the checks also regenerates screenshots.

The five fixture paths cover Google test-key acceptance, a local missing-token rejection, and Turnstile test-key acceptance, forced rejection and forced duplicate-token error. The fixture's backend calls the real verification endpoints when appropriate; the missing-token case is rejected locally. These are integration checks, not production CAPTCHA solves. Google's test secret is deliberately permissive, and the Turnstile duplicate error uses a documented dummy secret rather than replaying a production token.

For an application you control, start with the providers' reCAPTCHA testing guidance or Turnstile testing guidance. The packet includes the complete local fixture server.

For browser setup and navigation basics, see Web Scraping with Playwright.

Which Playwright CAPTCHA libraries were tested?​

Versions below identify the measured packages, not a claim that they will remain the latest releases.

ApproachTested versionWhat was exercisedWhat the evidence supports
2captcha-python2.1.1Synchronous official client, six paid cases, target verification where availableA measured Python integration candidate; results below
anticaptchaofficial1.0.70Synchronous official client, the same six cases and verification criteriaA second measured integration candidate, with retained failures
playwright-recaptcha0.5.1Three bounded audio workflows on Google's public demoNo accepted submission in those workflows
playwright-extra + puppeteer-extra-plugin-recaptcha4.3.6 + 3.6.8Widget discovery and the missing-provider pathOne widget found; paid solving was not tested through this plugin
capsolver1.0.7Installation, import, method signature and credential preflightLive results unavailable because no key was obtained

The package inventory, repository snapshots and initial library observations retain the source and execution details. In particular, the Python provider results do not validate a different Node plugin's solving path.

Run one funded Python integration​

After the default checks pass, supply TWOCAPTCHA_API_KEY or ANTICAPTCHA_API_KEY through your environment or secret manager. Each command below can spend funds on at most one task. Choose the provider you want to exercise; these commands are separate runs, and rerunning one can incur another charge.

python -c 'import subprocess,sys; sys.exit(subprocess.run([sys.executable,"04_2captcha.py","--live","--trial","1"],timeout=400).returncode)'

For Anti-Captcha:

python -c 'import subprocess,sys; sys.exit(subprocess.run([sys.executable,"05_anticaptcha.py","--live","--trial","1"],timeout=400).returncode)'

These are the bounded commands used for the introductory Google-v2 experiment. The complete shared runner checks the fixture URL/sitekey, obtains a proxyless token, sets the demo response field, submits the form and reads the new document. It requires the expected page, HTTP 200 and Verification Success. Both providers had two accepted submissions in that separate four-task experiment.

The six-case runner uses the same separation between acquisition and acceptance. These selected fields from its first 2Captcha v2 record show what an accepted result records:

{
"outcome": "accepted",
"task_create_requests": 1,
"paid_tasks_created": 1,
"solution_delivered": true,
"server_acceptance_tested": true,
"server_accepted": true
}

The complete provider adapters and fixture/verification functions are the code to inspect when adapting this flow. Their selectors and acceptance checks belong to the named demos; they are not a universal form integration.

The pinned SDKs omit HTTP request timeouts. The measured runners add scoped 10-second connect and 30-second read timeouts. An external 400-second child-process timeout supplements the intended signal deadline. Stopping a client does not cancel a task already accepted by a provider. Error captures keep classes/codes rather than raw exception messages or solutions.

What happened with the audio helper and Node plugin?​

The audio helper needs FFmpeg and sends speech transcription to Google's speech-recognition service; it is not a purely local solver. With that prerequisite available, invoke it from the installed packet:

python 02_playwright_recaptcha.py --attempts 1

The recorded headless attempt hit RecaptchaRateLimitError. A headed one-attempt run did not finish the solve. A later headed run with the default five-attempt budget was rate-limited, and testing stopped. Those unequal attempt budgets and a shared host/IP do not support a headed-versus-headless success-rate comparison. See the three captures.

For the Node discovery check:

node 03_extra_recaptcha.cjs

It found one reCAPTCHA widget and returned Please provide a solution provider to the plugin. with zero solutions. That was the expected missing-provider result, not a solved CAPTCHA. Use this evidence to evaluate its discovery plumbing; a funded plugin integration still needs its own test.

CapSolver remains a preflight-only candidate in this packet. Missing credentials are not a failed solve, and no live accuracy, latency or cost is assigned to it.

Results: 100 requests per provider and CAPTCHA case​

CAPTCHA case2CaptchaAnti-Captcha
reCAPTCHA v2 checkbox100/100 accepted99/100 accepted
Invisible reCAPTCHA v2100/100 accepted100/100 accepted
reCAPTCHA v3100/100 accepted100/100 accepted
GeeTest v499/100 accepted86/100 accepted
Synthetic image OCR100/100 correct100/100 correct
Turnstile100/100 tokens; unverified100/100 tokens; unverified

The full comparison and machine-readable summary contain every denominator and failure. The outcome criteria differ:

  • v2 and invisible v2: accepted form submission on Google's demos.
  • v3: a browser-context request to Google's demo verifier, requiring boolean success, the expected hostname/action, and score at least 0.5. All 200 measured scores were 0.9. This was not a full form-click workflow.
  • GeeTest v4: a browser-context request to the 2Captcha-hosted demo backend, requiring its success result. The fixture is hosted by one of the compared vendors.
  • OCR: case-insensitive exact matches on 100 distinct synthetic images. Both providers received the same image bytes for each paired trial. These images are not a representative production CAPTCHA dataset.
  • Turnstile: token delivery only. The selected public fixture had no backend verifier, so server acceptance is unknown.

Do not combine these into one overall success percentage. The failures were one 2Captcha GeeTest ERROR_CAPTCHA_UNSOLVABLE, thirteen Anti-Captcha GeeTest ERROR_TOKEN_EXPIRED errors, one Anti-Captcha GeeTest ERROR_CAPTCHA_UNSOLVABLE, and one rejected Anti-Captcha v2 submission. None was replaced with another paid attempt.

A separate GeeTest fixture-setup timeout occurred before any provider request. Its original diagnostic was archived after verifying zero creation requests, zero tasks and no charge or ambiguity. That unmeasured slot was then run serially and accepted. The final dataset contains exactly 1,200 requests and confirmed tasks, plus that one uncharged setup diagnostic.

Recommendation from this experiment: start with 2Captcha when reproducing the measured GeeTest integration, while keeping the vendor-hosted-fixture limitation visible. Both official Python clients are candidates for the other server-verified demos. Validate your own target's acceptance separately before making a production choice. For owned-application automated tests, use test keys rather than this paid matrix.

Latency and cost​

These are SDK call times for returned solutions, including task creation and polling. The columns show median / p95 seconds; the rejected Anti-Captcha v2 token remains in its timing sample. GeeTest has 99 returned solutions for 2Captcha and 86 for Anti-Captcha; every other cell has 100. Failed provider-call durations are reported separately in the full evidence.

CAPTCHA case2Captcha: median / p95 secondsAnti-Captcha: median / p95 seconds
reCAPTCHA v2 checkbox6.13 / 61.2424.91 / 154.61
Invisible reCAPTCHA v22.13 / 62.7434.23 / 139.56
reCAPTCHA v312.06 / 24.3317.50 / 29.12
GeeTest v411.84 / 34.8724.24 / 49.00
Synthetic image OCR10.52 / 12.048.09 / 13.99
Turnstile10.94 / 16.6516.88 / 23.72

The median averages the middle two values for even sample sizes; p95 uses nearest rank. The first ten trials per cell ran with up to four jobs, two per provider. The additional 90 used up to twelve jobs, six per provider, in a later time window on the same submitting host/network; the setup recovery used one job. Provider networks and SDK polling also differ. Pooled timings therefore do not isolate an inherent provider speed difference. The evidence includes separate phase summaries, p90 values and ten-trial blocks.

ProviderKnown receipt subtotalReceipt coverageObserved account decrease
2Captcha$1.43801599/600 requests$1.43801
Anti-Captcha$0.96480586/600 requests$0.96260

Known receipts total $2.40281, covering 1,185 of the 1,200 requests. Observed account decreases total $2.40061. Missing receipts are unknown, not zero-cost tasks. The $0.00220 Anti-Captcha difference remains unreconciled; settlement timing, refunds or other account activity were not established as its cause. These are recorded experiment charges, not a current price quote.

Reproduce the comparison without accidentally repeating paid jobs​

Inspect the plan and rebuild the committed results without creating tasks:

python broad_matrix.py --rounds 100
python summarize_100.py --output run_output/rebuilt.json

The first command is an offline plan. The second reads the committed completed dataset. To run a new funded matrix, follow the explicit live-run instructions. A fresh 100-round output directory can create up to 1,200 paid requests; it does not automatically reuse the published observations.

Use one controller per output directory. The matrix skips existing job records, including failures and uncertain creations. Each trial ID allows at most one creation request. Do not delete a failed record to obtain a more favorable result. The narrow setup-recovery tool only releases a slot with proof that no request was sent, preserving its diagnostic first; it is not a paid retry mechanism.

Inspect both outcome and the relevant acceptance field. A provider error, a rejected submission, an OCR mismatch and a token-only observation mean different things. Default CI checks cover test keys and offline assertions; they do not rerun this funded experiment.

Where ScrapingAnt fits—and where it is unnecessary​

ScrapingAnt is unnecessary for the local test-key fixtures and direct Playwright/provider integrations above. They run without a ScrapingAnt key.

A separate ScrapingAnt browser/datacenter fetch of Google's demo returned API HTTP 200, target HTTP 200 and widget markup. The recorded cost was 10 credits; no response textarea or success message was found, and no protected form was submitted. That observation does not establish automatic CAPTCHA solving.

The verified credit rule is: A request with JavaScript rendering through a datacenter proxy costs 10 API credits. For product documentation, see CAPTCHA and Cloudflare. This guide makes no broader automatic-solving claim from the single fetch.

Limits to carry into your own integration​

This is a dated comparison on named public demos and synthetic images, from one submitting host/network. Fresh browser contexts do not create independent network samples. The providers controlled their own proxyless solver environments. No production-site reliability estimate follows from the counts.

The negative controls are retained in the packet. Immediate v3 replay probes were accepted by Google's demo, while the GeeTest demo rejected both replay probes. These fixture-specific observations do not establish production single-use behavior. Test-key paths intentionally provide controlled outcomes; they are not part of the paid totals.

Keep the stages separate in your implementation: detect the challenge, obtain a solution, submit through the target workflow, and record acceptance or rejection. The useful result is the target's decision, together with enough failure information to reproduce what happened.

For related integrations, see Playwright browser diagnostics and the Puppeteer CAPTCHA guide.

Examples tested on 2026-09-23 with Playwright 1.63.0, 2captcha-python 2.1.1 and anticaptchaofficial 1.0.70. Code: pinned evidence packet.

This article was drafted with AI assistance from a tested evidence packet. The named author is responsible for the code, measurements and corrections.

Forget about getting blocked while scraping the Web

Try out ScrapingAnt Web Scraping API with thousands of proxy servers and an entire headless Chrome cluster