How to Bypass Cloudflare with Playwright in 2026

Master Playwright techniques to handle Cloudflare challenges in your test automation. Test your bypass strategies on real browsers and devices with BrowserStack.

Get Started free
How to Bypass Cloudflare with Playwright in 2026
Home Guide How to Bypass Cloudflare with Playwright in 2026

How to Bypass Cloudflare with Playwright in 2026

Ever fired up a Playwright test only to hit Cloudflare’s “Checking your browser” wall?

Your scripts work perfectly locally, but the moment they touch a Cloudflare-protected site, everything breaks.

CAPTCHAs appear, requests get blocked, and your CI/CD pipeline stalls.

I faced this exact issue testing an e-commerce platform.

My Playwright scripts kept failing, and I couldn’t figure out why. Then I realized Cloudflare was detecting automation signatures, browser fingerprints, and bot-like navigation patterns that Playwright leaves by default.

The solution?

Overview

Use Playwright in Cloudflare Workers

Cloudflare makes it possible to run Playwright right on the edge without provisioning servers or managing a browser infrastructure. Their Playwright distribution includes a headless Chromium instance that executes within the Workers runtime, which is useful for tasks that require real webpage rendering.

To get started, install the Cloudflare’#145;specific library:

npm install -s @cloudflare/playwright

Before deployment, the Worker must be configured correctly in wrangler.toml. A recent compatibility date is required, and a browser binding must be declared so the Worker can access the bundled browser:

compatibility_date = “2025-09-15”
compatibility_flags = [“nodejs_compat”]
browser = { binding = “MYBROWSER” }

Once set up, this enables features such as:

  • Full page rendering for visual capture or PDF output
  • Functional checks against UI elements
  • Automated interaction flows executed close to the end user

Cloudflare also offers Playwright MCP, which lets AI agents via Workers AI operate browsers programmatically and exchange structured data with web pages, expanding possibilities for agent’#145;driven automation on the edge.

In this guide, I’ll show you how to handle Cloudflare protection with stealth plugins, proxy rotation, fingerprint spoofing, and human behavior simulation so your legitimate test automation runs smoothly.

How Cloudflare Detects Automation with Playwright

Cloudflare’s bot detection isn’t just checking if you’re a browser or not. It’s analyzing dozens of signals to determine whether you’re a real human or an automated script. Understanding these detection methods is crucial before you can configure Playwright to work around them.

1. Browser Fingerprinting

Cloudflare collects browser characteristics like canvas fingerprints, WebGL rendering patterns, audio context signatures, and font lists. Playwright’s default configuration often produces fingerprints that are suspiciously consistent or match known automation patterns. Even minor inconsistencies between what your browser claims to be and how it actually behaves can trigger blocks.

2. Automation Indicators

Out of the box, Playwright sets properties like navigator.webdriver to true, exposes automation-specific JavaScript objects, and leaves traces in the browser’s Chrome DevTools Protocol. Cloudflare actively checks for these telltale signs. If it finds window.chrome missing in a Chrome browser or detects Playwright-specific properties, you’re flagged instantly.

3. Behavioral Analysis

Real users don’t navigate websites like robots. Cloudflare monitors mouse movements, scroll patterns, keystroke dynamics, and timing between actions. Playwright scripts that click buttons instantly, load pages without any mouse activity, or navigate with inhuman precision stand out. The lack of natural pauses and erratic human behavior makes automated traffic obvious.

Cloudflare’s detection methods make it difficult to predict how automation scripts will behave in real environments. Running Playwright tests on BrowserStack lets you execute scripts on real browsers and devices, observe exactly how they interact with sites under realistic conditions, and identify points where automation triggers blocks.

Run Playwright Tests on Real Devices

4. Network and IP Reputation

Cloudflare tracks IP addresses and their reputation. If you’re testing from a datacenter IP, making hundreds of requests in quick succession, or showing inconsistent geolocation data, you’ll trigger rate limits or outright blocks. Residential IPs and proper request pacing are essential to avoid this detection layer.

5. TLS and HTTP Fingerprinting

Beyond the browser, Cloudflare analyzes the TLS handshake and HTTP/2 fingerprints. Automated tools often have distinct TLS configurations that differ from standard browsers. Mismatched cipher suites, extension orders, or HTTP header sequences can expose automation even before your JavaScript executes.

