use captchaforge::stealth::browser::launch_reynard;
use captchaforge::StealthProfile;
use std::time::Duration;
fn reynard_binary() -> Option<String> {
if let Ok(p) = std::env::var("REYNARD_BIN") {
if !p.trim().is_empty() {
return Some(p);
}
}
let home = std::env::var("HOME").ok()?;
let cached = format!("{home}/.cache/camoufox/camoufox");
std::path::Path::new(&cached).exists().then_some(cached)
}
async fn within<T>(secs: u64, fut: impl std::future::Future<Output = T>) -> Option<T> {
tokio::time::timeout(Duration::from_secs(secs), fut).await.ok()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reynard_engine_closes_native_webdriver() {
let Some(bin) = reynard_binary() else {
eprintln!("SKIP reynard_webdriver: no REYNARD_BIN and no ~/.cache/camoufox/camoufox");
return;
};
eprintln!("[reynard] binary: {bin}");
let page = match within(90, launch_reynard(&bin, &StealthProfile::FirefoxLinux, true)).await {
Some(Ok(p)) => p,
Some(Err(e)) => {
eprintln!("SKIP reynard_webdriver: launch_reynard failed ({e})");
return;
}
None => {
eprintln!("SKIP reynard_webdriver: launch_reynard timed out (90s)");
return;
}
};
eprintln!("[reynard] launched + attached over BiDi");
if within(25, page.goto("about:blank")).await.is_none() {
eprintln!("SKIP reynard_webdriver: goto timed out");
let _ = within(5, page.close()).await;
return;
}
let native = match within(25, page.evaluate("String(navigator.webdriver)")).await {
Some(Ok(ev)) => ev.into_value::<String>().unwrap_or_else(|_| "<parse-fail>".into()),
_ => {
eprintln!("SKIP reynard_webdriver: evaluate timed out / errored");
let _ = within(5, page.close()).await;
return;
}
};
eprintln!("[reynard] native navigator.webdriver = {native}");
let _ = within(8, page.close()).await;
assert_eq!(
native, "false",
"the reynard engine must read native navigator.webdriver=false even under BiDi \
— the webDriverAdvanced tell plain Firefox cannot hide."
);
}