Skip to main content

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