Skip to main content

playwright_rs/api/
launch_options.rs

1// Launch options for BrowserType::launch()
2//
3// This module provides options for launching browsers, matching the Playwright API exactly.
4// See: https://playwright.dev/docs/api/class-browsertype#browser-type-launch
5
6use crate::protocol::proxy::ProxySettings;
7use serde::{Deserialize, Serialize};
8use serde_json::{Value, json};
9use std::collections::HashMap;
10
11/// Options for launching a browser
12///
13/// All options are optional and will use Playwright's defaults if not specified.
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct LaunchOptions {
17    /// Additional arguments to pass to browser instance
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub args: Option<Vec<String>>,
20
21    /// Browser distribution channel (e.g., "chrome", "msedge")
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub channel: Option<String>,
24
25    /// Enable Chromium sandboxing (default: false on Linux)
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub chromium_sandbox: Option<bool>,
28
29    /// Auto-open DevTools (deprecated, default: false)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub devtools: Option<bool>,
32
33    /// Directory to save downloads
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub downloads_path: Option<String>,
36
37    /// Environment variables for browser process
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub env: Option<HashMap<String, String>>,
40
41    /// Path to custom browser executable
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub executable_path: Option<String>,
44
45    /// Firefox user preferences (Firefox only)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub firefox_user_prefs: Option<HashMap<String, Value>>,
48
49    /// Close browser on SIGHUP (default: true)
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub handle_sighup: Option<bool>,
52
53    /// Close browser on SIGINT/Ctrl-C (default: true)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub handle_sigint: Option<bool>,
56
57    /// Close browser on SIGTERM (default: true)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub handle_sigterm: Option<bool>,
60
61    /// Run in headless mode (default: true unless devtools=true)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub headless: Option<bool>,
64
65    /// Filter or disable default browser arguments
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub ignore_default_args: Option<IgnoreDefaultArgs>,
68
69    /// Network proxy settings
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub proxy: Option<ProxySettings>,
72
73    /// Slow down operations by N milliseconds
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub slow_mo: Option<f64>,
76
77    /// Timeout for browser launch in milliseconds (default: DEFAULT_TIMEOUT_MS)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub timeout: Option<f64>,
80
81    /// Directory to save traces
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub traces_dir: Option<String>,
84}
85
86/// Filter or disable default browser arguments
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(untagged)]
89pub enum IgnoreDefaultArgs {
90    /// Ignore all default arguments
91    Bool(bool),
92    /// Filter specific default arguments
93    Array(Vec<String>),
94}
95
96impl LaunchOptions {
97    /// Creates a new LaunchOptions with default values
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    /// Set additional arguments to pass to browser instance
103    pub fn args(mut self, args: Vec<String>) -> Self {
104        self.args = Some(args);
105        self
106    }
107
108    /// Set browser distribution channel
109    pub fn channel(mut self, channel: String) -> Self {
110        self.channel = Some(channel);
111        self
112    }
113
114    /// Enable or disable Chromium sandboxing
115    pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
116        self.chromium_sandbox = Some(enabled);
117        self
118    }
119
120    /// Auto-open DevTools
121    pub fn devtools(mut self, enabled: bool) -> Self {
122        self.devtools = Some(enabled);
123        self
124    }
125
126    /// Set directory to save downloads
127    pub fn downloads_path(mut self, path: String) -> Self {
128        self.downloads_path = Some(path);
129        self
130    }
131
132    /// Set environment variables for browser process
133    pub fn env(mut self, env: HashMap<String, String>) -> Self {
134        self.env = Some(env);
135        self
136    }
137
138    /// Set path to custom browser executable
139    pub fn executable_path(mut self, path: String) -> Self {
140        self.executable_path = Some(path);
141        self
142    }
143
144    /// Set Firefox user preferences (Firefox only)
145    pub fn firefox_user_prefs(mut self, prefs: HashMap<String, Value>) -> Self {
146        self.firefox_user_prefs = Some(prefs);
147        self
148    }
149
150    /// Set whether to close browser on SIGHUP
151    pub fn handle_sighup(mut self, enabled: bool) -> Self {
152        self.handle_sighup = Some(enabled);
153        self
154    }
155
156    /// Set whether to close browser on SIGINT/Ctrl-C
157    pub fn handle_sigint(mut self, enabled: bool) -> Self {
158        self.handle_sigint = Some(enabled);
159        self
160    }
161
162    /// Set whether to close browser on SIGTERM
163    pub fn handle_sigterm(mut self, enabled: bool) -> Self {
164        self.handle_sigterm = Some(enabled);
165        self
166    }
167
168    /// Run in headless mode
169    pub fn headless(mut self, enabled: bool) -> Self {
170        self.headless = Some(enabled);
171        self
172    }
173
174    /// Filter or disable default browser arguments
175    pub fn ignore_default_args(mut self, args: IgnoreDefaultArgs) -> Self {
176        self.ignore_default_args = Some(args);
177        self
178    }
179
180    /// Set network proxy settings
181    pub fn proxy(mut self, proxy: ProxySettings) -> Self {
182        self.proxy = Some(proxy);
183        self
184    }
185
186    /// Slow down operations by N milliseconds
187    pub fn slow_mo(mut self, ms: f64) -> Self {
188        self.slow_mo = Some(ms);
189        self
190    }
191
192    /// Set timeout for browser launch in milliseconds
193    pub fn timeout(mut self, ms: f64) -> Self {
194        self.timeout = Some(ms);
195        self
196    }
197
198    /// Set directory to save traces
199    pub fn traces_dir(mut self, path: String) -> Self {
200        self.traces_dir = Some(path);
201        self
202    }
203
204    /// Normalize options for protocol transmission
205    ///
206    /// This performs transformations required by the Playwright protocol:
207    /// 1. Set default timeout if not specified (required in 1.56.1+)
208    /// 2. Convert env HashMap to array of {name, value} objects
209    /// 3. Convert bool ignoreDefaultArgs to ignoreAllDefaultArgs
210    ///
211    /// This matches the behavior of playwright-python's parameter normalization.
212    pub(crate) fn normalize(self) -> Value {
213        let mut value = serde_json::to_value(&self).unwrap();
214
215        // Set default timeout if not specified
216        // Note: In Playwright 1.56.1+, timeout became a required parameter
217        if value.get("timeout").is_none() {
218            value["timeout"] = json!(crate::DEFAULT_TIMEOUT_MS);
219        }
220
221        // Convert env HashMap to array of {name, value} objects
222        if let Some(env_map) = value.get_mut("env") {
223            if let Some(map) = env_map.as_object() {
224                let env_array: Vec<_> = map
225                    .iter()
226                    .map(|(k, v)| json!({"name": k, "value": v}))
227                    .collect();
228                *env_map = json!(env_array);
229            }
230        }
231
232        // Convert bool ignoreDefaultArgs to ignoreAllDefaultArgs
233        if let Some(ignore) = value.get("ignoreDefaultArgs") {
234            if let Some(b) = ignore.as_bool() {
235                if b {
236                    value["ignoreAllDefaultArgs"] = json!(true);
237                }
238                value.as_object_mut().unwrap().remove("ignoreDefaultArgs");
239            }
240        }
241
242        value
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_launch_options_default() {
252        let opts = LaunchOptions::default();
253        assert!(opts.headless.is_none());
254        assert!(opts.args.is_none());
255    }
256
257    #[test]
258    fn test_launch_options_builder() {
259        let opts = LaunchOptions::default()
260            .headless(false)
261            .slow_mo(100.0)
262            .args(vec!["--no-sandbox".to_string()]);
263
264        assert_eq!(opts.headless, Some(false));
265        assert_eq!(opts.slow_mo, Some(100.0));
266        assert_eq!(opts.args, Some(vec!["--no-sandbox".to_string()]));
267    }
268
269    #[test]
270    fn test_launch_options_normalize_env() {
271        let opts = LaunchOptions::default().env(HashMap::from([
272            ("FOO".to_string(), "bar".to_string()),
273            ("BAZ".to_string(), "qux".to_string()),
274        ]));
275
276        let normalized = opts.normalize();
277
278        // Verify env is converted to array format
279        assert!(normalized["env"].is_array());
280        let env_array = normalized["env"].as_array().unwrap();
281        assert_eq!(env_array.len(), 2);
282
283        // Check that both env vars are present (order may vary)
284        let names: Vec<_> = env_array
285            .iter()
286            .map(|v| v["name"].as_str().unwrap())
287            .collect();
288        assert!(names.contains(&"FOO"));
289        assert!(names.contains(&"BAZ"));
290    }
291
292    #[test]
293    fn test_launch_options_normalize_ignore_default_args_bool() {
294        let opts = LaunchOptions::default().ignore_default_args(IgnoreDefaultArgs::Bool(true));
295
296        let normalized = opts.normalize();
297
298        // Verify bool is converted to ignoreAllDefaultArgs
299        assert!(normalized["ignoreAllDefaultArgs"].as_bool().unwrap());
300        assert!(normalized.get("ignoreDefaultArgs").is_none());
301    }
302
303    #[test]
304    fn test_launch_options_normalize_ignore_default_args_array() {
305        let opts = LaunchOptions::default()
306            .ignore_default_args(IgnoreDefaultArgs::Array(vec!["--foo".to_string()]));
307
308        let normalized = opts.normalize();
309
310        // Verify array is preserved
311        assert!(normalized["ignoreDefaultArgs"].is_array());
312        assert_eq!(
313            normalized["ignoreDefaultArgs"][0].as_str().unwrap(),
314            "--foo"
315        );
316    }
317
318    #[test]
319    fn test_proxy_settings() {
320        let proxy = ProxySettings {
321            server: "http://proxy:8080".to_string(),
322            bypass: Some("localhost,127.0.0.1".to_string()),
323            username: Some("user".to_string()),
324            password: Some("pass".to_string()),
325        };
326
327        let opts = LaunchOptions::default().proxy(proxy);
328        assert!(opts.proxy.is_some());
329    }
330
331    #[test]
332    fn test_builder_pattern_chaining() {
333        let opts = LaunchOptions::new()
334            .headless(true)
335            .slow_mo(50.0)
336            .timeout(60000.0)
337            .args(vec![
338                "--no-sandbox".to_string(),
339                "--disable-gpu".to_string(),
340            ])
341            .channel("chrome".to_string());
342
343        assert_eq!(opts.headless, Some(true));
344        assert_eq!(opts.slow_mo, Some(50.0));
345        assert_eq!(opts.timeout, Some(60000.0));
346        assert_eq!(opts.args.as_ref().unwrap().len(), 2);
347        assert_eq!(opts.channel, Some("chrome".to_string()));
348    }
349}