test: drop the 15 hand-rolled test_botd_* (real BotD now runs on CI)
These mirrored BotD's individual detectors (webdriver/appVersion/userAgent/ functionBind/productSub/process/evalLength/languages/plugins/mimeTypes/ distinctive-props/documentAttributes/windowSize/webGL) — our reverse-engineering of BotD from before we ran the real library. Now that test_detectors_e2e.py runs the actual @fingerprintjs/botd against the binary on CI (asserts bot===false), they're redundant. Kept the complementary/unique tests: fpjs-surface, pinning, sannysoft + fpscanner (which mimic detectors we do NOT run, so not covered by BotD/FpJS), and the determinism/consistency suite.
This commit is contained in:
@@ -71,170 +71,6 @@ def _ev(page, expr):
|
|||||||
return page.evaluate(expr)
|
return page.evaluate(expr)
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# BotD detectors (github.com/fingerprintjs/BotD/tree/main/src/detectors)
|
|
||||||
# Each detector becomes one pytest. The failure name maps to the BotKind
|
|
||||||
# constant BotD would emit on the wire.
|
|
||||||
# ===========================================================================
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_webdriver_property_is_falsey(page):
|
|
||||||
"""BotD: navigator.webdriver === true → HeadlessChrome verdict."""
|
|
||||||
assert not _ev(page, "navigator.webdriver"), (
|
|
||||||
"navigator.webdriver is truthy — instant HeadlessChrome verdict"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_app_version_no_headless_token(page):
|
|
||||||
"""BotD detectAppVersion: /headless|electron|slimerjs/i in appVersion."""
|
|
||||||
av = _ev(page, "navigator.appVersion")
|
|
||||||
for token in ("headless", "electron", "slimerjs"):
|
|
||||||
assert not re.search(token, av, re.I), (
|
|
||||||
f"navigator.appVersion contains {token!r}: {av!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_user_agent_no_headless_or_selenium_token(page):
|
|
||||||
"""BotD: /headless|selenium|phantom/i in UA."""
|
|
||||||
ua = _ev(page, "navigator.userAgent")
|
|
||||||
for token in ("headless", "selenium", "phantom"):
|
|
||||||
assert not re.search(token, ua, re.I), (
|
|
||||||
f"navigator.userAgent contains {token!r}: {ua!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_function_bind_is_function(page):
|
|
||||||
"""BotD detectFunctionBind: missing Function.prototype.bind = PhantomJS."""
|
|
||||||
assert _ev(page, "typeof Function.prototype.bind === 'function'")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_product_sub_is_gecko_value(page):
|
|
||||||
"""BotD detectProductSub: Firefox must return '20100101'; '20030107'
|
|
||||||
on a Firefox UA = Chrome-stub leaked under spoof."""
|
|
||||||
assert _ev(page, "navigator.productSub") == "20100101", (
|
|
||||||
"navigator.productSub must be '20100101' on Firefox"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_no_process_global(page):
|
|
||||||
"""BotD detectProcess: window.process indicates Electron."""
|
|
||||||
assert not _ev(page,
|
|
||||||
"typeof window.process !== 'undefined' && "
|
|
||||||
"window.process.type === 'renderer'"
|
|
||||||
)
|
|
||||||
assert not _ev(page,
|
|
||||||
"typeof window.process !== 'undefined' && "
|
|
||||||
"window.process.versions != null && "
|
|
||||||
"typeof window.process.versions.electron !== 'undefined'"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_eval_length_matches_engine(page):
|
|
||||||
"""BotD detectEvalLengthInconsistency: `eval.toString().length` must be
|
|
||||||
37 on Gecko (33 on Chromium, 39 on IE). Mismatch = engine spoof."""
|
|
||||||
assert _ev(page, "eval.toString().length") == 37
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_languages_array_non_empty(page):
|
|
||||||
"""BotD detectLanguagesLengthInconsistency: empty navigator.languages
|
|
||||||
is the classic HeadlessChrome tell."""
|
|
||||||
assert _ev(page, "navigator.languages.length") > 0
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_plugins_instance_of_PluginArray(page):
|
|
||||||
"""BotD detectPluginsArray: navigator.plugins must be a real PluginArray."""
|
|
||||||
assert _ev(page, "navigator.plugins instanceof PluginArray")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_mime_types_consistent_prototype_chain(page):
|
|
||||||
"""BotD areMimeTypesConsistent: navigator.mimeTypes and each entry
|
|
||||||
must have proper prototype chain. Spoofers using plain arrays fail."""
|
|
||||||
consistent = _ev(page, """() => {
|
|
||||||
if (typeof navigator.mimeTypes === 'undefined' ||
|
|
||||||
typeof MimeTypeArray === 'undefined') return false;
|
|
||||||
let ok = Object.getPrototypeOf(navigator.mimeTypes) === MimeTypeArray.prototype;
|
|
||||||
for (let i = 0; i < navigator.mimeTypes.length; i++) {
|
|
||||||
ok = ok && Object.getPrototypeOf(navigator.mimeTypes[i]) === MimeType.prototype;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}""")
|
|
||||||
assert consistent, "navigator.mimeTypes prototype chain inconsistent"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_no_distinctive_window_props(page):
|
|
||||||
"""BotD checkDistinctiveProperties: scan window for automation globals."""
|
|
||||||
DISTINCTIVE = [
|
|
||||||
"awesomium", "RunPerfTest", "CefSharp", "fmget_targets", "geb",
|
|
||||||
"__nightmare", "nightmare", "__phantomas", "callPhantom", "_phantom",
|
|
||||||
"wdioElectron", "__webdriverFunc", "_WEBDRIVER_ELEM_CACHE",
|
|
||||||
"ChromeDriverw", "domAutomation", "domAutomationController",
|
|
||||||
]
|
|
||||||
leaks = [n for n in DISTINCTIVE
|
|
||||||
if _ev(page, f"typeof window[{n!r}] !== 'undefined'")]
|
|
||||||
assert not leaks, f"Distinctive bot globals leaked: {leaks}"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_no_distinctive_document_props(page):
|
|
||||||
"""BotD: document-side automation globals (webdriver/selenium/cdc)."""
|
|
||||||
DOC_LEAKS = [
|
|
||||||
"__webdriver_evaluate", "__selenium_evaluate",
|
|
||||||
"__webdriver_script_function", "__webdriver_script_func",
|
|
||||||
"__webdriver_script_fn", "__fxdriver_evaluate",
|
|
||||||
"__driver_unwrapped", "__webdriver_unwrapped",
|
|
||||||
"__driver_evaluate", "__selenium_unwrapped",
|
|
||||||
"__fxdriver_unwrapped",
|
|
||||||
"$cdc_asdjflasutopfhvcZLmcf", "$cdc_asdjflasutopfhvcZLmcfl_",
|
|
||||||
"$chrome_asyncScriptInfo", "__$webdriverAsyncExecutor",
|
|
||||||
]
|
|
||||||
leaks = [n for n in DOC_LEAKS
|
|
||||||
if _ev(page, f"typeof document[{n!r}] !== 'undefined'")]
|
|
||||||
assert not leaks, f"document carries automation property names: {leaks}"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_document_html_attributes_clean(page):
|
|
||||||
"""BotD detectDocumentAttributes: html element attrs contain 'selenium'
|
|
||||||
/ 'webdriver' / 'driver' → Selenium verdict."""
|
|
||||||
attrs = _ev(page,
|
|
||||||
"Array.from(document.documentElement.attributes).map(a => a.name + '=' + a.value)")
|
|
||||||
bad = [a for a in attrs if any(t in a.lower()
|
|
||||||
for t in ("selenium", "webdriver", "driver"))]
|
|
||||||
assert not bad, f"HTML attributes contain bot tokens: {bad}"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_window_size_nonzero(page):
|
|
||||||
"""BotD detectWindowSize: headless without window manager → 0x0."""
|
|
||||||
ow = _ev(page, "window.outerWidth")
|
|
||||||
oh = _ev(page, "window.outerHeight")
|
|
||||||
assert ow > 0 and oh > 0, (
|
|
||||||
f"outerWidth/Height = {ow}/{oh} — headless without window manager"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.e2e
|
|
||||||
def test_botd_webgl_debug_renderer_info_available(page):
|
|
||||||
"""BotD detectWebGL: WEBGL_debug_renderer_info extension must exist."""
|
|
||||||
has_ext = _ev(page, """() => {
|
|
||||||
const c = document.createElement('canvas');
|
|
||||||
const gl = c.getContext('webgl') || c.getContext('experimental-webgl');
|
|
||||||
return !!gl && !!gl.getExtension('WEBGL_debug_renderer_info');
|
|
||||||
}""")
|
|
||||||
assert has_ext
|
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# sannysoft.com — classic Puppeteer detection harness
|
# sannysoft.com — classic Puppeteer detection harness
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user