Preparing Playwright for Evasion in 2026

Before diving into stealth plugins and advanced techniques, you need to set up Playwright with the right foundation. A few configuration tweaks during initialization can significantly reduce detection rates and make your automation appear more legitimate.

1. Use Chromium with Proper Launch Arguments

Start by launching Playwright with arguments that disable automation flags and mimic a real browser environment. The default Playwright setup screams “bot,” so you’ll need to override several Chrome flags:

const browser = await chromium.launch({ headless: false, // Headless mode is easier to detect
args: [
‘–disable-blink-features=AutomationControlled’,
‘–disable-dev-shm-usage’,
‘–no-sandbox’,
‘–disable-setuid-sandbox’,
‘–disable-web-security’,
‘–disable-features=IsolateOrigins,site-per-process’
]
});

Setting headless: false is crucial for testing environments where visual verification matters, though newer headless modes are harder to detect than older versions.

2. Remove Automation Indicators

After launching the browser, inject JavaScript that removes common automation signatures. Cloudflare checks for properties like navigator.webdriver, so you’ll need to override these before navigating to any Cloudflare-protected page:

const context = await browser.newContext();await context.addInitScript(() => {
Object.defineProperty(navigator, ‘webdriver’, {
get: () => undefined
});

window.chrome = {
runtime: {}
};
});

This script runs before any page loads, ensuring Cloudflare’s JavaScript never sees automation markers.

3. Set Realistic Browser Context

Configure your browser context with realistic viewport sizes, user agents, and locale settings. Avoid using obvious automation user agents or unusual screen resolutions:

const context = await browser.newContext({ viewport: { width: 1920, height: 1080 },
userAgent: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36’,
locale: ‘en-US’,
timezoneId: ‘America/New_York’,
permissions: [‘geolocation’]
});

Consistency matters here. If your user agent claims you’re on Windows but your canvas fingerprint suggests macOS, Cloudflare will notice.

4. Enable JavaScript and Cookies

This sounds obvious, but ensure JavaScript is enabled and cookies are properly handled. Cloudflare relies heavily on JavaScript challenges and cookie-based tracking:

const context = await browser.newContext({ javaScriptEnabled: true,
acceptDownloads: true,
ignoreHTTPSErrors: false
});

With these foundational settings in place, you’re ready to add stealth plugins and more sophisticated evasion techniques.

Using Stealth Plugins and Fingerprint Spoofing

Even with proper launch configurations, Playwright can still be detected through sophisticated fingerprinting techniques. Stealth plugins and fingerprint randomization add an extra layer of protection by automatically handling detection vectors you might miss manually.

1. Install and Configure Playwright-Extra Stealth

The playwright-extra framework with the puppeteer-extra-plugin-stealth plugin is your best bet for comprehensive evasion. It patches dozens of automation indicators automatically:

const { chromium } = require(‘playwright-extra’);const StealthPlugin = require(‘puppeteer-extra-plugin-stealth’);

chromium.use(StealthPlugin());

const browser = await chromium.launch({
headless: false
});

This plugin handles navigator.webdriver removal, chrome object fixes, permissions API spoofing, and plugin array manipulation without manual intervention. It’s actively maintained and updated to counter new detection methods.

2. Randomize Canvas Fingerprints

Canvas fingerprinting is one of Cloudflare’s most reliable detection methods. Each browser renders canvas elements slightly differently based on hardware and software configurations. Add noise to your canvas to avoid consistent fingerprints:

await context.addInitScript(() => { const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {
const canvas = this;
const ctx = canvas.getContext(‘2d’);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Add minimal noise
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] += Math.floor(Math.random() * 3) – 1;
}

ctx.putImageData(imageData, 0, 0);
return originalToDataURL.call(this, type);
};
});

3. Spoof WebGL Fingerprints

WebGL rendering provides another unique fingerprint. Randomize WebGL parameters to avoid detection:

await context.addInitScript(() => { const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) {
return ‘Intel Inc.’; // UNMASKED_VENDOR_WEBGL
}
if (parameter === 37446) {
return ‘Intel Iris OpenGL Engine’; // UNMASKED_RENDERER_WEBGL
}
return getParameter.call(this, parameter);
};
});

4. Randomize Fonts and Plugins

Cloudflare checks available fonts and browser plugins. Use different font lists and plugin configurations across sessions:

