Playwright and CAPTCHA-Protected Sites: A Practical Guide

· All articles

Playwright is the fastest way to automate a modern website, and the fastest way to hit a CAPTCHA: headless Chromium trips the same defenses a human does, only without the patience to solve them. This guide shows the three real routes out, and then builds the one you should be using — solving the challenge through the EndCaptcha API and injecting the token into the page — with code you can copy into a project today.

Why a Playwright script stops at the challenge

A CAPTCHA-protected page does not care that your script is honest. The challenge appears when the site's rules decide the visit looks automated: Playwright's default automation flags, a headless user agent with no plugins, a WebDriver object on window, or simply a login attempt from an address with no history. When the challenge appears it usually lives in its own iframes — reCAPTCHA widgets render inside iframe[title*="reCAPTCHA"] — which is why clicking blindly with page.click() times out: you are clicking the wrapper, not the checkbox.

The practical consequence: your script needs a token, not a click loop.

The three routes, honestly

Clicking through it. Fragile by design. Even when the widget cooperates headless — Cloudflare's documentation test key 3x00000000000000000000FF is the classic trick — the solving services do not accept it: we measured Capsolver rejecting it outright with ERROR_INVALID_TASK_DATA: invalid websiteKey. Test keys prove plumbing, they do not solve production traffic.

Building your own solver. Out of scope for this article and for most teams: it needs a farm, fingerprints and upkeep.

Using a solving API. You submit the challenge details, the service returns a token, you put the token where the page expects it. The rest of this article is that path with the official EndCaptcha client.

Step one: get the token

The Python client ships in the downloads of the API documentation page. For a reCAPTCHA v2 widget you send the site key and the full URL of the page that hosts it:

import json
from endcaptcha import HttpClient

client = HttpClient("YOUR_USERNAME", "YOUR_PASSWORD")

captcha = client.decode(
    type=4,
    token_params=json.dumps({
        "googlekey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
        "pageurl": "https://www.example.com/signup",
    }),
)

token = captcha["text"]

decode() does the upload and the polling for you. If you poll by hand, know the protocol: right after upload the API answers UNSOLVED_YET:/poll/<id> while the job waits in queue, the poll answers ? while a solver is working on it, and the token text when it is solved. Poll no faster than once every three seconds.

For reCAPTCHA v3 the same call takes type=5 plus action and min_score inside the token parameters — see the API reference for the full field list.

Step two: put the token into the page

Form-based sites store the answer in a hidden field before they submit:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://www.example.com/signup")

    page.evaluate(
        """(token) => {
            const field = document.querySelector(
                '[name="g-recaptcha-response"]');
            if (field) {
                field.value = token;
                field.dispatchEvent(new Event('input', {bubbles: true}));
                field.dispatchEvent(new Event('change', {bubbles: true}));
            }
        }""",
        token,
    )

    page.click('button[type="submit"]')
    page.wait_for_url("**/dashboard")
    browser.close()

Sites that submit through fetch() instead of a form need the token on the wire, not in the DOM — intercept the request and add it:

import re

def pass_token(route, token):
    response = route.fetch()
    body = response.text() + "&g-recaptcha-response=" + token
    route.fulfill(response=response, body=body)

page.route(re.compile(r".*/api/signup"), lambda r: pass_token(r, token))

If the widget refuses the injected token (the page verifies a callback), the troubleshooting table below has the two causes worth checking first: wrong pageurl and token reuse.

The same flow in Node.js

The Node client in the zip speaks the identical contract. The example example_recaptchaV3.js that ships in ec_api_v1_1_nodejs.zip is the shape you copy — an object carrying the type and the fields, decode() returning an event emitter:

const HttpClient = require('./lib/endcaptcha.js');
const client = new HttpClient(process.env.EC_USER, process.env.EC_PASS);

const job = {
  type: 4,
  googlekey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
  pageurl: 'https://www.example.com/signup',
};

client.decode(job).on('response', (res) => {
  if (res.error) return console.error(res.error);
  console.log('token:', res.text);
});

Drive the page with page.evaluate() exactly as in the Python version; the injection code does not care which language produced the token. For automation that already lives in Node, type: 5 with action and min_score in the same object covers reCAPTCHA v3.

Choosing between reCAPTCHA v2 and v3

