diff --git a/README.md b/README.md index 292443a..383ba62 100644 --- a/README.md +++ b/README.md @@ -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.1–0.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?** diff --git a/cloakbrowser/_version.py b/cloakbrowser/_version.py index 3ced358..b5fdc75 100644 --- a/cloakbrowser/_version.py +++ b/cloakbrowser/_version.py @@ -1 +1 @@ -__version__ = "0.2.1" +__version__ = "0.2.2" diff --git a/examples/recaptcha_score.py b/examples/recaptcha_score.py index a75be51..c1c4fd5 100644 --- a/examples/recaptcha_score.py +++ b/examples/recaptcha_score.py @@ -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() diff --git a/examples/stealth_test.py b/examples/stealth_test.py index 60c89e4..c5ca2b8 100644 --- a/examples/stealth_test.py +++ b/examples/stealth_test.py @@ -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; diff --git a/js/README.md b/js/README.md index 35f8592..dfc1af7 100644 --- a/js/README.md +++ b/js/README.md @@ -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.1–0.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) diff --git a/js/package.json b/js/package.json index 2539720..0fbcc6b 100644 --- a/js/package.json +++ b/js/package.json @@ -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", diff --git a/tests/test_stealth.py b/tests/test_stealth.py index d5edb2c..d2eb606 100644 --- a/tests/test_stealth.py +++ b/tests/test_stealth.py @@ -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;