fix: replace page.wait_for_timeout with time.sleep to avoid CDP leak

page.wait_for_timeout() sends CDP protocol commands that reCAPTCHA and
other antibot systems detect. Replaced with time.sleep() (Python) which
is invisible to the browser.

- examples/stealth_test.py: 7 replacements
- examples/recaptcha_score.py: 1 replacement
- tests/test_stealth.py: 7 replacements
- README.md + js/README.md: added reCAPTCHA troubleshooting section
- Bump version to 0.2.2
This commit is contained in:
CloakHQ
2026-03-01 19:37:41 +01:00
parent 67efadef26
commit 1082c810af
7 changed files with 75 additions and 17 deletions
+33
View File
@@ -535,6 +535,39 @@ You do NOT need `playwright install chromium`. CloakBrowser downloads its own bi
playwright install-deps chromium
```
**reCAPTCHA v3 scores are low (0.10.3)**
Avoid `page.wait_for_timeout()` — it sends CDP protocol commands that reCAPTCHA detects. Use native sleep instead:
```python
# Bad — sends CDP commands, reCAPTCHA detects this
page.wait_for_timeout(3000)
# Good — invisible to the browser
import time
time.sleep(3)
```
```javascript
// Bad — sends CDP commands
await page.waitForTimeout(3000);
// Good — invisible to the browser
await new Promise(r => setTimeout(r, 3000));
```
Other tips for maximizing reCAPTCHA scores:
- **Use Playwright, not Puppeteer** — Puppeteer sends more CDP protocol traffic that reCAPTCHA detects ([details](#puppeteer))
- **Use residential proxies** — datacenter IPs are flagged by IP reputation, not browser fingerprint
- **Spend 15+ seconds on the page** before triggering reCAPTCHA — short visits score lower
- **Space out requests** — back-to-back `grecaptcha.execute()` calls from the same session get penalized. Wait 30+ seconds between pages with reCAPTCHA
- **Use a fixed fingerprint seed** (`--fingerprint=12345`) for consistent device identity across sessions
- **Use `page.type()` instead of `page.fill()`** for form filling — `fill()` sets values directly without keyboard events, which reCAPTCHA's behavioral analysis flags. `type()` with a delay simulates real keystrokes:
```python
page.type("#email", "user@example.com", delay=50)
```
- **Minimize `page.evaluate()` calls** before the reCAPTCHA check fires — each one sends CDP traffic
## FAQ
**Q: Is this legal?**
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.2.1"
__version__ = "0.2.2"
+3 -1
View File
@@ -5,6 +5,8 @@ Expected: 0.9 (human-level) with cloakbrowser.
Default Playwright typically scores 0.1-0.3.
"""
import time
from cloakbrowser import launch
browser = launch(headless=True)
@@ -18,7 +20,7 @@ page.wait_for_load_state("networkidle")
button = page.query_selector("button")
if button:
button.click()
page.wait_for_timeout(3000)
time.sleep(3)
# Extract score from page
content = page.content()
+7 -7
View File
@@ -27,7 +27,7 @@ for i, arg in enumerate(sys.argv):
def test_bot_sannysoft(page):
"""bot.sannysoft.com — classic bot detection checks."""
page.goto("https://bot.sannysoft.com", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(3000)
time.sleep(3)
results = page.evaluate("""() => {
const rows = document.querySelectorAll('table tr');
@@ -53,7 +53,7 @@ def test_bot_sannysoft(page):
def test_bot_incolumitas(page):
"""bot.incolumitas.com — comprehensive 30+ check bot detection."""
page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(12000) # needs time to run all detection tests
time.sleep(12) # needs time to run all detection tests
# Site outputs JSON blocks in page text, not HTML tables
results = page.evaluate("""() => {
@@ -74,7 +74,7 @@ def test_bot_incolumitas(page):
def test_browserscan(page):
"""browserscan.net/bot-detection — WebDriver, UA, CDP, Navigator checks."""
page.goto("https://www.browserscan.net/bot-detection", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(5000)
time.sleep(5)
results = page.evaluate("""() => {
const items = document.querySelectorAll('[class*="result"], [class*="item"], [class*="check"]');
@@ -95,7 +95,7 @@ def test_browserscan(page):
def test_deviceandbrowserinfo(page):
"""deviceandbrowserinfo.com/are_you_a_bot — fingerprint + behavioral detection."""
page.goto("https://deviceandbrowserinfo.com/are_you_a_bot", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(8000)
time.sleep(8)
results = page.evaluate("""() => {
const text = document.body.innerText;
@@ -120,12 +120,12 @@ def test_deviceandbrowserinfo(page):
def test_fingerprintjs(page):
"""demo.fingerprint.com/web-scraping — industry-standard bot detection."""
page.goto("https://demo.fingerprint.com/web-scraping", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(8000)
time.sleep(8)
# Click search to trigger bot detection — bots get blocked, humans see flights
try:
page.click("button:has-text('Search')", timeout=5000)
page.wait_for_timeout(5000)
time.sleep(5)
except Exception:
pass
@@ -147,7 +147,7 @@ def test_recaptcha(page):
timeout=30000,
)
# Page auto-submits via grecaptcha.execute() — wait for backend response
page.wait_for_timeout(8000)
time.sleep(8)
results = page.evaluate("""() => {
const text = document.body.innerText;
+22
View File
@@ -184,6 +184,28 @@ const page = await browser.newPage();
- Node.js >= 18
- One of: `playwright-core` >= 1.40 or `puppeteer-core` >= 21
## Troubleshooting
**reCAPTCHA v3 scores are low (0.10.3)**
Avoid `page.waitForTimeout()` — it sends CDP protocol commands that reCAPTCHA detects. Use native sleep instead:
```javascript
// Bad — sends CDP commands, reCAPTCHA detects this
await page.waitForTimeout(3000);
// Good — invisible to the browser
await new Promise(r => setTimeout(r, 3000));
```
Other tips for maximizing reCAPTCHA scores:
- **Use Playwright, not Puppeteer** — Puppeteer sends more CDP protocol traffic that reCAPTCHA detects ([details](#puppeteer))
- **Use residential proxies** — datacenter IPs are flagged by IP reputation, not browser fingerprint
- **Spend 15+ seconds on the page** before triggering reCAPTCHA — short visits score lower
- **Space out requests** — back-to-back `grecaptcha.execute()` calls from the same session get penalized. Wait 30+ seconds between pages with reCAPTCHA
- **Use a fixed fingerprint seed** (`--fingerprint=12345`) for consistent device identity across sessions
- **Minimize `page.evaluate()` calls** before the reCAPTCHA check fires — each one sends CDP traffic
## Links
- 🌐 [Website](https://cloakbrowser.dev)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cloakbrowser",
"version": "0.2.1",
"version": "0.2.2",
"description": "Stealth Chromium that passes every bot detection test. Drop-in Playwright/Puppeteer replacement with source-level fingerprint patches.",
"type": "module",
"main": "dist/index.js",
+8 -7
View File
@@ -5,6 +5,7 @@ bot detection checks. They require network access.
"""
import os
import time
import pytest
from cloakbrowser import launch
@@ -89,7 +90,7 @@ class TestBotDetectionSites:
def test_bot_sannysoft(self, page):
"""bot.sannysoft.com — all checks should pass (0 failures)."""
page.goto("https://bot.sannysoft.com", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(3000)
time.sleep(3)
results = page.evaluate("""() => {
const rows = document.querySelectorAll('table tr');
@@ -115,7 +116,7 @@ class TestBotDetectionSites:
def test_bot_incolumitas(self, page):
"""bot.incolumitas.com — max 1 failure (WEBDRIVER false positive expected)."""
page.goto("https://bot.incolumitas.com", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(12000)
time.sleep(12)
# Known acceptable failures (not browser fingerprint issues):
# - WEBDRIVER: spec-level false positive across all builds
@@ -138,7 +139,7 @@ class TestBotDetectionSites:
def test_browserscan(self, page):
"""BrowserScan bot detection — 0 abnormal checks."""
page.goto("https://www.browserscan.net/bot-detection", wait_until="networkidle", timeout=30000)
page.wait_for_timeout(5000)
time.sleep(5)
results = page.evaluate("""() => {
const text = document.body.innerText;
@@ -157,7 +158,7 @@ class TestBotDetectionSites:
def test_device_and_browser_info(self, page):
"""deviceandbrowserinfo.com — isBot must be false."""
page.goto("https://deviceandbrowserinfo.com/are_you_a_bot", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(8000)
time.sleep(8)
results = page.evaluate("""() => {
const text = document.body.innerText;
@@ -179,11 +180,11 @@ class TestBotDetectionSites:
def test_fingerprintjs(self, page):
"""FingerprintJS — must not be blocked, should see flight data."""
page.goto("https://demo.fingerprint.com/web-scraping", wait_until="domcontentloaded", timeout=30000)
page.wait_for_timeout(8000)
time.sleep(8)
try:
page.click("button:has-text('Search')", timeout=5000)
page.wait_for_timeout(5000)
time.sleep(5)
except Exception:
pass
@@ -205,7 +206,7 @@ class TestBotDetectionSites:
wait_until="domcontentloaded",
timeout=60000,
)
page.wait_for_timeout(8000)
time.sleep(8)
results = page.evaluate("""() => {
const text = document.body.innerText;