Both arrive as a token you inject; what changes is the conversation with the page. v2 is a widget: the page expects the g-recaptcha-response field to be filled and a checkbox to appear ticked. v3 has no widget: the token rides along with whatever the page submits, and the page scores it — send min_score with your request so the service solves for your threshold instead of accepting anything the site will reject. A site that scores below the line rejects the token at submit time, which looks exactly like an expired token in your logs; check the score before you blame the poll loop.

If the site runs both, solve the type the page actually renders: inspect the widget iframe for v2 (iframe[title*="reCAPTCHA"] or the data-sitekey attribute in the script), and read data-action for v3 before you choose type=4 or type=5.

Proxies and the pageurl rule

Send the full URL of the page that hosts the widget, path included: https://www.example.com/signup, never the domain root. If the solving address and the submitting address differ, pages reject the answer — pass proxy and proxytype inside the token parameters so the solver comes from the same address you submit from. The fields are documented in the Turnstile and Amazon WAF guides, which follow the same shape.

Troubleshooting

Answer from the API What it means What to do
UNSOLVED_YET:/poll/<id> Your job is in queue Keep polling, no faster than every 3 seconds
? A solver is working on it Keep polling; the job expires if abandoned for too long
ERROR:NOT A VALID CAPTCHA A field failed validation Check sitekey/googlekey and the pageurl format
ERROR:UNSUCCESSFUL UPLOAD The upload itself failed Credentials, balance, or a malformed token payload
Token accepted by API, rejected by the page Wrong pageurl, or the token was already used Match the URL exactly; tokens are single-use and expire in about two minutes
Works headless=False, fails headless=True The site fingerprints the browser Keep the same pageurl, add a consistent proxy, avoid changing fingerprints between solve and submit

Questions developers ask

Does this work in headless Chromium? Yes — the token is produced off the browser by the API; only the injection happens in the page.

