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")]
16#[non_exhaustive]
17pub struct LaunchOptions {
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub args: Option<Vec<String>>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
26 pub artifacts_dir: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub channel: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub chromium_sandbox: Option<bool>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub devtools: Option<bool>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub downloads_path: Option<String>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub env: Option<HashMap<String, String>>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub executable_path: Option<String>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub firefox_user_prefs: Option<HashMap<String, Value>>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub handle_sighup: Option<bool>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub handle_sigint: Option<bool>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub handle_sigterm: Option<bool>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub headless: Option<bool>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub ignore_default_args: Option<IgnoreDefaultArgs>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub proxy: Option<ProxySettings>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub slow_mo: Option<f64>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub timeout: Option<f64>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub traces_dir: Option<String>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(untagged)]
96#[non_exhaustive]
97pub enum IgnoreDefaultArgs {
98 Bool(bool),
100 Array(Vec<String>),
102}
103
104impl LaunchOptions {
105 pub fn new() -> Self {
107 Self::default()
108 }
109
110 pub fn args(mut self, args: Vec<String>) -> Self {
112 self.args = Some(args);
113 self
114 }
115
116 pub fn artifacts_dir(mut self, path: String) -> Self {
118 self.artifacts_dir = Some(path);
119 self
120 }
121
122 pub fn channel(mut self, channel: String) -> Self {
124 self.channel = Some(channel);
125 self
126 }
127
128 pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
130 self.chromium_sandbox = Some(enabled);
131 self
132 }
133
134 pub fn devtools(mut self, enabled: bool) -> Self {
136 self.devtools = Some(enabled);
137 self
138 }
139
140 pub fn downloads_path(mut self, path: String) -> Self {
142 self.downloads_path = Some(path);
143 self
144 }
145
146 pub fn env(mut self, env: HashMap<String, String>) -> Self {
148 self.env = Some(env);
149 self
150 }
151
152 pub fn executable_path(mut self, path: String) -> Self {
154 self.executable_path = Some(path);
155 self
156 }
157
158 pub fn firefox_user_prefs(mut self, prefs: HashMap<String, Value>) -> Self {
160 self.firefox_user_prefs = Some(prefs);
161 self
162 }
163
164 pub fn handle_sighup(mut self, enabled: bool) -> Self {
166 self.handle_sighup = Some(enabled);
167 self
168 }
169
170 pub fn handle_sigint(mut self, enabled: bool) -> Self {
172 self.handle_sigint = Some(enabled);
173 self
174 }
175
176 pub fn handle_sigterm(mut self, enabled: bool) -> Self {
178 self.handle_sigterm = Some(enabled);
179 self
180 }
181
182 pub fn headless(mut self, enabled: bool) -> Self {
184 self.headless = Some(enabled);
185 self
186 }
187
188 pub fn ignore_default_args(mut self, args: IgnoreDefaultArgs) -> Self {
190 self.ignore_default_args = Some(args);
191 self
192 }
193
194 pub fn proxy(mut self, proxy: ProxySettings) -> Self {
196 self.proxy = Some(proxy);
197 self
198 }
199
200 pub fn slow_mo(mut self, ms: f64) -> Self {
202 self.slow_mo = Some(ms);
203 self
204 }
205
206 pub fn timeout(mut self, ms: f64) -> Self {
208 self.timeout = Some(ms);
209 self
210 }
211
212 pub fn traces_dir(mut self, path: String) -> Self {
214 self.traces_dir = Some(path);
215 self
216 }
217
218 pub(crate) fn normalize(self) -> Value {
227 let mut value =
228 serde_json::to_value(&self).expect("serialization of LaunchOptions cannot fail");
229
230 if value.get("timeout").is_none() {
233 value["timeout"] = json!(crate::DEFAULT_TIMEOUT_MS);
234 }
235
236 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 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 assert!(normalized["env"].is_array());
298 let env_array = normalized["env"].as_array().unwrap();
299 assert_eq!(env_array.len(), 2);
300
301 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 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 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}