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
//! Integration tests for error handling and edge cases.

use captchaforge::detect::{detect, is_captcha, CaptchaInfo};
use captchaforge::solver::CaptchaSolverChain;

mod common;

#[tokio::test]
async fn detect_on_data_url() {
    let mut browser = common::launch_browser().await;
    let page = browser
        .new_page("data:text/html,<h1>Hello</h1>")
        .await
        .unwrap();
    let result = detect(&page).await.unwrap();
    assert!(!is_captcha(&result));
    browser.close().await.unwrap();
}

#[tokio::test]
async fn chain_solve_on_plain_page_returns_unsolved() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let chain = CaptchaSolverChain::default_chain();
    let info = CaptchaInfo {
        kind: captchaforge::detect::DetectedCaptcha::None,
        site_key: None,
        page_url: page.url().await.unwrap().unwrap_or_default(),
        container_selector: None,
    };
    let result = chain.solve(&page, &info).await;
    assert!(!result.success);
    browser.close().await.unwrap();
}

#[tokio::test]
async fn navigate_to_invalid_url_graceful() {
    let mut browser = common::launch_browser().await;
    let result = browser.new_page("not-a-valid-url").await;
    // Chromiumoxide may either succeed (and load an error page) or fail.
    // Either is acceptable; we just verify it doesn't panic.
    if let Ok(page) = result {
        let _ = detect(&page).await;
    }
    browser.close().await.unwrap();
}

#[tokio::test]
async fn evaluate_malformed_js_returns_error() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let result = page.evaluate("function() { return 1").await;
    assert!(result.is_err());
    browser.close().await.unwrap();
}

#[tokio::test]
async fn evaluate_undefined_is_ok() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let result = page.evaluate("undefined").await;
    assert!(result.is_ok());
    browser.close().await.unwrap();
}

#[tokio::test]
async fn evaluate_null_is_ok() {
    let mut browser = common::launch_browser().await;
    let page = browser.new_page("about:blank").await.unwrap();
    let result = page.evaluate("null").await;
    assert!(result.is_ok());
    browser.close().await.unwrap();
}

#[tokio::test]
async fn page_reload_preserves_title() {
    let mut browser = common::launch_browser().await;
    let addr = common::serve_once(
        r#"<!DOCTYPE html><html><head><title>Persist</title></head><body></body></html>"#.into(),
    )
    .await;
    let page = browser.new_page(format!("http://{}", addr)).await.unwrap();
    tokio::time::sleep(std::time::Duration::from_millis(200)).await;
    let title: String = page
        .evaluate("document.title")
        .await
        .unwrap()
        .into_value()
        .unwrap();
    assert_eq!(title, "Persist");
    page.reload().await.unwrap();
    tokio::time::sleep(std::time::Duration::from_millis(200)).await;
    let title_after: String = page
        .evaluate("document.title")
        .await
        .unwrap()
        .into_value()
        .unwrap();
    assert_eq!(title_after, "Persist");
    browser.close().await.unwrap();
}