mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
feat: cloakbrowser v0.1.0 — stealth Chromium wrapper for Playwright
Drop-in Playwright replacement with source-level fingerprint patches. Auto-downloads patched Chromium 142 from GitHub Releases on first use. 17/17 stealth tests passing (reCAPTCHA 0.9, Cloudflare Turnstile, BrowserScan).
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""Basic launch tests for cloakbrowser."""
|
||||
|
||||
import pytest
|
||||
from cloakbrowser import launch, launch_async, binary_info, CHROMIUM_VERSION
|
||||
|
||||
|
||||
def test_binary_info():
|
||||
"""binary_info() returns expected structure."""
|
||||
info = binary_info()
|
||||
assert "version" in info
|
||||
assert "platform" in info
|
||||
assert "binary_path" in info
|
||||
assert "installed" in info
|
||||
assert info["version"] == CHROMIUM_VERSION
|
||||
|
||||
|
||||
def test_launch_and_close():
|
||||
"""Can launch browser and close it."""
|
||||
browser = launch(headless=True)
|
||||
assert browser.is_connected()
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_launch_new_page():
|
||||
"""Can create a page and navigate."""
|
||||
browser = launch(headless=True)
|
||||
page = browser.new_page()
|
||||
page.goto("https://example.com")
|
||||
assert "Example Domain" in page.title()
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_launch_with_extra_args():
|
||||
"""Can pass extra Chrome args."""
|
||||
browser = launch(headless=True, args=["--disable-gpu"])
|
||||
page = browser.new_page()
|
||||
page.goto("https://example.com")
|
||||
assert page.title()
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_webdriver_flag():
|
||||
"""navigator.webdriver should be false (patched)."""
|
||||
browser = launch(headless=True)
|
||||
page = browser.new_page()
|
||||
page.goto("https://example.com")
|
||||
webdriver = page.evaluate("navigator.webdriver")
|
||||
assert webdriver is False, f"navigator.webdriver should be false, got {webdriver}"
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_chrome_object_exists():
|
||||
"""window.chrome should exist (Playwright leaks undefined)."""
|
||||
browser = launch(headless=True)
|
||||
page = browser.new_page()
|
||||
page.goto("https://example.com")
|
||||
chrome_exists = page.evaluate("typeof window.chrome")
|
||||
assert chrome_exists == "object", f"window.chrome should be 'object', got '{chrome_exists}'"
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_plugins_count():
|
||||
"""navigator.plugins should have entries (Playwright has 0)."""
|
||||
browser = launch(headless=True)
|
||||
page = browser.new_page()
|
||||
page.goto("https://example.com")
|
||||
plugins = page.evaluate("navigator.plugins.length")
|
||||
assert plugins > 0, f"Expected plugins > 0, got {plugins}"
|
||||
browser.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_launch_async():
|
||||
"""Async launch works."""
|
||||
browser = await launch_async(headless=True)
|
||||
assert browser.is_connected()
|
||||
page = await browser.new_page()
|
||||
await page.goto("https://example.com")
|
||||
title = await page.title()
|
||||
assert "Example Domain" in title
|
||||
await browser.close()
|
||||
@@ -0,0 +1,108 @@
|
||||
"""Stealth detection tests for cloakbrowser.
|
||||
|
||||
These tests verify that the stealth Chromium binary passes common
|
||||
bot detection checks. They require network access.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from cloakbrowser import launch
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def browser():
|
||||
"""Shared browser instance for stealth tests."""
|
||||
b = launch(headless=True)
|
||||
yield b
|
||||
b.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page(browser):
|
||||
"""Fresh page for each test."""
|
||||
p = browser.new_page()
|
||||
yield p
|
||||
p.close()
|
||||
|
||||
|
||||
class TestWebDriverDetection:
|
||||
"""Tests for WebDriver/automation detection signals."""
|
||||
|
||||
def test_navigator_webdriver_false(self, page):
|
||||
"""navigator.webdriver must be false."""
|
||||
page.goto("https://example.com")
|
||||
assert page.evaluate("navigator.webdriver") is False
|
||||
|
||||
def test_no_headless_chrome_ua(self, page):
|
||||
"""User agent must not contain 'HeadlessChrome'."""
|
||||
page.goto("https://example.com")
|
||||
ua = page.evaluate("navigator.userAgent")
|
||||
assert "HeadlessChrome" not in ua
|
||||
assert "Chrome/" in ua
|
||||
|
||||
def test_window_chrome_exists(self, page):
|
||||
"""window.chrome must be an object (not undefined)."""
|
||||
page.goto("https://example.com")
|
||||
assert page.evaluate("typeof window.chrome") == "object"
|
||||
|
||||
def test_plugins_present(self, page):
|
||||
"""Must have browser plugins (real Chrome has 5)."""
|
||||
page.goto("https://example.com")
|
||||
count = page.evaluate("navigator.plugins.length")
|
||||
assert count >= 1, f"Expected plugins, got {count}"
|
||||
|
||||
def test_languages_present(self, page):
|
||||
"""navigator.languages must be populated."""
|
||||
page.goto("https://example.com")
|
||||
langs = page.evaluate("navigator.languages")
|
||||
assert len(langs) >= 1
|
||||
|
||||
def test_cdp_not_detected(self, page):
|
||||
"""Chrome DevTools Protocol should not be detectable."""
|
||||
page.goto("https://example.com")
|
||||
# Common CDP detection: check for Runtime.evaluate artifacts
|
||||
has_cdp = page.evaluate("""
|
||||
() => {
|
||||
try {
|
||||
// Check common CDP leak: window.cdc_
|
||||
const keys = Object.keys(window);
|
||||
return keys.some(k => k.startsWith('cdc_') || k.startsWith('__webdriver'));
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
""")
|
||||
assert has_cdp is False
|
||||
|
||||
|
||||
class TestBotDetectionSites:
|
||||
"""Live tests against bot detection services.
|
||||
|
||||
These require network access and may be slow.
|
||||
Mark with pytest -m slow to skip in CI.
|
||||
"""
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_bot_incolumitas(self, page):
|
||||
"""bot.incolumitas.com should detect minimal flags."""
|
||||
page.goto("https://bot.incolumitas.com", timeout=30000)
|
||||
page.wait_for_timeout(5000)
|
||||
# Check that we're not immediately flagged
|
||||
title = page.title()
|
||||
assert title # Page loaded successfully
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_browserscan(self, page):
|
||||
"""BrowserScan bot detection should show NORMAL."""
|
||||
page.goto("https://www.browserscan.net/bot-detection", timeout=30000)
|
||||
page.wait_for_timeout(5000)
|
||||
title = page.title()
|
||||
assert title # Page loaded
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_device_and_browser_info(self, page):
|
||||
"""deviceandbrowserinfo.com should report isBot: false."""
|
||||
page.goto("https://deviceandbrowserinfo.com/are_you_a_bot", timeout=30000)
|
||||
page.wait_for_timeout(5000)
|
||||
content = page.content()
|
||||
# The page shows bot detection results
|
||||
assert "deviceandbrowserinfo" in page.url.lower()
|
||||
Reference in New Issue
Block a user