How long does a solve take? Seconds when the queue is free; poll with the timeout your SLA can afford (the client's decode() caps it for you).

What about reCAPTCHA v3 scores? Send min_score with the request and treat a low score like a failed login, not like an exception — see the v3 type page.

Can I run this inside an existing Playwright fixture? Yes — keep the solve step as a plain function call before page.goto, cache the token only until the form is submitted, and never reuse it: the API returns single-use tokens that expire in about two minutes.

What if the automation lives in Selenium instead? The solve step is identical - the token loop does not care which driver produced it. The Selenium integration guide covers the same flow with execute_script injection and the Node client.

Where do I start? The quick start shows the same flow with cURL, and creating an account takes a minute.

Back to all articles

Sign Up

Create Account

Already have an account? please
Login
Terms Of Services

Terms of Services

EndCaptcha Terms of Service:

EndCaptcha is a Data Entry Company that focuses on fast and reliable conversion of Images to Text and general digitization. Any use of the service for activities that are deemed illegal is strictly prohibited.

EndCaptcha is not responsible for the actions of its clients. Any complaint about any particular EndCaptcha client should be reported to [email protected]. EndCaptcha reserves the right to suspend its clients' account if, through an [email protected] report, gets hold of undeniable proof that the client is engaging in illicit activities.

EndCaptcha reserves the right to change at any time its Terms of Service, prices of packages, Service Level Agreement conditions and compensation, and Unsupported Images without prior notice.

Unsupported Activities:

EndCaptcha reserves the right to terminate any client account without notice if, by its sole judgement, EndCaptcha determines that the client is engaged in activities that negatively affect service levels. Examples of these harmful activities are:
- Using EndCaptcha's API in ways that negatively affects the operation of the service
- Constantly submitting images that are unreadable
- Exploiting in any way the Service Level Agreement compensation
- Exploiting the service function used to claim images as incorrectly solved

Ownership of an EndCaptcha account:

All accounts created in EndCaptcha remain as sole property of EndCaptcha, and its parent company. Sharing account details, selling and reselling accounts, and selling awarded coupons is prohibited. Violation of this policy will result in account suspension.

Service Level Agreement:

Even though customers may submit Unsupported Images, and EndCaptcha will do it’s best effort to solve said images in a timely fashion, the Service Level Agreement won't cover any Unsupported Images and won't be available during dates included in the Maintenance and Holidays Calendar. EndCaptcha reserves the right to include Unsupported Images at any time without previous notice.

Additional information about the types of insurances provided in the Service Level Agreement may be found here.

Refunds and Chargebacks:

Any fulfilled claims for chargebacks before consulting our helpdesk will result in an immediate suspension of the clients account and appropriate legal action (fraud).

Refunds are processed in a case-by-case basis. We will not award refunds if:
- The client is unsatisfied with the results obtained when submitting Unsupported Images.
- The client has partially used the service and has no use for remaining balance in the client's account.
- The client was unaware of any explicitly written service constraints before making a purchase.

If a refund is awarded, the client will receive funds equivalent to the remaining credit balance in the client's account.

Maintenance and Holidays Calendar

Maintenance and Holidays Calendar

Although we will try our best to keep maintenance and undercapacity occurrences to a minimum, there will be rare occasions in which these events will be unavoidable, and thus not covered by the Insurances of our Service Level Agreement. The Maintenance and Holidays Calendar will show the foreseeable events of diminished Quality of Service.

Event Description Date Duration
Unsupported Images

Unsupported Images

The following images can be solved by our team of workers, but are not insured by the Slowness Insurance. These images are usually unreadable, math problems, or have a lengthy amount of characters. The same exception applies for the Recaptcha V2 challenges. Due to their complexity, are not eligible for Slowness Insurance


Recaptcha V2 Fast Solving Service Recaptcha Invisible Decoding Solution
Refunds and Chargebacks

Refunds and Chargebacks

Any fulfilled claims for chargebacks before consulting our helpdesk will result in an immediate suspension of the clients account and appropriate legal action (fraud).

Refunds are processed in a case-by-case basis. We will not award refunds if:
- The client is unsatisfied with the results obtained when submitting Unsupported Images.
- The client has partially used the service and has no use for remaining balance in the client's account.
- The client was unaware of any explicitly written service constraints before making a purchase.

If a refund is awarded, the client will receive funds equivalent to the remaining credit balance in the client's account.

Service Level Agreement

Service Level Agreement

EndCaptcha offers a unique Service Level Agreement that compensates its users when there is a drop in Quality Standards.

About Compensation

Users will be compensated with Compensatory Credits, which will be deposited once the currently used Credit Package has been consumed. The amount of Compensatory Credits will be calculated depending on the type of Insurance and the Compensation Coverage Level of the currently used package.

Compensatory Credits, Promotional Credits, and Free Credits won't be subject to the terms of the Service Level Agreement and its insurances.

Compensation Coverage Level

The user will be given a Slowness Insurance and an Outage Insurance. These types of insurances will provide compensation to users whenever certain Quality Standards are not met. There will be different Compensation Coverage Levels depending on what Credit Package was bought. The amount of Compensatory Credits will be the result of multiplying the Compensation Coverage Level percentage with the amount of quantifiable times the Quality Standards are not met.

Slowness Insurance

*Approximately 70% of all images are decoded with-in 7-8 seconds on average. The remainder is also guaranteed to be decoded with-in 11 seconds max. For any images that take longer than 11 seconds to solve, our slowness insurance policies kick in.

The Slowness Insurance will provide compensation to EndCaptcha users for images that are getting typed in more than 11 seconds. The compensation will be calculated on an Image-by-image basis.

Example Case:
Compensation Level: 60% Coverage
Images over 11 seconds: 2,000
Compensation Credits: 1,200

The Slowness Insurance won't cover Unsupported Images, and is not applicable during the time ranges included in the Maintenance and Holidays Calendar.

Outage Insurance

The Outage Insurance will provide compensation to EndCaptcha users when the service suffers from an unexpected outage that renders the service partially or completely unresponsive. The Outage Insurance will only cover cases of registered server problems on EndCaptcha's side, it will not cover issues in the user's side or connectivity problems between the user and EndCaptcha.

The Outage Insurance will only compensate credits in accordance with the usual activity of the users during the event. The usual activity of the user will be determined by the average past submissions made by the user during the weekday and time of the outage event.

Example Case:
Compensation Level: 100% Coverage
Usual Activity During Event: 2,000 Images
Compensation Credits: 2,000 Images

The Outage Insurance will be evaluated on a case by case basis whenever the user opens an outage claim at [email protected]. For outage claims to be valid, they have to be opened with-in 72 hours of the outage occurring.

The Outage Insurance is not applicable during the Maintenance time ranges included in the Maintenance and Holidays Calendar, which have been announced a minimum of 12 hours in advance. The Outage Insurance is not applicable when deliberate attacks to our service are made.

EndCaptcha reserves the right to change its Service Level Agreement without prior notice.