playwright_rs/api/
launch_options.rs1use crate::protocol::proxy::ProxySettings;
7use serde::{Deserialize, Serialize};
8use serde_json::{Value, json};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct LaunchOptions {
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub args: Option<Vec<String>>,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
25 pub artifacts_dir: Option<String>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub channel: Option<String>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub chromium_sandbox: Option<bool>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub devtools: Option<bool>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub downloads_path: Option<String>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub env: Option<HashMap<String, String>>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub executable_path: Option<String>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub firefox_user_prefs: Option<HashMap<String, Value>>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub handle_sighup: Option<bool>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub handle_sigint: Option<bool>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub handle_sigterm: Option<bool>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub headless: Option<bool>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub ignore_default_args: Option<IgnoreDefaultArgs>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub proxy: Option<ProxySettings>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub slow_mo: Option<f64>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub timeout: Option<f64>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub traces_dir: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(untagged)]
95pub enum IgnoreDefaultArgs {
96 Bool(bool),
98 Array(Vec<String>),
100}
101
102impl LaunchOptions {
103 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn args(mut self, args: Vec<String>) -> Self {
110 self.args = Some(args);
111 self
112 }
113
114 pub fn artifacts_dir(mut self, path: String) -> Self {
116 self.artifacts_dir = Some(path);
117 self
118 }
119
120 pub fn channel(mut self, channel: String) -> Self {
122 self.channel = Some(channel);
123 self
124 }
125
126 pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
128 self.chromium_sandbox = Some(enabled);
129 self
130 }
131
132 pub fn devtools(mut self, enabled: bool) -> Self {
134 self.devtools = Some(enabled);
135 self
136 }
137
138 pub fn downloads_path(mut self, path: String) -> Self {
140 self.downloads_path = Some(path);
141 self
142 }
143
144 pub fn env(mut self, env: HashMap<String, String>) -> Self {
146 self.env = Some(env);
147 self
148 }
149
150 pub fn executable_path(mut self, path: String) -> Self {
152 self.executable_path = Some(path);
153 self
154 }
155
156 pub fn firefox_user_prefs(mut self, prefs: HashMap<String, Value>) -> Self {
158 self.firefox_user_prefs = Some(prefs);
159 self
160 }
161
162 pub fn handle_sighup(mut self, enabled: bool) -> Self {
164 self.handle_sighup = Some(enabled);
165 self
166 }
167
168 pub fn handle_sigint(mut self, enabled: bool) -> Self {
170 self.handle_sigint = Some(enabled);
171 self
172 }
173
174 pub fn handle_sigterm(mut self, enabled: bool) -> Self {
176 self.handle_sigterm = Some(enabled);
177 self
178 }
179
180 pub fn headless(mut self, enabled: bool) -> Self {
182 self.headless = Some(enabled);
183 self
184 }
185
186 pub fn ignore_default_args(mut self, args: IgnoreDefaultArgs) -> Self {
188 self.ignore_default_args = Some(args);
189 self
190 }
191
192 pub fn proxy(mut self, proxy: ProxySettings) -> Self {
194 self.proxy = Some(proxy);
195 self
196 }
197
198 pub fn slow_mo(mut self, ms: f64) -> Self {
200 self.slow_mo = Some(ms);
201 self
202 }
203
204 pub fn timeout(mut self, ms: f64) -> Self {
206 self.timeout = Some(ms);
207 self
208 }
209
210 pub fn traces_dir(mut self, path: String) -> Self {
212 self.traces_dir = Some(path);
213 self
214 }
215
216 pub(crate) fn normalize(self) -> Value {
225 let mut value =
226 serde_json::to_value(&self).expect("serialization of LaunchOptions cannot fail");
227
228 if value.get("timeout").is_none() {
231 value["timeout"] = json!(crate::DEFAULT_TIMEOUT_MS);
232 }
233
234 if let Some(env_map) = value.get_mut("env")
236 && let Some(map) = env_map.as_object()
237 {
238 let env_array: Vec<_> = map
239 .iter()
240 .map(|(k, v)| json!({"name": k, "value": v}))
241 .collect();
242 *env_map = json!(env_array);
243 }
244
245 if let Some(ignore) = value.get("ignoreDefaultArgs")
247 && let Some(b) = ignore.as_bool()
248 {
249 if b {
250 value["ignoreAllDefaultArgs"] = json!(true);
251 }
252 value
253 .as_object_mut()
254 .expect("params is a JSON object")
255 .remove("ignoreDefaultArgs");
256 }
257
258 value
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_launch_options_default() {
268 let opts = LaunchOptions::default();
269 assert!(opts.headless.is_none());
270 assert!(opts.args.is_none());
271 }
272
273 #[test]
274 fn test_launch_options_builder() {
275 let opts = LaunchOptions::default()
276 .headless(false)
277 .slow_mo(100.0)
278 .args(vec!["--no-sandbox".to_string()]);
279
280 assert_eq!(opts.headless, Some(false));
281 assert_eq!(opts.slow_mo, Some(100.0));
282 assert_eq!(opts.args, Some(vec!["--no-sandbox".to_string()]));
283 }
284
285 #[test]
286 fn test_launch_options_normalize_env() {
287 let opts = LaunchOptions::default().env(HashMap::from([
288 ("FOO".to_string(), "bar".to_string()),
289 ("BAZ".to_string(), "qux".to_string()),
290 ]));
291
292 let normalized = opts.normalize();
293
294 assert!(normalized["env"].is_array());
296 let env_array = normalized["env"].as_array().unwrap();
297 assert_eq!(env_array.len(), 2);
298
299 let names: Vec<_> = env_array
301 .iter()
302 .map(|v| v["name"].as_str().unwrap())
303 .collect();
304 assert!(names.contains(&"FOO"));
305 assert!(names.contains(&"BAZ"));
306 }
307
308 #[test]
309 fn test_launch_options_normalize_ignore_default_args_bool() {
310 let opts = LaunchOptions::default().ignore_default_args(IgnoreDefaultArgs::Bool(true));
311
312 let normalized = opts.normalize();
313
314 assert!(normalized["ignoreAllDefaultArgs"].as_bool().unwrap());
316 assert!(normalized.get("ignoreDefaultArgs").is_none());
317 }
318
319 #[test]
320 fn test_launch_options_normalize_ignore_default_args_array() {
321 let opts = LaunchOptions::default()
322 .ignore_default_args(IgnoreDefaultArgs::Array(vec!["--foo".to_string()]));
323
324 let normalized = opts.normalize();
325
326 assert!(normalized["ignoreDefaultArgs"].is_array());
328 assert_eq!(
329 normalized["ignoreDefaultArgs"][0].as_str().unwrap(),
330 "--foo"
331 );
332 }
333
334 #[test]
335 fn test_proxy_settings() {
336 let proxy = ProxySettings {
337 server: "http://proxy:8080".to_string(),
338 bypass: Some("localhost,127.0.0.1".to_string()),
339 username: Some("user".to_string()),
340 password: Some("pass".to_string()),
341 };
342
343 let opts = LaunchOptions::default().proxy(proxy);
344 assert!(opts.proxy.is_some());
345 }
346
347 #[test]
348 fn test_builder_pattern_chaining() {
349 let opts = LaunchOptions::new()
350 .headless(true)
351 .slow_mo(50.0)
352 .timeout(60000.0)
353 .args(vec![
354 "--no-sandbox".to_string(),
355 "--disable-gpu".to_string(),
356 ])
357 .channel("chrome".to_string());
358
359 assert_eq!(opts.headless, Some(true));
360 assert_eq!(opts.slow_mo, Some(50.0));
361 assert_eq!(opts.timeout, Some(60000.0));
362 assert_eq!(opts.args.as_ref().unwrap().len(), 2);
363 assert_eq!(opts.channel, Some("chrome".to_string()));
364 }
365}