use captchaforge::detect::{detect, is_captcha, DetectedCaptcha};
use captchaforge::solver::{CaptchaSolverChain, SolveConfig};
use chromiumoxide::browser::{Browser, BrowserConfig};
use futures_util::stream::StreamExt;
async fn launch_browser() -> Browser {
let temp_dir = std::env::temp_dir().join(format!(
"captchaforge-test-{}",
std::process::id()
));
let config = BrowserConfig::builder()
.chrome_executable(
std::env::var("CHROME_BIN")
.ok()
.or_else(|| which::which("google-chrome").ok().map(|p| p.to_string_lossy().to_string()))
.or_else(|| which::which("chromium").ok().map(|p| p.to_string_lossy().to_string()))
.expect("Chrome/Chromium not found. Set CHROME_BIN or install Chrome."),
)
.arg("--no-sandbox")
.arg("--disable-setuid-sandbox")
.arg("--disable-dev-shm-usage")
.arg("--disable-gpu")
.arg("--single-process")
.arg("--no-zygote")
.arg(format!("--user-data-dir={}", temp_dir.display()))
.build()
.unwrap();
let (browser, mut handler) = Browser::launch(config).await.unwrap();
tokio::spawn(async move {
while let Some(_evt) = handler.next().await {}
});
browser
}
#[tokio::test]
#[ignore = "requires Chrome binary"]
async fn detect_turnstile_on_mock_page() {
let mut browser = launch_browser().await;
let page = browser.new_page("about:blank").await.unwrap();
let js = r#"
document.body.innerHTML = '<div class="cf-turnstile" data-sitekey="0xTEST"></div>' +
'<script src="https://challenges.cloudflare.com/turnstile/v0/api.js"></script>';
"true"
"#;
let _: String = page.evaluate(js).await.unwrap().into_value().unwrap();
let info = detect(&page).await.unwrap();
assert!(is_captcha(&info));
assert_eq!(info.kind, DetectedCaptcha::Turnstile);
assert_eq!(info.site_key, Some("0xTEST".to_string()));
browser.close().await.unwrap();
}
#[tokio::test]
#[ignore = "requires Chrome binary"]
async fn detect_recaptcha_v2_on_mock_page() {
let mut browser = launch_browser().await;
let page = browser.new_page("about:blank").await.unwrap();
let js = r#"
document.body.innerHTML = '<div class="g-recaptcha" data-sitekey="test-key-123"></div>' +
'<script src="https://www.google.com/recaptcha/api.js"></script>';
"true"
"#;
let _: String = page.evaluate(js).await.unwrap().into_value().unwrap();
let info = detect(&page).await.unwrap();
assert!(is_captcha(&info));
assert_eq!(info.kind, DetectedCaptcha::RecaptchaV2);
assert_eq!(info.site_key, Some("test-key-123".to_string()));
browser.close().await.unwrap();
}
#[tokio::test]
#[ignore = "requires Chrome binary"]
async fn detect_none_on_plain_page() {
let mut browser = launch_browser().await;
let page = browser.new_page("about:blank").await.unwrap();
let info = detect(&page).await.unwrap();
assert!(!is_captcha(&info));
assert_eq!(info.kind, DetectedCaptcha::None);
browser.close().await.unwrap();
}
#[tokio::test]
#[ignore = "requires Chrome binary"]
async fn chain_falls_through_on_unknown_captcha() {
let mut browser = launch_browser().await;
let page = browser.new_page("about:blank").await.unwrap();
let _: String = page
.evaluate("document.body.innerHTML = '<h1>Hello</h1>'; 'done'")
.await
.unwrap()
.into_value()
.unwrap();
let info = detect(&page).await.unwrap();
let chain = CaptchaSolverChain::default_chain();
let result = chain.solve(&page, &info).await;
assert!(!result.success);
browser.close().await.unwrap();
}
#[tokio::test]
#[ignore = "requires Chrome binary"]
async fn detect_challenge_page_title() {
let mut browser = launch_browser().await;
let page = browser.new_page("about:blank").await.unwrap();
let js = r#"
document.title = 'Just a moment...';
document.body.innerHTML = '<form id="challenge-form"></form>';
"true"
"#;
let _: String = page.evaluate(js).await.unwrap().into_value().unwrap();
let info = detect(&page).await.unwrap();
assert!(is_captcha(&info));
assert_eq!(info.kind, DetectedCaptcha::Turnstile);
browser.close().await.unwrap();
}
#[tokio::test]
#[ignore = "requires Chrome binary"]
async fn solve_config_custom_timeouts_work() {
let config = SolveConfig {
checkbox_poll_interval_ms: 100,
checkbox_max_attempts: 3,
token_poll_interval_ms: 100,
token_max_attempts: 3,
audio_button_delay_ms: 100,
audio_submit_delay_ms: 100,
vlm_http_timeout_ms: 1000,
client_http_timeout_ms: 1000,
};
assert_eq!(config.checkbox_max_attempts, 3);
assert_eq!(config.vlm_http_timeout_ms, 1000);
}