Skip to main content

runtime_foxdriver/
runtime.rs

1//! Browser launch via rustenium Firefox (replaces runtime_headless).
2//!
3//! CLI, examples, and bindings should use [`drive_browser`] instead of
4//! hand-rolling `captchaforge::browser::launch_firefox` + handler tasks.
5
6use anyhow::{Context, Result};
7
8use crate::browser::{launch_firefox, FoxBrowserConfig, Page};
9
10/// Options for [`drive_browser`].
11#[derive(Debug, Clone)]
12pub struct BrowserDriveOptions {
13    /// When false, launch a visible window.
14    pub headless: bool,
15    /// Disable chromium sandbox (no-op for Firefox, kept for API compat).
16    pub no_sandbox: bool,
17}
18
19impl Default for BrowserDriveOptions {
20    fn default() -> Self {
21        Self {
22            headless: true,
23            no_sandbox: true,
24        }
25    }
26}
27
28/// Build launch config aligned with the old runtime-headless shape.
29#[must_use]
30pub fn launch_options(opts: &BrowserDriveOptions) -> FoxBrowserConfig {
31    FoxBrowserConfig {
32        headless: opts.headless,
33        ..Default::default()
34    }
35}
36
37/// Launch Firefox, navigate to `url`, run `f` with the live page, then tear down.
38pub async fn drive_browser<F, Fut, T>(url: &str, opts: BrowserDriveOptions, f: F) -> Result<T>
39where
40    F: FnOnce(Page) -> Fut,
41    Fut: std::future::Future<Output = Result<T>>,
42{
43    let page = launch_firefox(launch_options(&opts))
44        .await
45        .map_err(|e| anyhow::anyhow!("launch firefox (is it installed and on PATH?): {e}"))?;
46    tokio::time::timeout(std::time::Duration::from_secs(30), page.goto(url))
47        .await
48        .map_err(|_| anyhow::anyhow!("navigate to {url} timed out after 30s"))?
49        .with_context(|| format!("navigate to {url}"))?;
50    f(page).await
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn launch_options_headless_maps_correctly() {
59        let opts = launch_options(&BrowserDriveOptions {
60            headless: true,
61            ..BrowserDriveOptions::default()
62        });
63        assert!(opts.headless);
64    }
65
66    #[test]
67    fn launch_options_headful_maps_correctly() {
68        let opts = launch_options(&BrowserDriveOptions {
69            headless: false,
70            ..BrowserDriveOptions::default()
71        });
72        assert!(!opts.headless);
73    }
74}