browser_agent/
browser.rs

1use anyhow::{anyhow, Result};
2use chromiumoxide::{
3    fetcher::BrowserFetcherRevisionInfo, Browser, BrowserConfig, BrowserFetcher,
4    BrowserFetcherOptions, Page,
5};
6use std::path::Path;
7use tokio::time::{sleep, Duration};
8use tokio_stream::StreamExt;
9use tracing::debug;
10
11/// Starts the browser and returns a handle to it.
12///
13/// # Arguments
14///
15/// * `browser_path` - The path to the browser executable (will be downloaded if not found).
16/// * `user_data_dir` - The path to the user data directory (will be created if not found).
17/// * `headless` - Whether to run the browser in headless mode.
18///
19/// # Errors
20///
21/// * If the browser executable cannot be found or downloaded.
22/// * If the user data directory cannot be created.
23/// * If the browser cannot be launched.
24/// * If the browser handler cannot be spawned.
25pub async fn init(browser_path: &Path, user_data_dir: &Path, headless: bool) -> Result<Browser> {
26    let browser_info = ensure_browser(browser_path).await?;
27
28    let mut config = BrowserConfig::builder()
29        .user_data_dir(user_data_dir)
30        .chrome_executable(browser_info.executable_path);
31
32    if headless {
33        config = config.with_head();
34    }
35
36    let (browser, mut handler) = Browser::launch(config.build().map_err(|e| anyhow!(e))?).await?;
37
38    tokio::spawn(async move {
39        while let Some(h) = handler.next().await {
40            if h.is_err() {
41                debug!("Browser handler error: {:?}", h);
42                break;
43            }
44        }
45    });
46
47    Ok(browser)
48}
49
50async fn ensure_browser(path: &Path) -> Result<BrowserFetcherRevisionInfo> {
51    let fetcher = BrowserFetcher::new(BrowserFetcherOptions::builder().with_path(path).build()?);
52
53    Ok(fetcher.fetch().await?)
54}
55
56/// Waits for the page to navigate or for 5 seconds to pass.
57///
58/// # Arguments
59///
60/// * `page` - The page to wait for.
61pub async fn wait_for_page(page: &Page) {
62    tokio::select! {
63        _ = page.wait_for_navigation() => {},
64        _ = sleep(Duration::from_secs(5)) => {},
65    }
66}