browser_commander/browser/
launcher.rs

1//! Browser launcher for browser automation.
2//!
3//! This module provides utilities for launching browser instances
4//! with appropriate configuration.
5
6use crate::core::constants::CHROME_ARGS;
7use crate::core::engine::EngineType;
8use std::path::PathBuf;
9
10/// Options for launching a browser.
11#[derive(Debug, Clone)]
12pub struct LaunchOptions {
13    /// The browser engine to use.
14    pub engine: EngineType,
15    /// Path to user data directory.
16    pub user_data_dir: Option<PathBuf>,
17    /// Run in headless mode.
18    pub headless: bool,
19    /// Slow down operations by this many milliseconds.
20    pub slow_mo: u64,
21    /// Enable verbose logging.
22    pub verbose: bool,
23    /// Additional Chrome arguments.
24    pub args: Vec<String>,
25}
26
27impl Default for LaunchOptions {
28    fn default() -> Self {
29        Self {
30            engine: EngineType::Chromiumoxide,
31            user_data_dir: None,
32            headless: false,
33            slow_mo: 0,
34            verbose: false,
35            args: Vec::new(),
36        }
37    }
38}
39
40impl LaunchOptions {
41    /// Create options for chromiumoxide engine.
42    pub fn chromiumoxide() -> Self {
43        Self {
44            engine: EngineType::Chromiumoxide,
45            ..Default::default()
46        }
47    }
48
49    /// Create options for fantoccini (WebDriver) engine.
50    pub fn fantoccini() -> Self {
51        Self {
52            engine: EngineType::Fantoccini,
53            ..Default::default()
54        }
55    }
56
57    /// Set headless mode.
58    pub fn headless(mut self, headless: bool) -> Self {
59        self.headless = headless;
60        self
61    }
62
63    /// Set the user data directory.
64    pub fn user_data_dir(mut self, dir: impl Into<PathBuf>) -> Self {
65        self.user_data_dir = Some(dir.into());
66        self
67    }
68
69    /// Set slow motion delay.
70    pub fn slow_mo(mut self, ms: u64) -> Self {
71        self.slow_mo = ms;
72        self
73    }
74
75    /// Enable verbose logging.
76    pub fn verbose(mut self, verbose: bool) -> Self {
77        self.verbose = verbose;
78        self
79    }
80
81    /// Add additional Chrome arguments.
82    pub fn with_args(mut self, args: Vec<String>) -> Self {
83        self.args = args;
84        self
85    }
86
87    /// Get all Chrome arguments (default + custom).
88    pub fn all_chrome_args(&self) -> Vec<String> {
89        let mut all_args: Vec<String> = CHROME_ARGS.iter().map(|s| s.to_string()).collect();
90        all_args.extend(self.args.clone());
91        all_args
92    }
93
94    /// Get the user data directory, using a default if not specified.
95    pub fn get_user_data_dir(&self) -> PathBuf {
96        if let Some(ref dir) = self.user_data_dir {
97            dir.clone()
98        } else {
99            let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
100            home.join(".browser-commander")
101                .join(format!("{}-data", self.engine))
102        }
103    }
104}
105
106/// Browser instance wrapper.
107///
108/// This is a placeholder struct that would wrap the actual browser
109/// instance from the underlying engine (chromiumoxide or fantoccini).
110#[derive(Debug)]
111pub struct Browser {
112    /// The engine type being used.
113    pub engine: EngineType,
114    /// The user data directory.
115    pub user_data_dir: PathBuf,
116    /// Whether the browser is running headless.
117    pub headless: bool,
118}
119
120/// Result of a browser launch.
121#[derive(Debug)]
122pub struct LaunchResult {
123    /// The browser instance.
124    pub browser: Browser,
125}
126
127/// Launch a browser with the given options.
128///
129/// Note: This is a placeholder implementation. The actual implementation
130/// would use chromiumoxide or fantoccini to launch a real browser.
131///
132/// # Arguments
133///
134/// * `options` - Launch options
135///
136/// # Returns
137///
138/// The launch result containing the browser instance
139///
140/// # Errors
141///
142/// Returns an error if the browser fails to launch
143pub async fn launch_browser(options: LaunchOptions) -> Result<LaunchResult, anyhow::Error> {
144    if options.verbose {
145        tracing::info!("Launching browser with {} engine...", options.engine);
146    }
147
148    let user_data_dir = options.get_user_data_dir();
149
150    // Create user data directory if it doesn't exist
151    std::fs::create_dir_all(&user_data_dir)?;
152
153    // This is a placeholder - actual implementation would launch real browser
154    let browser = Browser {
155        engine: options.engine,
156        user_data_dir,
157        headless: options.headless,
158    };
159
160    if options.verbose {
161        tracing::info!("Browser launched with {} engine", options.engine);
162    }
163
164    Ok(LaunchResult { browser })
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    #[test]
172    fn launch_options_default() {
173        let options = LaunchOptions::default();
174        assert_eq!(options.engine, EngineType::Chromiumoxide);
175        assert!(!options.headless);
176        assert_eq!(options.slow_mo, 0);
177        assert!(!options.verbose);
178        assert!(options.args.is_empty());
179    }
180
181    #[test]
182    fn launch_options_builder() {
183        let options = LaunchOptions::chromiumoxide()
184            .headless(true)
185            .slow_mo(100)
186            .verbose(true)
187            .with_args(vec!["--custom-arg".to_string()]);
188
189        assert_eq!(options.engine, EngineType::Chromiumoxide);
190        assert!(options.headless);
191        assert_eq!(options.slow_mo, 100);
192        assert!(options.verbose);
193        assert_eq!(options.args, vec!["--custom-arg"]);
194    }
195
196    #[test]
197    fn launch_options_fantoccini() {
198        let options = LaunchOptions::fantoccini();
199        assert_eq!(options.engine, EngineType::Fantoccini);
200    }
201
202    #[test]
203    fn all_chrome_args_includes_defaults() {
204        let options = LaunchOptions::default();
205        let args = options.all_chrome_args();
206
207        assert!(args.contains(&"--disable-infobars".to_string()));
208        assert!(args.contains(&"--no-first-run".to_string()));
209    }
210
211    #[test]
212    fn all_chrome_args_includes_custom() {
213        let options = LaunchOptions::default().with_args(vec!["--custom".to_string()]);
214        let args = options.all_chrome_args();
215
216        assert!(args.contains(&"--custom".to_string()));
217    }
218
219    #[test]
220    fn get_user_data_dir_uses_custom() {
221        let options = LaunchOptions::default().user_data_dir("/custom/path");
222        assert_eq!(options.get_user_data_dir(), PathBuf::from("/custom/path"));
223    }
224
225    #[test]
226    fn get_user_data_dir_creates_default() {
227        let options = LaunchOptions::default();
228        let dir = options.get_user_data_dir();
229        assert!(dir.to_string_lossy().contains("browser-commander"));
230        assert!(dir.to_string_lossy().contains("chromiumoxide-data"));
231    }
232}