captchaforge 0.2.36

[DO NOT USE — UNDER ACTIVE DEVELOPMENT, NOT PRODUCTION-READY] Captcha solver scaffolding for chromiumoxide-driven browsers. The architecture is in place (vendor solvers, retry-loop iframe walking, VLM provider abstraction, real-WAF bench harness) but the live-vendor success rate is still 0% — Cloudflare Turnstile / hCaptcha / reCAPTCHA detect us at a TLS / CDP fingerprint layer that no flag-based stealth has cleared. Watch the repo; do not depend on this for any real workload.
Documentation
//! Solver integration tests against mock CAPTCHA pages.

use captchaforge::detect::detect;
use captchaforge::solver::{
    BehavioralCaptchaSolver, CaptchaSolver, CaptchaSolverChain, SolveConfig,
};

mod common;

#[tokio::test]
async fn behavioral_solver_runs_on_turnstile_mock() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let js = r#"
        document.body.innerHTML = '<div class="cf-turnstile" data-sitekey="test"></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();
    let solver = BehavioralCaptchaSolver::new();
    let result = solver.solve(&page, &info).await.unwrap();
    // Mock page won't succeed but solver should return gracefully.
    assert!(!result.success || result.solution.is_empty());
    browser.close().await.unwrap();
}

#[tokio::test]
async fn default_chain_runs_on_unknown_page() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let js = r#"document.body.innerHTML = '<h1>Nothing here</h1>'; 'done'"#;
    let _: String = page.evaluate(js).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]
async fn default_chain_runs_on_recaptcha_mock() {
    let mut browser = common::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"></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();
    let chain = CaptchaSolverChain::default_chain();
    let result = chain.solve(&page, &info).await;
    // Chain should attempt but likely not succeed on a mock page.
    assert!(!result.success || result.time_ms > 0);
    browser.close().await.unwrap();
}

#[tokio::test]
async fn default_chain_runs_on_hcaptcha_mock() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let js = r#"
        document.body.innerHTML = '<div class="h-captcha" data-sitekey="test"></div>' +
            '<script src="https://js.hcaptcha.com/1/api.js"></script>';
        "true"
    "#;
    let _: String = page.evaluate(js).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 || result.time_ms > 0);
    browser.close().await.unwrap();
}

#[tokio::test]
async fn solver_name_is_non_empty() {
    let solver = BehavioralCaptchaSolver::new();
    assert!(!solver.name().is_empty());
}

#[tokio::test]
async fn solve_config_custom_values_used() {
    let config = SolveConfig {
        checkbox_poll_interval_ms: 50,
        checkbox_max_attempts: 1,
        token_poll_interval_ms: 50,
        token_max_attempts: 1,
        audio_button_delay_ms: 50,
        audio_submit_delay_ms: 50,
        vlm_http_timeout_ms: 500,
        client_http_timeout_ms: 500,
    };
    assert_eq!(config.checkbox_poll_interval_ms, 50);
    assert_eq!(config.checkbox_max_attempts, 1);
}

#[tokio::test]
async fn empty_chain_on_image_captcha() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let js = r#"
        document.body.innerHTML = '<div class="captcha"><img src="/captcha.png"></div>';
        "true"
    "#;
    let _: String = page.evaluate(js).await.unwrap().into_value().unwrap();
    let info = detect(&page).await.unwrap();
    let chain = CaptchaSolverChain::empty();
    let result = chain.solve(&page, &info).await;
    assert!(!result.success);
    browser.close().await.unwrap();
}

#[tokio::test]
async fn chain_with_added_solver() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let js = r#"
        document.body.innerHTML = '<div class="cf-turnstile" data-sitekey="test"></div>';
        "true"
    "#;
    let _: String = page.evaluate(js).await.unwrap().into_value().unwrap();
    let info = detect(&page).await.unwrap();
    let mut chain = CaptchaSolverChain::empty();
    chain.add_solver(BehavioralCaptchaSolver::new());
    let result = chain.solve(&page, &info).await;
    assert!(!result.success || result.time_ms > 0);
    browser.close().await.unwrap();
}

#[tokio::test]
async fn behavioral_solver_method_is_behavioral() {
    let solver = BehavioralCaptchaSolver::new();
    assert_eq!(
        solver.method(),
        captchaforge::solver::SolveMethod::BehavioralBypass
    );
}