playwright_rs/api/
launch_options.rs1use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct LaunchOptions {
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub args: Option<Vec<String>>,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub channel: Option<String>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub chromium_sandbox: Option<bool>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub devtools: Option<bool>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub downloads_path: Option<String>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub env: Option<HashMap<String, String>>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub executable_path: Option<String>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub firefox_user_prefs: Option<HashMap<String, Value>>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub handle_sighup: Option<bool>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub handle_sigint: Option<bool>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub handle_sigterm: Option<bool>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub headless: Option<bool>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub ignore_default_args: Option<IgnoreDefaultArgs>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub proxy: Option<ProxySettings>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub slow_mo: Option<f64>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub timeout: Option<f64>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub traces_dir: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(untagged)]
88pub enum IgnoreDefaultArgs {
89 Bool(bool),
91 Array(Vec<String>),
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ProxySettings {
99 pub server: String,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub bypass: Option<String>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub username: Option<String>,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub password: Option<String>,
113}
114
115impl LaunchOptions {
116 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn args(mut self, args: Vec<String>) -> Self {
123 self.args = Some(args);
124 self
125 }
126
127 pub fn channel(mut self, channel: String) -> Self {
129 self.channel = Some(channel);
130 self
131 }
132
133 pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
135 self.chromium_sandbox = Some(enabled);
136 self
137 }
138
139 pub fn devtools(mut self, enabled: bool) -> Self {
141 self.devtools = Some(enabled);
142 self
143 }
144
145 pub fn downloads_path(mut self, path: String) -> Self {
147 self.downloads_path = Some(path);
148 self
149 }
150
151 pub fn env(mut self, env: HashMap<String, String>) -> Self {
153 self.env = Some(env);
154 self
155 }
156
157 pub fn executable_path(mut self, path: String) -> Self {
159 self.executable_path = Some(path);
160 self
161 }
162
163 pub fn firefox_user_prefs(mut self, prefs: HashMap<String, Value>) -> Self {
165 self.firefox_user_prefs = Some(prefs);
166 self
167 }
168
169 pub fn handle_sighup(mut self, enabled: bool) -> Self {
171 self.handle_sighup = Some(enabled);
172 self
173 }
174
175 pub fn handle_sigint(mut self, enabled: bool) -> Self {
177 self.handle_sigint = Some(enabled);
178 self
179 }
180
181 pub fn handle_sigterm(mut self, enabled: bool) -> Self {
183 self.handle_sigterm = Some(enabled);
184 self
185 }
186
187 pub fn headless(mut self, enabled: bool) -> Self {
189 self.headless = Some(enabled);
190 self
191 }
192
193 pub fn ignore_default_args(mut self, args: IgnoreDefaultArgs) -> Self {
195 self.ignore_default_args = Some(args);
196 self
197 }
198
199 pub fn proxy(mut self, proxy: ProxySettings) -> Self {
201 self.proxy = Some(proxy);
202 self
203 }
204
205 pub fn slow_mo(mut self, ms: f64) -> Self {
207 self.slow_mo = Some(ms);
208 self
209 }
210
211 pub fn timeout(mut self, ms: f64) -> Self {
213 self.timeout = Some(ms);
214 self
215 }
216
217 pub fn traces_dir(mut self, path: String) -> Self {
219 self.traces_dir = Some(path);
220 self
221 }
222
223 pub(crate) fn normalize(self) -> Value {
232 let mut value = serde_json::to_value(&self).unwrap();
233
234 if value.get("timeout").is_none() {
237 value["timeout"] = json!(crate::DEFAULT_TIMEOUT_MS);
238 }
239
240 if let Some(env_map) = value.get_mut("env") {
242 if let Some(map) = env_map.as_object() {
243 let env_array: Vec<_> = map
244 .iter()
245 .map(|(k, v)| json!({"name": k, "value": v}))
246 .collect();
247 *env_map = json!(env_array);
248 }
249 }
250
251 if let Some(ignore) = value.get("ignoreDefaultArgs") {
253 if let Some(b) = ignore.as_bool() {
254 if b {
255 value["ignoreAllDefaultArgs"] = json!(true);
256 }
257 value.as_object_mut().unwrap().remove("ignoreDefaultArgs");
258 }
259 }
260
261 value
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_launch_options_default() {
271 let opts = LaunchOptions::default();
272 assert!(opts.headless.is_none());
273 assert!(opts.args.is_none());
274 }
275
276 #[test]
277 fn test_launch_options_builder() {
278 let opts = LaunchOptions::default()
279 .headless(false)
280 .slow_mo(100.0)
281 .args(vec!["--no-sandbox".to_string()]);
282
283 assert_eq!(opts.headless, Some(false));
284 assert_eq!(opts.slow_mo, Some(100.0));
285 assert_eq!(opts.args, Some(vec!["--no-sandbox".to_string()]));
286 }
287
288 #[test]
289 fn test_launch_options_normalize_env() {
290 let opts = LaunchOptions::default().env(HashMap::from([
291 ("FOO".to_string(), "bar".to_string()),
292 ("BAZ".to_string(), "qux".to_string()),
293 ]));
294
295 let normalized = opts.normalize();
296
297 assert!(normalized["env"].is_array());
299 let env_array = normalized["env"].as_array().unwrap();
300 assert_eq!(env_array.len(), 2);
301
302 let names: Vec<_> = env_array
304 .iter()
305 .map(|v| v["name"].as_str().unwrap())
306 .collect();
307 assert!(names.contains(&"FOO"));
308 assert!(names.contains(&"BAZ"));
309 }
310
311 #[test]
312 fn test_launch_options_normalize_ignore_default_args_bool() {
313 let opts = LaunchOptions::default().ignore_default_args(IgnoreDefaultArgs::Bool(true));
314
315 let normalized = opts.normalize();
316
317 assert!(normalized["ignoreAllDefaultArgs"].as_bool().unwrap());
319 assert!(normalized.get("ignoreDefaultArgs").is_none());
320 }
321
322 #[test]
323 fn test_launch_options_normalize_ignore_default_args_array() {
324 let opts = LaunchOptions::default()
325 .ignore_default_args(IgnoreDefaultArgs::Array(vec!["--foo".to_string()]));
326
327 let normalized = opts.normalize();
328
329 assert!(normalized["ignoreDefaultArgs"].is_array());
331 assert_eq!(
332 normalized["ignoreDefaultArgs"][0].as_str().unwrap(),
333 "--foo"
334 );
335 }
336
337 #[test]
338 fn test_proxy_settings() {
339 let proxy = ProxySettings {
340 server: "http://proxy:8080".to_string(),
341 bypass: Some("localhost,127.0.0.1".to_string()),
342 username: Some("user".to_string()),
343 password: Some("pass".to_string()),
344 };
345
346 let opts = LaunchOptions::default().proxy(proxy);
347 assert!(opts.proxy.is_some());
348 }
349
350 #[test]
351 fn test_builder_pattern_chaining() {
352 let opts = LaunchOptions::new()
353 .headless(true)
354 .slow_mo(50.0)
355 .timeout(60000.0)
356 .args(vec![
357 "--no-sandbox".to_string(),
358 "--disable-gpu".to_string(),
359 ])
360 .channel("chrome".to_string());
361
362 assert_eq!(opts.headless, Some(true));
363 assert_eq!(opts.slow_mo, Some(50.0));
364 assert_eq!(opts.timeout, Some(60000.0));
365 assert_eq!(opts.args.as_ref().unwrap().len(), 2);
366 assert_eq!(opts.channel, Some("chrome".to_string()));
367 }
368}