firefox_webdriver/driver/
options.rs

1//! Firefox command-line options and configuration.
2//!
3//! Provides a type-safe interface for configuring Firefox process options
4//! such as headless mode, window size, and other command-line arguments.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use firefox_webdriver::FirefoxOptions;
10//!
11//! let options = FirefoxOptions::new()
12//!     .with_headless()
13//!     .with_window_size(1920, 1080)
14//!     .with_private();
15//!
16//! let args = options.to_args();
17//! // ["--headless", "--window-size", "1920,1080", "--private-window"]
18//! ```
19
20// ============================================================================
21// FirefoxOptions
22// ============================================================================
23
24/// Firefox process configuration options.
25///
26/// Controls how Firefox is launched, including display mode, window dimensions,
27/// and additional command-line arguments.
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
29pub struct FirefoxOptions {
30    /// Run Firefox without a GUI (headless mode).
31    pub headless: bool,
32
33    /// Window dimensions in pixels (width, height).
34    pub window_size: Option<(u32, u32)>,
35
36    /// Enable kiosk mode (fullscreen with restricted UI).
37    pub kiosk: bool,
38
39    /// Open Developer Tools on startup.
40    pub devtools: bool,
41
42    /// Open a private browsing window.
43    pub private: bool,
44
45    /// Additional custom command-line arguments.
46    pub extra_args: Vec<String>,
47}
48
49// ============================================================================
50// Constructors
51// ============================================================================
52
53impl FirefoxOptions {
54    /// Creates a new options instance with default settings.
55    #[inline]
56    #[must_use]
57    pub const fn new() -> Self {
58        Self {
59            headless: false,
60            window_size: None,
61            kiosk: false,
62            devtools: false,
63            private: false,
64            extra_args: Vec::new(),
65        }
66    }
67
68    /// Creates options configured for headless mode.
69    #[inline]
70    #[must_use]
71    pub fn headless() -> Self {
72        Self {
73            headless: true,
74            ..Default::default()
75        }
76    }
77}
78
79// ============================================================================
80// Builder Methods
81// ============================================================================
82
83impl FirefoxOptions {
84    /// Enables headless mode.
85    #[inline]
86    #[must_use]
87    pub fn with_headless(mut self) -> Self {
88        self.headless = true;
89        self
90    }
91
92    /// Sets window size in pixels.
93    #[inline]
94    #[must_use]
95    pub fn with_window_size(mut self, width: u32, height: u32) -> Self {
96        self.window_size = Some((width, height));
97        self
98    }
99
100    /// Enables kiosk mode.
101    #[inline]
102    #[must_use]
103    pub fn with_kiosk(mut self) -> Self {
104        self.kiosk = true;
105        self
106    }
107
108    /// Enables developer tools on startup.
109    #[inline]
110    #[must_use]
111    pub fn with_devtools(mut self) -> Self {
112        self.devtools = true;
113        self
114    }
115
116    /// Enables private browsing mode.
117    #[inline]
118    #[must_use]
119    pub fn with_private(mut self) -> Self {
120        self.private = true;
121        self
122    }
123
124    /// Adds a custom command-line argument.
125    #[inline]
126    #[must_use]
127    pub fn with_arg(mut self, arg: impl Into<String>) -> Self {
128        self.extra_args.push(arg.into());
129        self
130    }
131
132    /// Adds multiple custom command-line arguments.
133    #[inline]
134    #[must_use]
135    pub fn with_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
136        self.extra_args.extend(args.into_iter().map(Into::into));
137        self
138    }
139}
140
141// ============================================================================
142// Conversion Methods
143// ============================================================================
144
145impl FirefoxOptions {
146    /// Converts options to Firefox command-line arguments.
147    #[must_use]
148    pub fn to_args(&self) -> Vec<String> {
149        let mut args = Vec::with_capacity(8 + self.extra_args.len());
150
151        if self.headless {
152            args.push("--headless".to_string());
153        }
154
155        if let Some((width, height)) = self.window_size {
156            args.push("--window-size".to_string());
157            args.push(format!("{width},{height}"));
158        }
159
160        if self.kiosk {
161            args.push("--kiosk".to_string());
162        }
163
164        if self.devtools {
165            args.push("--devtools".to_string());
166        }
167
168        if self.private {
169            args.push("--private-window".to_string());
170        }
171
172        args.extend(self.extra_args.clone());
173        args
174    }
175
176    /// Validates the options configuration.
177    ///
178    /// # Errors
179    ///
180    /// Returns error message if validation fails.
181    pub fn validate(&self) -> Result<(), String> {
182        if let Some((width, height)) = self.window_size
183            && (width == 0 || height == 0)
184        {
185            return Err("Window dimensions must be greater than zero".to_string());
186        }
187        Ok(())
188    }
189
190    /// Returns `true` if headless mode is enabled.
191    #[inline]
192    #[must_use]
193    pub const fn is_headless(&self) -> bool {
194        self.headless
195    }
196
197    /// Returns `true` if private browsing is enabled.
198    #[inline]
199    #[must_use]
200    pub const fn is_private(&self) -> bool {
201        self.private
202    }
203}
204
205// ============================================================================
206// Tests
207// ============================================================================
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_new_creates_default() {
215        let options = FirefoxOptions::new();
216        assert!(!options.headless);
217        assert!(options.window_size.is_none());
218        assert!(!options.kiosk);
219        assert!(!options.devtools);
220        assert!(!options.private);
221        assert!(options.extra_args.is_empty());
222    }
223
224    #[test]
225    fn test_headless_constructor() {
226        let options = FirefoxOptions::headless();
227        assert!(options.headless);
228        assert!(options.is_headless());
229    }
230
231    #[test]
232    fn test_builder_chain() {
233        let options = FirefoxOptions::new()
234            .with_headless()
235            .with_window_size(1920, 1080)
236            .with_devtools()
237            .with_private();
238
239        assert!(options.headless);
240        assert_eq!(options.window_size, Some((1920, 1080)));
241        assert!(options.devtools);
242        assert!(options.private);
243    }
244
245    #[test]
246    fn test_to_args_headless() {
247        let options = FirefoxOptions::new().with_headless();
248        let args = options.to_args();
249        assert!(args.contains(&"--headless".to_string()));
250    }
251
252    #[test]
253    fn test_to_args_window_size() {
254        let options = FirefoxOptions::new().with_window_size(800, 600);
255        let args = options.to_args();
256        assert!(args.contains(&"--window-size".to_string()));
257        assert!(args.contains(&"800,600".to_string()));
258    }
259
260    #[test]
261    fn test_to_args_all_options() {
262        let options = FirefoxOptions::new()
263            .with_headless()
264            .with_window_size(1024, 768)
265            .with_kiosk()
266            .with_devtools()
267            .with_private()
268            .with_arg("--custom");
269
270        let args = options.to_args();
271        assert!(args.contains(&"--headless".to_string()));
272        assert!(args.contains(&"--kiosk".to_string()));
273        assert!(args.contains(&"--devtools".to_string()));
274        assert!(args.contains(&"--private-window".to_string()));
275        assert!(args.contains(&"--custom".to_string()));
276    }
277
278    #[test]
279    fn test_with_args_multiple() {
280        let options = FirefoxOptions::new().with_args(["--arg1", "--arg2"]);
281        assert_eq!(options.extra_args.len(), 2);
282    }
283
284    #[test]
285    fn test_validate_valid() {
286        let options = FirefoxOptions::new().with_window_size(800, 600);
287        assert!(options.validate().is_ok());
288    }
289
290    #[test]
291    fn test_validate_zero_width() {
292        let options = FirefoxOptions::new().with_window_size(0, 600);
293        assert!(options.validate().is_err());
294    }
295
296    #[test]
297    fn test_validate_zero_height() {
298        let options = FirefoxOptions::new().with_window_size(800, 0);
299        assert!(options.validate().is_err());
300    }
301}