await context.addInitScript(() => { Object.defineProperty(navigator, ‘plugins’, {
get: () => [
{
name: ‘Chrome PDF Plugin’,
filename: ‘internal-pdf-viewer’,
description: ‘Portable Document Format’
},
{
name: ‘Chrome PDF Viewer’,
filename: ‘mhjfbmdgcfjbbpaeojofohoefgiehjai’,
description: ”
}
]
});
});

5. Use FingerprintJS for Testing

Before hitting Cloudflare-protected sites, test your fingerprint consistency using FingerprintJS or similar services. This helps identify what’s leaking your automation:

const page = await context.newPage();await page.goto(‘https://fingerprint.com/demo/’);
await page.waitForTimeout(3000);
// Check if fingerprint changes between runs

With stealth plugins and fingerprint spoofing in place, your Playwright instance looks significantly more like a real browser. Next, we’ll tackle IP reputation and geolocation issues.

Proxy Rotation, Residential IPs & Geolocation in Playwright

Even with perfect browser fingerprinting, your IP address can give you away. Cloudflare tracks IP reputation, request patterns, and geolocation consistency. Using datacenter IPs or making too many requests from a single address will get you blocked fast.

Why Residential Proxies Matter

Datacenter IPs are flagged by Cloudflare because they’re associated with hosting providers, not real users. Residential proxies route your traffic through real ISP-assigned IP addresses, making your requests appear legitimate. For testing environments that need to bypass Cloudflare, residential proxies are essential.

1. Configure Playwright with Proxy Support

Playwright supports HTTP, HTTPS, and SOCKS5 proxies. Configure them during context creation:

const context = await browser.newContext({ proxy: {
server: ‘http://proxy-server.com:8080’,
username: ‘your-username’,
password: ‘your-password’
}
});

For authenticated proxies, include credentials directly in the configuration. Most proxy providers offer rotating residential IPs that change with each request or session.

2. Implement Proxy Rotation

Don’t use the same IP for every request. Rotate proxies between test runs or even between page navigations:

const proxies = [ { server: ‘http://proxy1.com:8080’, username: ‘user1’, password: ‘pass1’ },
{ server: ‘http://proxy2.com:8080’, username: ‘user2’, password: ‘pass2’ },
{ server: ‘http://proxy3.com:8080’, username: ‘user3’, password: ‘pass3’ }
];

function getRandomProxy() {
return proxies[Math.floor(Math.random() * proxies.length)];
}

const context = await browser.newContext({
proxy: getRandomProxy()
});

This prevents Cloudflare from associating multiple requests with a single IP and triggering rate limits.

3. Match Geolocation with IP Address

If your proxy IP is in New York but your browser timezone says Los Angeles, Cloudflare will notice. Always match your geolocation settings with your proxy location:

const context = await browser.newContext({ proxy: {
server: ‘http://us-east-proxy.com:8080’
},
locale: ‘en-US’,
timezoneId: ‘America/New_York’,
geolocation: { latitude: 40.7128, longitude: -74.0060 },
permissions: [‘geolocation’]
});

Check your proxy provider’s documentation for accurate geolocation coordinates for each IP.

4. Use Sticky Sessions When Needed

Some testing scenarios require maintaining the same IP across multiple requests (like testing a complete user journey). Use sticky session proxies that keep the same IP for a defined period:

const context = await browser.newContext({ proxy: {
server: ‘http://sticky-proxy.com:8080’,
username: ‘user-session-123’, // Session identifier
password: ‘your-password’
}
});

However, running multiple Playwright scripts in parallel across different IPs, regions, and devices requires infrastructure that is hard to maintain locally. Tools like BrowserStack let teams scale testing effortlessly by providing instant access to thousands of real browsers and devices in the cloud, eliminating proxy management overhead.

Playwright Testing

Simulating Human Activity and Navigation Patterns

Perfect browser configuration and residential IPs won’t help if your Playwright script navigates like a robot. Cloudflare analyzes behavioral patterns, and inhuman precision is a dead giveaway. You need to add randomness, delays, and realistic interactions to your automation.

1. Add Random Delays Between Actions

Real users don’t click buttons instantly or navigate at machine speed. Introduce random delays between actions:

function randomDelay(min, max) { return Math.floor(Math.random() * (max – min + 1) + min);
}

await page.click(‘#login-button’);
await page.waitForTimeout(randomDelay(1000, 3000)); // Wait 1-3 seconds
await page.fill(‘#username’, ‘testuser’);
await page.waitForTimeout(randomDelay(500, 1500));

2. Simulate Mouse Movements

Humans move their mouse around the page before clicking. Add realistic mouse movements:

async function humanClick(page, selector) { const element = await page.locator(selector);
const box = await element.boundingBox();

if (box) {
// Move to random position near the element first
await page.mouse.move(
box.x + Math.random() * box.width,
box.y + Math.random() * box.height,
{ steps: randomDelay(5, 15) }
);

await page.waitForTimeout(randomDelay(100, 500));
await element.click();
}
}

await humanClick(page, ‘#submit-button’);

3. Add Scroll Behavior

Users scroll through pages naturally. Add random scrolling before interacting with elements:

async function humanScroll(page) { const scrollHeight = await page.evaluate(() => document.body.scrollHeight);
const viewportHeight = await page.evaluate(() => window.innerHeight);

// Scroll in chunks
for (let i = 0; i window.scrollTo(0, y), scrollTo);
await page.waitForTimeout(randomDelay(500, 1500));
}
}

await page.goto(‘https://example.com’);
await humanScroll(page);
await page.click(‘#target-element’);

4. Type Like a Human

Don’t fill form fields instantly. Use type() with delays instead of fill():

async function humanType(page, selector, text) { await page.click(selector);

for (const char of text) {
await page.keyboard.type(char);
await page.waitForTimeout(randomDelay(50, 150)); // 50-150ms per character
}
}

await humanType(page, ‘#email’, ‘user@example.com’);

Add occasional typos and corrections for even more realism:

async function humanTypeWithErrors(page, selector, text) { await page.click(selector);

for (let i = 0; i < text.length; i++) {
// 5% chance of typo
if (Math.random() 0) {
await page.keyboard.type(‘x’); // Wrong character
await page.waitForTimeout(randomDelay(100, 300));
await page.keyboard.press(‘Backspace’);
await page.waitForTimeout(randomDelay(50, 150));
}

await page.keyboard.type(text[i]);
await page.waitForTimeout(randomDelay(50, 150));
}
}

5. Mimic Reading Time

Users spend time reading content before acting. Add delays proportional to content length:

async function simulateReading(page, selector) { const text = await page.textContent(selector);
const wordCount = text.split(‘ ‘).length;
const readingTime = (wordCount / 200) * 60 * 1000; // 200 words per minute

await page.waitForTimeout(randomDelay(readingTime * 0.5, readingTime * 1.5));
}

await page.goto(‘https://example.com/article’);
await simulateReading(page, ‘article’);
await page.click(‘#next-page’);

Handling Cloudflare Turnstile, CAPTCHA and Advanced Challenges

Even with perfect configuration, you’ll occasionally encounter Cloudflare’s challenge pages. Turnstile, CAPTCHAs, and JavaScript challenges require specific handling strategies to keep your automation running smoothly.

Cloudflare uses several challenge mechanisms:

  • Turnstile: A CAPTCHA alternative that checks browser authenticity without user interaction
  • Managed Challenge: JavaScript-based verification that happens automatically
  • Interactive Challenge: Traditional CAPTCHA requiring manual solving
  • Block Page: Hard block with no bypass option

Your approach depends on which challenge you encounter.

Detecting Challenge Pages

Before handling challenges, detect when you’ve hit one:

async function isCloudflareChallenge(page) { const title = await page.title();
const content = await page.content();

return title.includes(‘Just a moment’) ||
content.includes(‘Checking your browser’) ||
content.includes(‘cloudflare’) ||
await page.locator(‘#challenge-form’).isVisible().catch(() => false);
}

await page.goto(‘https://example.com’);

if (await isCloudflareChallenge(page)) {
console.log(‘Cloudflare challenge detected’);
// Handle accordingly
}

Wait for Automatic Challenges to Complete

Many Cloudflare challenges resolve automatically if your configuration is correct. Simply wait:

async function waitForCloudflareChallenge(page, timeout = 30000) { if (await isCloudflareChallenge(page)) {
console.log(‘Waiting for Cloudflare challenge to resolve…’);
await page.waitForFunction(
() => !document.title.includes(‘Just a moment’),
{ timeout }
);
await page.waitForTimeout(randomDelay(2000, 4000)); // Extra safety delay
}
}

await page.goto(‘https://example.com’);
await waitForCloudflareChallenge(page);

Handle Turnstile Challenges

Turnstile challenges often pass automatically with proper stealth configuration. If not, you may need to wait longer:

async function handleTurnstile(page) { const turnstileFrame = page.frameLocator(‘iframe[src*=”challenges.cloudflare.com”]’);

try {
await turnstileFrame.locator(‘#challenge-stage’).waitFor({
state: ‘hidden’,
timeout: 30000
});

console.log(‘Turnstile challenge passed’);
await page.waitForTimeout(randomDelay(1000, 3000));
} catch (error) {
console.log(‘Turnstile challenge may require manual intervention’);
}
}

Integrate CAPTCHA Solving Services

For interactive CAPTCHAs, integrate third-party solving services like 2Captcha, Anti-Captcha, or CapSolver:

async function solveCaptcha(page, apiKey) { const siteKey = await page.getAttribute(‘[data-sitekey]’, ‘data-sitekey’);
const pageUrl = page.url();

// Send to solving service
const response = await fetch(‘https://2captcha.com/in.php’, {
method: ‘POST’,
body: JSON.stringify({
key: apiKey,
method: ‘turnstile’,
sitekey: siteKey,
pageurl: pageUrl
})
});

const taskId = await response.json();

// Poll for solution
let solution;
for (let i = 0; i setTimeout(resolve, 5000));

const result = await fetch(`https://2captcha.com/res.php?key=${apiKey}&action=get&id=${taskId}`);
const data = await result.text();

if (data.includes(‘OK|’)) {
solution = data.split(‘|’)[1];
break;
}
}

// Inject solution
if (solution) {
await page.evaluate((token) => {
document.querySelector(‘[name=”cf-turnstile-response”]’).value = token;
}, solution);

await page.click(‘#challenge-form button[type=”submit”]’);
}
}

Retry Logic for Failed Challenges

Implement retry mechanisms when challenges fail:

async function navigateWithRetry(page, url, maxRetries = 3) { for (let attempt = 1; attempt

Testing and Validating the Bypass Flow on BrowserStack

Bypass techniques can successfully overcome Cloudflare challenges in a controlled environment. The next step is confirming that these solutions remain reliable when automation runs across different browsers, operating systems, IP regions, and device capabilities.

Cloudflare applies risk scoring dynamically so behavior that appears legitimate in one context can still be flagged in others.

Testing on BrowserStack ensures automated flows continue to work under real-world conditions. It gives you access to over 3,500+ browsers and device combinations. You can browse through the logs, screenshots, and video playback to check where Cloudflare detection tightens and refine fingerprinting or proxy configurations before deployment.

Here are some key features of BrowserStack Automate for testing bypass flow with Playwright.

  • Real Device Cloud: Test Cloudflare bypass flows on physical devices and real browsers to ensure production-level behavior.
  • Global Infrastructure: Validate proxy and geolocation settings with test runs from multiple regions that match real residential routing.
  • Parallel Testing: Compare multiple bypass strategies at once across different browser and OS combinations.
  • Session Insights: Use captured videos, logs, and network traces to pinpoint where Cloudflare challenges trigger.
  • Instant Browser Access: Spin up any browser version quickly to verify compatibility without local setup.

Talk to an Expert

Conclusion

Cloudflare’s bot protection introduces complex detection signals that challenge traditional browser automation workflows. Leveraging Playwright with the right configuration, fingerprinting safeguards, and proxy strategies can help automation behave more like a real user and successfully navigate Cloudflare’s defense systems.

To validate that these bypass strategies hold in real usage conditions, BrowserStack provides the final layer of confidence. Real device coverage, global routing options, and detailed debugging insights allow teams to confirm that their Playwright automations remain consistent across environments that Cloudflare evaluates differently.

Try BrowserStack Now

Useful Resources for Playwright

Tool Comparisons:

Tags
Automation framework Automation Testing Real Device Cloud
Cloudflare Blocking Your Playwright Tests?
Run Playwright on real devices. Handle challenges, captchas, and rate limits with clean devices.

Get answers on our Discord Community

Join our Discord community to connect with others! Get your questions answered and stay informed.

Join Discord Community
Discord