mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23ae521832 | ||
|
|
c2eb0ef21a | ||
|
|
2d7894ad68 |
@@ -45,3 +45,7 @@ AGENTS.md
|
|||||||
|
|
||||||
# Private docs (launch posts, strategy)
|
# Private docs (launch posts, strategy)
|
||||||
docs/
|
docs/
|
||||||
|
|
||||||
|
# Release scripts
|
||||||
|
publish.sh
|
||||||
|
.env
|
||||||
|
|||||||
+5
-2
@@ -1,11 +1,14 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
|
|
||||||
# Playwright system deps
|
# Chromium system deps (matches fingerprint-chromium 142+ requirements)
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
|
||||||
libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 \
|
libdbus-1-3 libdrm2 libxkbcommon0 libatspi2.0-0 libxcomposite1 \
|
||||||
libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \
|
libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \
|
||||||
libcairo2 libasound2 libx11-xcb1 \
|
libcairo2 libasound2 libx11-xcb1 libfontconfig1 libx11-6 \
|
||||||
|
libxcb1 libxext6 libxshmfence1 \
|
||||||
|
libglib2.0-0 libgtk-3-0 libpangocairo-1.0-0 libcairo-gobject2 \
|
||||||
|
libgdk-pixbuf-2.0-0 libxss1 libxtst6 fonts-liberation \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -244,12 +244,64 @@ See the [`examples/`](examples/) directory:
|
|||||||
| Linux x64 binary | ✅ Released |
|
| Linux x64 binary | ✅ Released |
|
||||||
| macOS arm64 (Apple Silicon) | 🔜 In progress |
|
| macOS arm64 (Apple Silicon) | 🔜 In progress |
|
||||||
| Chromium 145 build | 🔜 In progress |
|
| Chromium 145 build | 🔜 In progress |
|
||||||
|
| JavaScript/Puppeteer support (`cloakbrowser-js`) | 📋 Planned |
|
||||||
| Fingerprint rotation per session | 📋 Planned |
|
| Fingerprint rotation per session | 📋 Planned |
|
||||||
| Built-in proxy rotation | 📋 Planned |
|
| Built-in proxy rotation | 📋 Planned |
|
||||||
| Windows support | 📋 Planned |
|
| Windows support | 📋 Planned |
|
||||||
|
|
||||||
> ⭐ **Star this repo** to get notified when Chromium 145 and macOS builds drop.
|
> ⭐ **Star this repo** to get notified when Chromium 145 and macOS builds drop.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
A ready-to-use [`Dockerfile`](Dockerfile) is included. It installs system deps, the package, and pre-downloads the stealth binary during build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t cloakbrowser .
|
||||||
|
docker run --rm cloakbrowser python examples/basic.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The key steps in the Dockerfile:
|
||||||
|
1. **System deps** — Chromium requires ~15 shared libraries (`libnss3`, `libgbm1`, etc.)
|
||||||
|
2. **`pip install .`** — installs CloakBrowser + Playwright
|
||||||
|
3. **`ensure_binary()`** — downloads the stealth Chromium binary at build time (~200MB), so containers start instantly
|
||||||
|
|
||||||
|
To extend with your own script, just add a `COPY` + `CMD`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM cloakbrowser
|
||||||
|
COPY your_script.py /app/
|
||||||
|
CMD ["python", "your_script.py"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** If you run CloakBrowser inside a web server with uvloop (e.g., `uvicorn[standard]`), use `--loop asyncio` to avoid subprocess pipe hangs.
|
||||||
|
|
||||||
|
## Headed Mode (for aggressive bot detection)
|
||||||
|
|
||||||
|
Some sites using advanced bot detection (e.g., DataDome, Cloudflare Turnstile) can detect headless mode even with our C++ patches. For these sites, run in **headed mode** with a virtual display:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Xvfb (virtual framebuffer)
|
||||||
|
sudo apt install xvfb
|
||||||
|
|
||||||
|
# Start virtual display
|
||||||
|
Xvfb :99 -screen 0 1920x1080x24 &
|
||||||
|
export DISPLAY=:99
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
from cloakbrowser import launch
|
||||||
|
|
||||||
|
# Headed mode + residential proxy for maximum stealth
|
||||||
|
browser = launch(headless=False, proxy="http://your-residential-proxy:port")
|
||||||
|
page = browser.new_page()
|
||||||
|
page.goto("https://heavily-protected-site.com") # passes DataDome, etc.
|
||||||
|
browser.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs a real headed browser rendered on a virtual display — no physical monitor needed. Combined with a residential proxy, this passes even the most aggressive detection services.
|
||||||
|
|
||||||
|
> **Tip:** Datacenter IPs are often flagged by IP reputation databases regardless of browser fingerprint. For sites with strict bot detection, a residential proxy makes the difference.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Binary download fails / timeout**
|
**Binary download fails / timeout**
|
||||||
@@ -264,20 +316,6 @@ You do NOT need `playwright install chromium`. CloakBrowser downloads its own bi
|
|||||||
playwright install-deps chromium
|
playwright install-deps chromium
|
||||||
```
|
```
|
||||||
|
|
||||||
**Missing system libraries on Linux (Docker)**
|
|
||||||
If you see errors about `libgbm`, `libnss3`, etc.:
|
|
||||||
```bash
|
|
||||||
apt-get install -y libgbm1 libnss3 libatk-bridge2.0-0 libxkbcommon0 libgtk-3-0
|
|
||||||
```
|
|
||||||
Or use `playwright install-deps chromium` which handles this automatically.
|
|
||||||
|
|
||||||
**Pre-download binary in Docker**
|
|
||||||
```python
|
|
||||||
# In your Dockerfile or entrypoint:
|
|
||||||
from cloakbrowser import ensure_binary
|
|
||||||
ensure_binary()
|
|
||||||
```
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Q: Is this legal?**
|
**Q: Is this legal?**
|
||||||
@@ -293,7 +331,7 @@ A: Possibly. Bot detection is an arms race. Source-level patches are harder to d
|
|||||||
A: Yes. Pass `proxy="http://user:pass@host:port"` to `launch()`.
|
A: Yes. Pass `proxy="http://user:pass@host:port"` to `launch()`.
|
||||||
|
|
||||||
**Q: Can I use this with Docker?**
|
**Q: Can I use this with Docker?**
|
||||||
A: Yes. Use `ensure_binary()` in your Dockerfile to pre-download the binary during image build.
|
A: Yes. A ready-to-use Dockerfile is included — see the [Docker](#docker) section above.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .browser import launch, launch_async, launch_context
|
from .browser import launch, launch_async, launch_context
|
||||||
from .config import CHROMIUM_VERSION, DEFAULT_STEALTH_ARGS
|
from .config import CHROMIUM_VERSION, get_default_stealth_args
|
||||||
from .download import binary_info, clear_cache, ensure_binary
|
from .download import binary_info, clear_cache, ensure_binary
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
|
|
||||||
@@ -24,6 +24,6 @@ __all__ = [
|
|||||||
"clear_cache",
|
"clear_cache",
|
||||||
"binary_info",
|
"binary_info",
|
||||||
"CHROMIUM_VERSION",
|
"CHROMIUM_VERSION",
|
||||||
"DEFAULT_STEALTH_ARGS",
|
"get_default_stealth_args",
|
||||||
"__version__",
|
"__version__",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.2"
|
__version__ = "0.1.5"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .config import DEFAULT_STEALTH_ARGS
|
from .config import get_default_stealth_args
|
||||||
from .download import ensure_binary
|
from .download import ensure_binary
|
||||||
|
|
||||||
logger = logging.getLogger("cloakbrowser")
|
logger = logging.getLogger("cloakbrowser")
|
||||||
@@ -63,6 +63,7 @@ def launch(
|
|||||||
executable_path=binary_path,
|
executable_path=binary_path,
|
||||||
headless=headless,
|
headless=headless,
|
||||||
args=chrome_args,
|
args=chrome_args,
|
||||||
|
ignore_default_args=["--enable-automation"],
|
||||||
**_build_proxy_kwargs(proxy),
|
**_build_proxy_kwargs(proxy),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
@@ -123,6 +124,7 @@ async def launch_async(
|
|||||||
executable_path=binary_path,
|
executable_path=binary_path,
|
||||||
headless=headless,
|
headless=headless,
|
||||||
args=chrome_args,
|
args=chrome_args,
|
||||||
|
ignore_default_args=["--enable-automation"],
|
||||||
**_build_proxy_kwargs(proxy),
|
**_build_proxy_kwargs(proxy),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
@@ -209,7 +211,7 @@ def _build_args(stealth_args: bool, extra_args: list[str] | None) -> list[str]:
|
|||||||
"""Combine stealth args with user-provided args."""
|
"""Combine stealth args with user-provided args."""
|
||||||
result = []
|
result = []
|
||||||
if stealth_args:
|
if stealth_args:
|
||||||
result.extend(DEFAULT_STEALTH_ARGS)
|
result.extend(get_default_stealth_args())
|
||||||
if extra_args:
|
if extra_args:
|
||||||
result.extend(extra_args)
|
result.extend(extra_args)
|
||||||
return result
|
return result
|
||||||
|
|||||||
+13
-10
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import random
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
@@ -17,16 +18,18 @@ CHROMIUM_VERSION = "142.0.7444.175"
|
|||||||
# Default stealth arguments passed to the patched Chromium binary.
|
# Default stealth arguments passed to the patched Chromium binary.
|
||||||
# These activate source-level fingerprint patches compiled into the binary.
|
# These activate source-level fingerprint patches compiled into the binary.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
DEFAULT_STEALTH_ARGS: list[str] = [
|
def get_default_stealth_args() -> list[str]:
|
||||||
"--no-sandbox",
|
"""Build stealth args with a random fingerprint seed per launch."""
|
||||||
"--disable-blink-features=AutomationControlled",
|
seed = random.randint(10000, 99999)
|
||||||
# Fingerprint overrides (activate compiled C++ patches)
|
return [
|
||||||
"--fingerprint=98765",
|
"--no-sandbox",
|
||||||
"--fingerprint-platform=windows",
|
"--disable-blink-features=AutomationControlled",
|
||||||
"--fingerprint-hardware-concurrency=8",
|
f"--fingerprint={seed}",
|
||||||
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
"--fingerprint-platform=windows",
|
||||||
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 4070",
|
"--fingerprint-hardware-concurrency=8",
|
||||||
]
|
"--fingerprint-gpu-vendor=NVIDIA Corporation",
|
||||||
|
"--fingerprint-gpu-renderer=NVIDIA GeForce RTX 3070",
|
||||||
|
]
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Platform detection
|
# Platform detection
|
||||||
|
|||||||
+241
-27
@@ -1,57 +1,271 @@
|
|||||||
"""Run stealth tests against major bot detection services.
|
"""Run stealth tests against major bot detection services.
|
||||||
|
|
||||||
Tests cloakbrowser against multiple detection sites and reports results.
|
Tests cloakbrowser against multiple detection sites, extracts pass/fail
|
||||||
|
verdicts via JS evaluation, and reports results with screenshots.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python examples/stealth_test.py
|
||||||
|
python examples/stealth_test.py --headed # watch in real-time
|
||||||
|
python examples/stealth_test.py --no-screenshots
|
||||||
|
python examples/stealth_test.py --proxy http://10.50.96.5:8888
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
from cloakbrowser import launch
|
from cloakbrowser import launch
|
||||||
|
|
||||||
|
HEADED = "--headed" in sys.argv
|
||||||
|
SCREENSHOTS = "--no-screenshots" not in sys.argv
|
||||||
|
PROXY = None
|
||||||
|
for i, arg in enumerate(sys.argv):
|
||||||
|
if arg == "--proxy" and i + 1 < len(sys.argv):
|
||||||
|
PROXY = sys.argv[i + 1]
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
results = page.evaluate("""() => {
|
||||||
|
const rows = document.querySelectorAll('table tr');
|
||||||
|
const data = {};
|
||||||
|
rows.forEach(r => {
|
||||||
|
const cells = r.querySelectorAll('td');
|
||||||
|
if (cells.length >= 2) {
|
||||||
|
const key = cells[0].innerText.trim();
|
||||||
|
const val = cells[1].innerText.trim();
|
||||||
|
const cls = cells[1].className || '';
|
||||||
|
data[key] = {value: val, passed: !cls.includes('failed')};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}""")
|
||||||
|
|
||||||
|
failed = [k for k, v in results.items() if not v["passed"]]
|
||||||
|
total = len(results)
|
||||||
|
passed = total - len(failed)
|
||||||
|
return {"passed": passed, "total": total, "failed": failed}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Site outputs JSON blocks in page text, not HTML tables
|
||||||
|
results = page.evaluate("""() => {
|
||||||
|
const text = document.body.innerText;
|
||||||
|
const okMatches = text.match(/"\\w+":\\s*"OK"/g) || [];
|
||||||
|
const failMatches = text.match(/"\\w+":\\s*"FAIL"/g) || [];
|
||||||
|
const failedTests = failMatches.map(m => m.match(/"(\\w+)"/)[1]);
|
||||||
|
return {
|
||||||
|
passed: okMatches.length,
|
||||||
|
failed: failMatches.length,
|
||||||
|
failedTests,
|
||||||
|
total: okMatches.length + failMatches.length
|
||||||
|
};
|
||||||
|
}""")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
results = page.evaluate("""() => {
|
||||||
|
const items = document.querySelectorAll('[class*="result"], [class*="item"], [class*="check"]');
|
||||||
|
let normal = 0, abnormal = 0;
|
||||||
|
const text = document.body.innerText;
|
||||||
|
// Count "Normal" vs "Abnormal" verdicts
|
||||||
|
const normalMatches = text.match(/Normal/g);
|
||||||
|
const abnormalMatches = text.match(/Abnormal/g);
|
||||||
|
return {
|
||||||
|
normal: normalMatches ? normalMatches.length : 0,
|
||||||
|
abnormal: abnormalMatches ? abnormalMatches.length : 0,
|
||||||
|
pageText: text.substring(0, 500)
|
||||||
|
};
|
||||||
|
}""")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
results = page.evaluate("""() => {
|
||||||
|
const text = document.body.innerText;
|
||||||
|
// Site outputs JSON with "isBot": false and detail checks
|
||||||
|
const botMatch = text.match(/"isBot":\\s*(true|false)/);
|
||||||
|
const isBot = botMatch ? botMatch[1] === 'true' : null;
|
||||||
|
const checks = {};
|
||||||
|
const patterns = [
|
||||||
|
'isBot', 'hasBotUserAgent', 'hasWebdriverTrue',
|
||||||
|
'isHeadlessChrome', 'isAutomatedWithCDP', 'hasSuspiciousWeakSignals',
|
||||||
|
'isPlaywright', 'hasInconsistentChromeObject'
|
||||||
|
];
|
||||||
|
patterns.forEach(p => {
|
||||||
|
const match = text.match(new RegExp('"' + p + '":\\s*(true|false)'));
|
||||||
|
if (match) checks[p] = match[1] === 'true';
|
||||||
|
});
|
||||||
|
return {isBot, checks};
|
||||||
|
}""")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def test_fingerprintjs(page):
|
||||||
|
"""demo.fingerprint.com/web-scraping — industry-standard bot detection."""
|
||||||
|
page.goto("https://demo.fingerprint.com/web-scraping", wait_until="networkidle", timeout=30000)
|
||||||
|
page.wait_for_timeout(5000)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
results = page.evaluate("""() => {
|
||||||
|
const text = document.body.innerText;
|
||||||
|
// Bots see error messages; humans see flight prices
|
||||||
|
const hasFlights = text.includes('Price per adult') || text.includes('$');
|
||||||
|
const isBlocked = text.includes('request was blocked') || text.includes('bot visit detected');
|
||||||
|
return {passed: hasFlights && !isBlocked, isBlocked, hasFlights};
|
||||||
|
}""")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def test_recaptcha(page):
|
||||||
|
"""recaptcha-demo.appspot.com — Google's official reCAPTCHA v3 score."""
|
||||||
|
page.goto(
|
||||||
|
"https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php",
|
||||||
|
wait_until="networkidle",
|
||||||
|
timeout=30000,
|
||||||
|
)
|
||||||
|
# Page auto-submits via grecaptcha.execute() — wait for backend response
|
||||||
|
page.wait_for_timeout(8000)
|
||||||
|
|
||||||
|
results = page.evaluate("""() => {
|
||||||
|
const text = document.body.innerText;
|
||||||
|
// Score appears in JSON response block: "score": 0.9
|
||||||
|
const scoreMatch = text.match(/"score":\\s*(\\d+\\.\\d+)/);
|
||||||
|
return {
|
||||||
|
score: scoreMatch ? parseFloat(scoreMatch[1]) : null,
|
||||||
|
pageText: text.substring(0, 500)
|
||||||
|
};
|
||||||
|
}""")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
TESTS = [
|
TESTS = [
|
||||||
|
{
|
||||||
|
"name": "bot.sannysoft.com",
|
||||||
|
"url": "https://bot.sannysoft.com",
|
||||||
|
"runner": test_bot_sannysoft,
|
||||||
|
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
||||||
|
+ (f" (FAILED: {', '.join(r['failed'])})" if r["failed"] else " — ALL GREEN"),
|
||||||
|
"pass": lambda r: len(r["failed"]) == 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "bot.incolumitas.com",
|
"name": "bot.incolumitas.com",
|
||||||
"url": "https://bot.incolumitas.com",
|
"url": "https://bot.incolumitas.com",
|
||||||
"check": "Bot detection analysis",
|
"runner": test_bot_incolumitas,
|
||||||
|
"verdict": lambda r: f"{r['passed']}/{r['total']} passed"
|
||||||
|
+ (f" (FAILED: {', '.join(r.get('failedTests', []))})" if r.get("failed", 0) > 0 else " — ALL GREEN"),
|
||||||
|
"pass": lambda r: r.get("failed", 0) <= 1, # fpscanner.WEBDRIVER false positive expected (all builds)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "BrowserScan",
|
"name": "BrowserScan",
|
||||||
"url": "https://www.browserscan.net/bot-detection",
|
"url": "https://www.browserscan.net/bot-detection",
|
||||||
"check": "Bot detection status",
|
"runner": test_browserscan,
|
||||||
|
"verdict": lambda r: f"Normal: {r['normal']}, Abnormal: {r['abnormal']}",
|
||||||
|
"pass": lambda r: r.get("abnormal", 1) == 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "deviceandbrowserinfo.com",
|
"name": "deviceandbrowserinfo.com",
|
||||||
"url": "https://deviceandbrowserinfo.com/are_you_a_bot",
|
"url": "https://deviceandbrowserinfo.com/are_you_a_bot",
|
||||||
"check": "isBot flag",
|
"runner": test_deviceandbrowserinfo,
|
||||||
|
"verdict": lambda r: f"isBot: {r.get('isBot', 'unknown')}"
|
||||||
|
+ (f" checks: {json.dumps(r.get('checks', {}))}" if r.get("checks") else ""),
|
||||||
|
"pass": lambda r: not r.get("isBot", True),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "FingerprintJS",
|
"name": "FingerprintJS",
|
||||||
"url": "https://demo.fingerprint.com/web-scraping",
|
"url": "https://demo.fingerprint.com/web-scraping",
|
||||||
"check": "Bot detection result",
|
"runner": test_fingerprintjs,
|
||||||
|
"verdict": lambda r: "PASSED (flights shown)" if r.get("passed") else "BLOCKED" if r.get("isBlocked") else "NO FLIGHTS",
|
||||||
|
"pass": lambda r: r.get("passed", False),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reCAPTCHA v3 (Google)",
|
||||||
|
"url": "https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php",
|
||||||
|
"runner": test_recaptcha,
|
||||||
|
"verdict": lambda r: f"Score: {r.get('score', 'N/A')}",
|
||||||
|
"pass": lambda r: (r.get("score") or 0) >= 0.7,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
browser = launch(headless=True)
|
|
||||||
page = browser.new_page()
|
|
||||||
|
|
||||||
print("=" * 60)
|
def main():
|
||||||
print("CloakBrowser Stealth Test Suite")
|
print("=" * 60)
|
||||||
print("=" * 60)
|
print("CloakBrowser Stealth Test Suite")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"Mode: {'headed' if HEADED else 'headless'}")
|
||||||
|
print(f"Screenshots: {'on' if SCREENSHOTS else 'off'}")
|
||||||
|
print(f"Proxy: {PROXY or 'none'}")
|
||||||
|
print()
|
||||||
|
|
||||||
for test in TESTS:
|
browser = launch(headless=not HEADED, proxy=PROXY)
|
||||||
print(f"\n--- {test['name']} ---")
|
page = browser.new_page()
|
||||||
print(f"URL: {test['url']}")
|
|
||||||
try:
|
|
||||||
page.goto(test["url"], wait_until="networkidle", timeout=30000)
|
|
||||||
page.wait_for_timeout(3000)
|
|
||||||
|
|
||||||
# Screenshot each test
|
results_summary = []
|
||||||
filename = f"stealth_test_{test['name'].replace('.', '_').replace(' ', '_')}.png"
|
|
||||||
page.screenshot(path=filename)
|
|
||||||
print(f"Screenshot: {filename}")
|
|
||||||
print(f"Title: {page.title()}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
browser.close()
|
for test in TESTS:
|
||||||
|
name = test["name"]
|
||||||
|
print(f"--- {name} ---")
|
||||||
|
print(f"URL: {test['url']}")
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
try:
|
||||||
print("Tests complete. Check screenshots for results.")
|
result = test["runner"](page)
|
||||||
print("=" * 60)
|
passed = test["pass"](result)
|
||||||
|
verdict = test["verdict"](result)
|
||||||
|
status = "PASS" if passed else "FAIL"
|
||||||
|
results_summary.append((name, status, verdict))
|
||||||
|
|
||||||
|
print(f"Result: [{status}] {verdict}")
|
||||||
|
|
||||||
|
if SCREENSHOTS:
|
||||||
|
filename = f"stealth_test_{name.replace('.', '_').replace(' ', '_').replace('/', '_')}.png"
|
||||||
|
page.screenshot(path=filename)
|
||||||
|
print(f"Screenshot: {filename}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results_summary.append((name, "ERROR", str(e)))
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
|
||||||
|
# Summary table
|
||||||
|
print("=" * 60)
|
||||||
|
print("RESULTS SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
for name, status, verdict in results_summary:
|
||||||
|
icon = {"PASS": "+", "FAIL": "!", "ERROR": "x"}[status]
|
||||||
|
print(f" [{icon}] {name}: {verdict}")
|
||||||
|
|
||||||
|
passed_count = sum(1 for _, s, _ in results_summary if s == "PASS")
|
||||||
|
total = len(results_summary)
|
||||||
|
print(f"\n {passed_count}/{total} tests passed")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
return 0 if passed_count == total else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|||||||
Reference in New Issue
Block a user