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