use std::path::Path;
use chromiumoxide::browser::{Browser, BrowserConfig, BrowserConfigBuilder};
use futures::StreamExt;
use tempfile::TempDir;
use tokio::task::JoinHandle;
use crate::config::HeadlessConfig;
use crate::fetcher::headless::HeadlessError;
pub fn build_browser_config(
cfg: &HeadlessConfig,
profile_dir: &Path,
) -> Result<BrowserConfig, HeadlessError> {
let mut builder: BrowserConfigBuilder = BrowserConfig::builder();
if !cfg.chrome_executable.is_empty() {
builder = builder.chrome_executable(&cfg.chrome_executable);
}
builder = builder.enable_request_intercept();
builder = builder.user_data_dir(profile_dir);
builder
.build()
.map_err(|e| HeadlessError::ConfigInvalid(e.to_string()))
}
pub async fn launch(
cfg: &HeadlessConfig,
) -> Result<(Browser, JoinHandle<()>, TempDir), HeadlessError> {
let profile_dir = tempfile::Builder::new()
.prefix("rover-headless-")
.tempdir()
.map_err(|e| {
HeadlessError::LaunchFailed(format!("could not create browser profile dir: {e}"))
})?;
let bc = build_browser_config(cfg, profile_dir.path())?;
let (browser, mut handler) = Browser::launch(bc)
.await
.map_err(|e| HeadlessError::LaunchFailed(e.to_string()))?;
let task = tokio::spawn(async move {
while let Some(_event) = handler.next().await {
}
});
Ok((browser, task, profile_dir))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn config_with_empty_chrome_executable_uses_default_detection() {
let cfg = HeadlessConfig {
chrome_executable: String::new(),
..HeadlessConfig::default()
};
let profile_dir = tempfile::tempdir().expect("tempdir");
let bc = build_browser_config(&cfg, profile_dir.path());
assert!(
bc.is_ok(),
"config builds even without chrome installed; launch is the failing step"
);
}
}