use anyhow::Result;
use chaser_oxide::{Browser, BrowserConfig, ChaserPage};
use futures::StreamExt;
use serde_json::Value;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<()> {
println!("🤖 HEADLESS BOT DETECTION TEST");
println!("================================\n");
let (browser, mut handler) = Browser::launch(
BrowserConfig::builder()
.new_headless_mode() .build()
.map_err(|e| anyhow::anyhow!("{}", e))?,
)
.await?;
tokio::spawn(async move { while let Some(_) = handler.next().await {} });
let page = browser.new_page("about:blank").await?;
let chaser = ChaserPage::new(page);
chaser.apply_native_profile().await?;
println!("✅ Native profile applied in HEADLESS mode\n");
println!("📊 TEST 1: Sannysoft Bot Detector");
println!(" URL: https://bot.sannysoft.com");
chaser.goto("https://bot.sannysoft.com").await?;
tokio::time::sleep(Duration::from_secs(4)).await;
let red_count: u32 = extract_number(
&chaser
.evaluate(
r#"
(() => {
const rows = Array.from(document.querySelectorAll('tr td'));
return rows.filter(cell =>
cell.style.color === 'red' || cell.className.includes('failed')
).length;
})()
"#,
)
.await?,
);
let green_count: u32 = extract_number(
&chaser
.evaluate(
r#"
(() => {
const rows = Array.from(document.querySelectorAll('tr td'));
return rows.filter(cell =>
cell.style.color === 'green' || cell.className.includes('passed')
).length;
})()
"#,
)
.await?,
);
println!(" 🚩 Red flags: {}", red_count);
println!(" ✅ Green flags: {}", green_count);
let webdriver: String = extract_string(&chaser.evaluate("String(navigator.webdriver)").await?);
println!(" navigator.webdriver: {}", webdriver);
let chrome_check: String = extract_string(&chaser.evaluate("String(!!window.chrome)").await?);
println!(" window.chrome exists: {}", chrome_check);
println!();
println!("📊 TEST 2: AreYouHeadless Detection");
println!(" URL: https://arh.antoinevastel.com/bots/areyouheadless");
chaser
.goto("https://arh.antoinevastel.com/bots/areyouheadless")
.await?;
tokio::time::sleep(Duration::from_secs(3)).await;
let headless_result = extract_string(
&chaser
.evaluate(
r#"
(() => {
const pre = document.querySelector('pre');
return pre ? pre.textContent : 'Loading...';
})()
"#,
)
.await?,
);
println!(" Result: {}", headless_result);
println!();
println!("📊 TEST 3: Cloudflare Turnstile (winna.com)");
println!(" URL: https://winna.com");
chaser.goto("https://winna.com").await?;
tokio::time::sleep(Duration::from_secs(6)).await;
let turnstile_status = extract_string(
&chaser
.evaluate(
r#"
(() => {
const widget = document.querySelector('iframe[src*="turnstile"]');
if (!widget) return 'No Turnstile widget found';
const parent = widget.parentElement;
if (!parent) return 'Widget found but no parent';
// Check for success class
if (parent.className.includes('success') ||
parent.getAttribute('data-theme') === 'success') {
return 'PASSED ✅';
}
// Check for error
const error = document.querySelector('.cf-error-details');
if (error) return 'FAILED: ' + error.textContent;
return 'LOADING or UNKNOWN';
})()
"#,
)
.await?,
);
println!(" Status: {}", turnstile_status);
println!();
println!("📊 TEST 4: DeviceAndBrowserInfo Bot Detection");
println!(" URL: https://deviceandbrowserinfo.com/are_you_a_bot");
chaser
.goto("https://deviceandbrowserinfo.com/are_you_a_bot")
.await?;
tokio::time::sleep(Duration::from_secs(4)).await;
let result = chaser.raw_page().find_element("#resultsBotTest").await;
match result {
Ok(element) => {
let text = element
.inner_text()
.await?
.unwrap_or_else(|| "❓Unknown".to_string());
println!(" Detection Result: {}", text.replace("\n", ""));
}
Err(_) => {
println!(" Detection Result: element not found.");
}
}
println!();
println!("📊 TEST 5: Manual Stealth Checks");
let ua = extract_string(&chaser.evaluate("navigator.userAgent").await?);
println!(" User-Agent: {}", ua.chars().take(80).collect::<String>());
let platform = extract_string(&chaser.evaluate("navigator.platform").await?);
println!(" Platform: {}", platform);
let cores = extract_number(&chaser.evaluate("navigator.hardwareConcurrency").await?);
println!(" Hardware Concurrency: {}", cores);
let memory = extract_number(&chaser.evaluate("navigator.deviceMemory").await?);
println!(" Device Memory: {}GB", memory);
let plugins = extract_number(&chaser.evaluate("navigator.plugins.length").await?);
println!(" Plugins: {}", plugins);
let languages = extract_string(
&chaser
.evaluate("JSON.stringify(navigator.languages)")
.await?,
);
println!(" Languages: {}", languages);
let cdp_count = extract_number(
&chaser
.evaluate(
r#"
(() => {
let count = 0;
for (const prop of Object.getOwnPropertyNames(window)) {
if (/^cdc_|^\$cdc_|^__webdriver|^__selenium|^__driver|^\$chrome_/.test(prop)) {
count++;
}
}
return count;
})()
"#,
)
.await?,
);
println!(" CDP markers found: {}", cdp_count);
let chrome_runtime = extract_string(
&chaser
.evaluate("String(!!window.chrome?.runtime?.connect)")
.await?,
);
println!(" chrome.runtime.connect: {}", chrome_runtime);
let chrome_csi = extract_string(&chaser.evaluate("String(!!window.chrome?.csi)").await?);
println!(" chrome.csi: {}", chrome_csi);
println!();
println!("================================");
println!("🎯 TEST SUMMARY");
println!("================================");
let webdriver_clean = webdriver == "false";
let chrome_present = chrome_check == "true";
let low_red_flags = red_count < 5;
let no_cdp_markers = cdp_count == 0;
println!(
"✓ navigator.webdriver = false: {}",
if webdriver_clean { "✅" } else { "❌" }
);
println!(
"✓ window.chrome present: {}",
if chrome_present { "✅" } else { "❌" }
);
println!(
"✓ Low red flags (<5): {}",
if low_red_flags { "✅" } else { "❌" }
);
println!(
"✓ No CDP markers: {}",
if no_cdp_markers { "✅" } else { "❌" }
);
let score = [
webdriver_clean,
chrome_present,
low_red_flags,
no_cdp_markers,
]
.iter()
.filter(|&&x| x)
.count();
println!("\nOverall Score: {}/4", score);
if score >= 3 {
println!("🎉 HEADLESS STEALTH: EXCELLENT");
} else if score >= 2 {
println!("⚠️ HEADLESS STEALTH: GOOD");
} else {
println!("❌ HEADLESS STEALTH: NEEDS IMPROVEMENT");
}
Ok(())
}
fn extract_string(value: &Option<Value>) -> String {
match value {
Some(Value::String(s)) => s.clone(),
Some(Value::Bool(b)) => b.to_string(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::Null) => "null".to_string(),
Some(v) => v.to_string(),
None => "undefined".to_string(),
}
}
fn extract_number(value: &Option<Value>) -> u32 {
match value {
Some(Value::Number(n)) => n.as_u64().unwrap_or(0) as u32,
Some(Value::String(s)) => s.parse().unwrap_or(0),
_ => 0,
}
}