Tutorial
Playwright and Cloudflare "Just a Moment": Using cf_clearance
Why Playwright gets stuck on Cloudflare's "Just a moment..." page, how to detect it, and how to load a cf_clearance cookie with its user agent and proxy.
By ZeroCaptcha Engineering4 min readPublished Updated
When Playwright opens a site and stays on “Just a moment…”, it has met a Cloudflare challenge
page: a rule on the site asks every visitor to pass Cloudflare’s check before the page loads.
Cloudflare documents that browser automation frameworks, “such as Selenium, Puppeteer,
Playwright, and Cypress, are not supported for solving production challenges”, so waiting often
does not help. What the page wants is a cf_clearance cookie, which a browser earns by passing the
challenge. In Playwright you can use one: create a context with the cookie’s user agent, add the
cookie, and browse through the same proxy that earned it. This tutorial shows how to detect the
challenge and load a clearance, on sites you are allowed to automate.
Detect the challenge
Cloudflare’s docs say a challenge response carries the header cf-mitigated: challenge, whatever
resource was requested. Checking it is more reliable than looking for the title:
import { chromium } from "playwright";
const browser = await chromium.launch();const page = await browser.newPage();const response = await page.goto("https://shop.example.com/", { waitUntil: "domcontentloaded" });if (response?.headers()["cf-mitigated"] === "challenge") { console.log(`Cloudflare challenge, HTTP ${response.status()}: ${await page.title()}`);}await browser.close();A challenge usually answers with HTTP 403 and the title “Just a moment…”. An error page with a code such as 1020 is different: a rule blocked the request, and no cookie helps. See Cloudflare error 1020.
Why the clearance needs its user agent and proxy
Passing a challenge sets cf_clearance, which Cloudflare says “proves to Cloudflare that the
visitor is a verified human” and “is securely tied to the specific visitor and device it was issued
to”. Its lifetime is the site’s Challenge Passage setting, 30 minutes by default. In practice that
means three things for Playwright:
- The same user agent. Set the context’s
userAgentto exactly the one the cookie was issued with. - The same network. Cloudflare notes that a Managed Challenge solved from a different IP than the one it was issued to is not valid, so browse through the proxy that earned the clearance.
- A browser that matches the user agent. A user agent string says which browser it is; Chromium’s own behavior should agree. Use a Chromium whose major version is close to the one in the user agent. A mismatch is a signal Cloudflare may act on, and it will challenge again.
Get a clearance and load it
ZeroCaptcha’s challenge task passes the page through your proxy and returns the cf_clearance cookie with the user agent it was issued for.
The task takes the page’s URL and your proxy; a challenge page has no sitekey. The compatible
format answers the way CapSolver’s AntiCloudflareTask does, with solution.userAgent and
solution.cookies.cf_clearance. The challenge pages docs list every field.
Save this as clearance.mjs, with playwright installed and ZEROCAPTCHA_API,
ZEROCAPTCHA_KEY and PROXY_URL (such as http://user:pass@proxy.example.net:8080) set:
import { chromium } from "playwright";
const API = process.env.ZEROCAPTCHA_API;const KEY = process.env.ZEROCAPTCHA_KEY;const PROXY_URL = process.env.PROXY_URL;const TARGET = "https://shop.example.com/";const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function call(method, body) { const response = await fetch(`${API}/${method}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientKey: KEY, ...body }), signal: AbortSignal.timeout(15_000), }); const reply = await response.json(); if (reply.errorId) throw new Error(`${method}: ${reply.errorCode}`); return reply;}
async function getClearance(websiteURL, proxy) { const { taskId } = await call("createTask", { task: { type: "CloudflareChallengeTask", websiteURL, proxy } }); const deadline = Date.now() + 180_000; while (Date.now() < deadline) { await sleep(2_000); const result = await call("getTaskResult", { taskId }); if (result.status === "ready") { return { userAgent: result.solution.userAgent, cookie: result.solution.cookies.cf_clearance }; } } throw new Error(`task ${taskId}: no clearance within 180 seconds`);}
const { userAgent, cookie } = await getClearance(TARGET, PROXY_URL);
// Browse through the same proxy, with the same user agent, carrying the cookie.const proxy = new URL(PROXY_URL);const browser = await chromium.launch({ proxy: { server: `${proxy.protocol}//${proxy.host}`, username: decodeURIComponent(proxy.username), password: decodeURIComponent(proxy.password), },});const context = await browser.newContext({ userAgent });await context.addCookies([{ name: "cf_clearance", value: cookie, url: TARGET }]);const page = await context.newPage();const response = await page.goto(TARGET, { waitUntil: "domcontentloaded" });console.log(response?.headers()["cf-mitigated"] === "challenge" ? "Challenged again" : await page.title());await browser.close();Run it with node clearance.mjs. A challenge that is not passed ends with
ERROR_CAPTCHA_UNSOLVABLE, and a proxy that resolves to a private address with
ERROR_PROXY_NOT_ALLOWED; neither costs anything. The errors reference
lists every code.
When the challenge comes back
- After about 30 minutes: the site’s Challenge Passage ended. Get a new clearance.
- At once: the cookie is being sent from another IP, with another user agent, or by a browser whose other signals contradict the user agent. Check the proxy and the Chromium version.
- On some requests only: a CORS preflight (
OPTIONS) does not carry the cookie, and Cloudflare lists rules features combined with challenges among the causes of loops. See Cloudflare challenge loop.
The cf_clearance cookie explained covers the cookie itself, and the Cloudflare WAF and 5-second challenge solver page the task type. For a Cloudflare Turnstile widget inside a form, which needs a token rather than a cookie, see Playwright on the Cloudflare Turnstile solver page. Automate only sites you are allowed to: see responsible captcha automation.
ZeroCaptcha is not affiliated with CapSolver; its name appears only to describe the reply format.
Sources
- Cloudflare challenges: supported browsers, detect a challenge response, clearance, Challenge Passage, how challenges work and troubleshooting (checked 1 October 2026).
- Playwright: BrowserContext.addCookies and network proxies, version 1.63.0 (checked 1 October 2026).
The team that builds and runs the ZeroCaptcha API. Articles are drafted with AI tools, then checked against the API's code and the primary sources each one cites.