Point Playwright at a Cloudflare protected URL, call page.screenshot({ fullPage: true }), and the file you open is a crisp capture of a "Checking your browser" interstitial instead of the site you wanted.
By the end of this post you will know why headless Chromium trips Cloudflare, which stealth tricks actually move the needle, and the one call that skips the whole fight.
The short version: a stock Playwright browser looks like automation to Cloudflare, so it gets challenged before your screenshot code ever sees a rendered page. You can patch that with playwright-extra, a residential proxy, and a standing maintenance budget, or you can send one GET request to ScreenshotRender with Stealth Mode and skip it. Here is the whole request as a single line:
https://screenshotrender.com/api/v1/screenshot?apiKey=YOUR_API_KEY&url=https://www.g2.com&fullPage=true
The stealth-plugin route is real and sometimes works, so section three walks it, and section four is honest about where it breaks.
Why does Playwright get blocked by Cloudflare?
Playwright gets blocked because Cloudflare fingerprints the browser as automation before the page renders, then serves a challenge in place of the content. The decision happens at the edge, so your page.goto resolves against the challenge and your screenshot code never reaches the real DOM.
Four signals do most of the work. navigator.webdriver is set to true in any browser Playwright drives, and that single property is a giveaway. The navigator.plugins array is empty in a stock headless build where a real desktop Chrome reports several entries. The TLS handshake carries its own fingerprint, and the one produced by most automation stacks matches a known library rather than a consumer browser. On top of that, the default launch flags Playwright passes to Chromium are themselves a recognizable pattern. These are the same automation fingerprints that get headless Chrome flagged no matter which library launched it.
Cloudflare's Bot Management documentation describes how these signals feed a bot score, and a low score earns you a challenge. The page you wanted is behind that interstitial, and a screenshot taken now records the challenge, not the content.
What does a Cloudflare block look like in your screenshot?
It looks like a clean screenshot of Cloudflare, not your target: a near-empty page with a spinner and the line "Checking your browser before you access...", or a Turnstile checkbox floating on white. The trap is that nothing in your code threw an error.
The challenge page returns a normal 200 status, so page.goto resolves as if the navigation succeeded. Playwright then runs page.screenshot({ fullPage: true }) and captures whatever is on screen, which is the challenge. The Playwright screenshot API did its job perfectly; it just photographed the wrong document. If you want a refresher on the basic page.screenshot options, they behave identically here. The problem is never the capture call, it is what Cloudflare put in front of it.
How do you add stealth to Playwright to get past Cloudflare?
You swap the stock chromium launcher for playwright-extra with the stealth plugin, run the browser headful, and pair it with a residential IP. Registering the plugin is one line, chromium.use(StealthPlugin()), and it patches the obvious tells so navigator.webdriver reports false and the plugin array looks populated.
Getting a real page to capture takes three moving parts that all need upkeep:
- The stealth plugin to patch the fingerprints a stock browser leaks, so the easy automation checks pass.
- A headful browser instead of the default. Launch with
{ headless: false }under a virtual display like Xvfb, because headless Chromium exposes extra signals a real window does not. - A residential proxy so the request does not arrive from a datacenter IP range Cloudflare has already flagged, paired with a user agent that stays consistent with the proxy region.
Each part works until Cloudflare ships a new detection rule, and then you are patching again. Underneath all of it is a flat infrastructure cost: a warm Chromium is memory hungry, so a small server handles only a handful of parallel screenshots before it runs out of RAM. Most teams spend weeks here before deciding the screenshot was never the part worth owning.
Can playwright-extra reliably bypass Cloudflare?
Not reliably, no. The stealth plugin clears the easy fingerprint checks, but Cloudflare's managed challenges and Turnstile are tuned to catch exactly this setup, so the same script that renders a real page today can start capturing challenge pages after a quiet rule update. Here is where the do-it-yourself route runs out of road:
- Interactive Turnstile. A managed challenge that demands a real click is designed to stop headless automation, and a patched fingerprint alone will not satisfy it.
- IP reputation. Once a proxy pool is burned, fresh fingerprints do not help, and you are back to renting cleaner residential IPs.
- Maintenance drag. The plugin, the proxy, and the user agent list each need updating on Cloudflare's schedule, not yours, and a break shows up as a silent bad screenshot rather than a loud error.
None of this means the stealth stack is worthless. It means the cost is ongoing, and the failure mode is a capture that looks fine until someone opens it.
Skip the Cloudflare fight. Just get the screenshot.
Skip the Chromium build, the stealth-plugin upkeep, and the residential proxy budget. One HTTP GET to ScreenshotRender with Stealth Mode renders the real page and returns a clean PNG.
Try a renderHow do you screenshot a Cloudflare site without maintaining a browser?
Send one GET request to ScreenshotRender with Stealth Mode on your account and you get back a hosted PNG of the real page, no plugin and no proxy to run. The entire request is one line you can copy:
https://screenshotrender.com/api/v1/screenshot?apiKey=YOUR_API_KEY&url=https://www.g2.com&fullPage=true
Three parameters do the work. apiKey is your sr- prefixed key from the ScreenshotRender dashboard. url is the Cloudflare protected page you want, percent encoded if it carries its own query string. fullPage=true captures the entire scrollable document. The response is JSON with the hosted image at data.screenshot. Notice what is not in that URL: no proxy setting, no fingerprint flag, no headful toggle. Stealth Mode is a plan level feature, so it applies to every request once your account is on a plan that includes it.
Under the hood the render happens in a real Chromium browser, which is what makes the captured page match what a human visitor sees. Cookie consent banners, GDPR popups, ad overlays, and chat widgets are stripped before the capture on every plan, so the Cloudflare site you screenshot comes back clean as well as unblocked. You pay only for successful requests, so a render that fails does not count against your quota. Test the exact call against any protected site in the interactive playground before you wire it into code. For the framework-agnostic version of this walkthrough, the general Cloudflare screenshot guide covers the same idea without the Playwright specifics.
Two honest limits. The free tier does not include Stealth Mode, so a free key pointed at a protected site captures the challenge, not the content; Stealth Mode starts on the Hobby plan. And no screenshot service clears every interactive CAPTCHA every time, so treat the result as a high success rate rather than a guarantee, and pass a timeout value so a blocked request fails fast instead of hanging.
Common questions about Playwright and Cloudflare
Can Playwright bypass Cloudflare?
Sometimes, not reliably. A stock Playwright browser is fingerprinted as automation and gets challenged, so you need playwright-extra with the stealth plugin to patch the obvious tells, a residential proxy so the request does not come from a flagged datacenter IP, and a headful browser instead of the default headless one. That stack clears easy checks, but Cloudflare's managed challenges and Turnstile are tuned to catch exactly this setup, so a script that works today can start capturing challenge pages after a rule update.
Why does my Playwright screenshot show "Checking your browser"?
Because page.goto resolved successfully on the Cloudflare interstitial, not on your target page. The challenge HTML returns a normal 200 status, so Playwright throws no error and page.screenshot faithfully captures whatever is on screen. Nothing in your code failed; Cloudflare simply served a different document than the one you asked for, and your capture recorded it.
Does running Playwright headful help against Cloudflare?
It helps, but it is not enough on its own. Headless Chromium exposes extra automation signals that a headful browser does not, so switching to { headless: false } and running under a virtual display like Xvfb removes one class of tell. Cloudflare still fingerprints navigator.webdriver, the TLS handshake, and the IP reputation, so headful mode is one layer in a stealth stack rather than a fix by itself.
Can I screenshot a Cloudflare site in Playwright Python?
Yes, the same way as the Node version, with the same limits. Playwright's Python bindings expose page.screenshot(path="shot.png", full_page=True) after page.goto, and the stealth story is identical: you need a residential proxy and patched fingerprints to get past the challenge, and you maintain them as Cloudflare updates. If you only want the image, a single GET request to a screenshot API with stealth built in avoids the Python-side browser management entirely.
Is there a free way to screenshot a Cloudflare protected site?
ScreenshotRender's free plan includes 100 screenshots per month with no credit card, but Stealth Mode is not part of it, so the free tier handles ordinary pages rather than Cloudflare protected ones. Stealth Mode starts on the Hobby plan, which is $10 per month billed annually and includes 2,000 screenshots. Cookie banner and ad removal are enabled on every plan, including the free one, and you only pay for successful requests.
The honest verdict: screenshotting a Cloudflare protected site with raw Playwright is possible, but the stealth plugin, the proxy budget, and the rule-chasing maintenance rarely pay for themselves when the screenshot is all you needed. A hosted call with Stealth Mode turns the whole problem into one URL, and the free tier is enough to confirm ordinary pages render before you upgrade for the protected ones.



