1use super::{Geolocation, HttpCredentials, StorageState};
2use crate::api::launch_options::IgnoreDefaultArgs;
3use crate::protocol::ProxySettings;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Viewport {
12 pub width: u32,
14 pub height: u32,
16}
17
18impl From<crate::protocol::DeviceViewport> for Viewport {
19 fn from(size: crate::protocol::DeviceViewport) -> Self {
20 Self {
21 width: size.width,
22 height: size.height,
23 }
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Default)]
31#[serde(rename_all = "camelCase")]
32#[non_exhaustive]
33pub struct RecordHar {
34 pub path: String,
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub omit_content: Option<bool>,
39 #[serde(skip_serializing_if = "Option::is_none")]
42 pub content: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub mode: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub url_filter: Option<String>,
49}
50
51impl RecordHar {
52 pub fn new(path: impl Into<String>) -> Self {
54 Self {
55 path: path.into(),
56 omit_content: None,
57 content: None,
58 mode: None,
59 url_filter: None,
60 }
61 }
62 pub fn omit_content(mut self, omit_content: bool) -> Self {
64 self.omit_content = Some(omit_content);
65 self
66 }
67 pub fn content(mut self, content: impl Into<String>) -> Self {
69 self.content = Some(content.into());
70 self
71 }
72 pub fn mode(mut self, mode: impl Into<String>) -> Self {
74 self.mode = Some(mode.into());
75 self
76 }
77 pub fn url_filter(mut self, url_filter: impl Into<String>) -> Self {
79 self.url_filter = Some(url_filter.into());
80 self
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Default)]
88#[serde(rename_all = "camelCase")]
89#[non_exhaustive]
90pub struct RecordVideo {
91 pub dir: String,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub size: Option<Viewport>,
96 #[serde(skip_serializing_if = "Option::is_none")]
99 pub fps: Option<u32>,
100 #[serde(skip_serializing_if = "Option::is_none")]
104 pub show_actions: Option<crate::protocol::ShowActionsOptions>,
105}
106
107impl RecordVideo {
108 pub fn new(dir: impl Into<String>) -> Self {
110 Self {
111 dir: dir.into(),
112 ..Self::default()
113 }
114 }
115 pub fn size(mut self, size: Viewport) -> Self {
117 self.size = Some(size);
118 self
119 }
120 pub fn fps(mut self, fps: u32) -> Self {
122 self.fps = Some(fps);
123 self
124 }
125 pub fn show_actions(mut self, show_actions: crate::protocol::ShowActionsOptions) -> Self {
127 self.show_actions = Some(show_actions);
128 self
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
136#[non_exhaustive]
137pub enum AcceptDownloads {
138 #[serde(rename = "accept")]
140 Accept,
141 #[serde(rename = "deny")]
143 Deny,
144 #[serde(rename = "internal")]
146 Internal,
147}
148
149impl From<bool> for AcceptDownloads {
150 fn from(value: bool) -> Self {
151 if value { Self::Accept } else { Self::Deny }
152 }
153}
154
155fn serialize_header_pairs<S: serde::Serializer>(
159 headers: &Option<HashMap<String, String>>,
160 serializer: S,
161) -> std::result::Result<S::Ok, S::Error> {
162 #[derive(Serialize)]
163 struct NameValue<'a> {
164 name: &'a str,
165 value: &'a str,
166 }
167 match headers {
168 Some(headers) => serializer.collect_seq(
169 headers
170 .iter()
171 .map(|(name, value)| NameValue { name, value }),
172 ),
173 None => serializer.serialize_none(),
174 }
175}
176
177#[derive(Debug, Clone, Default, Serialize)]
216#[serde(rename_all = "camelCase")]
217#[non_exhaustive]
218pub struct BrowserContextOptions {
219 #[serde(skip_serializing_if = "Option::is_none")]
222 pub viewport: Option<Viewport>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
229 #[serde(rename = "noDefaultViewport")]
230 pub no_viewport: Option<bool>,
231
232 #[serde(skip_serializing_if = "Option::is_none")]
238 pub screen: Option<Viewport>,
239
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub user_agent: Option<String>,
243
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub locale: Option<String>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub timezone_id: Option<String>,
251
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub geolocation: Option<Geolocation>,
255
256 #[serde(rename = "httpCredentials", skip_serializing_if = "Option::is_none")]
258 pub http_credentials: Option<Vec<HttpCredentials>>,
259
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub permissions: Option<Vec<String>>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub proxy: Option<ProxySettings>,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub color_scheme: Option<String>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub has_touch: Option<bool>,
275
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub is_mobile: Option<bool>,
279
280 #[serde(rename = "javaScriptEnabled", skip_serializing_if = "Option::is_none")]
282 pub javascript_enabled: Option<bool>,
283
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub offline: Option<bool>,
287
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub accept_downloads: Option<AcceptDownloads>,
291
292 #[serde(rename = "bypassCSP", skip_serializing_if = "Option::is_none")]
294 pub bypass_csp: Option<bool>,
295
296 #[serde(rename = "ignoreHTTPSErrors", skip_serializing_if = "Option::is_none")]
298 pub ignore_https_errors: Option<bool>,
299
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub device_scale_factor: Option<f64>,
303
304 #[serde(
306 rename = "extraHTTPHeaders",
307 serialize_with = "serialize_header_pairs",
308 skip_serializing_if = "Option::is_none"
309 )]
310 pub extra_http_headers: Option<HashMap<String, String>>,
311
312 #[serde(rename = "baseURL", skip_serializing_if = "Option::is_none")]
314 pub base_url: Option<String>,
315
316 #[serde(skip_serializing_if = "Option::is_none")]
320 pub storage_state: Option<StorageState>,
321
322 #[serde(skip_serializing_if = "Option::is_none")]
325 pub storage_state_path: Option<String>,
326
327 #[serde(skip_serializing_if = "Option::is_none")]
330 pub args: Option<Vec<String>>,
331
332 #[serde(skip_serializing_if = "Option::is_none")]
334 pub channel: Option<String>,
335
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub chromium_sandbox: Option<bool>,
339
340 #[deprecated(
353 since = "0.20.0",
354 note = "removed from the protocol in Playwright 1.58; setting it has no effect. For Chromium, pass `--auto-open-devtools-for-tabs` in `args` (persistent contexts) or `LaunchOptions::args` instead"
355 )]
356 #[serde(skip_serializing)]
357 pub devtools: Option<bool>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub downloads_path: Option<String>,
362
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub executable_path: Option<String>,
366
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub firefox_user_prefs: Option<HashMap<String, serde_json::Value>>,
370
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub headless: Option<bool>,
374
375 #[serde(skip_serializing_if = "Option::is_none")]
381 pub ignore_default_args: Option<IgnoreDefaultArgs>,
382
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub slow_mo: Option<f64>,
386
387 #[serde(skip_serializing_if = "Option::is_none")]
391 pub timeout: Option<f64>,
392
393 #[serde(skip_serializing_if = "Option::is_none")]
395 pub traces_dir: Option<String>,
396
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub strict_selectors: Option<bool>,
400
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub reduced_motion: Option<String>,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub forced_colors: Option<String>,
408
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub service_workers: Option<String>,
412
413 #[serde(skip)]
417 pub record_har: Option<RecordHar>,
418
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub record_video: Option<RecordVideo>,
422}
423
424impl BrowserContextOptions {
425 pub fn builder() -> BrowserContextOptionsBuilder {
427 BrowserContextOptionsBuilder::default()
428 }
429}
430
431#[derive(Debug, Clone, Default)]
433pub struct BrowserContextOptionsBuilder {
434 viewport: Option<Viewport>,
435 no_viewport: Option<bool>,
436 screen: Option<Viewport>,
437 user_agent: Option<String>,
438 locale: Option<String>,
439 timezone_id: Option<String>,
440 geolocation: Option<Geolocation>,
441 http_credentials: Option<Vec<HttpCredentials>>,
442 permissions: Option<Vec<String>>,
443 proxy: Option<ProxySettings>,
444 color_scheme: Option<String>,
445 has_touch: Option<bool>,
446 is_mobile: Option<bool>,
447 javascript_enabled: Option<bool>,
448 offline: Option<bool>,
449 accept_downloads: Option<AcceptDownloads>,
450 bypass_csp: Option<bool>,
451 ignore_https_errors: Option<bool>,
452 device_scale_factor: Option<f64>,
453 extra_http_headers: Option<HashMap<String, String>>,
454 base_url: Option<String>,
455 storage_state: Option<StorageState>,
456 storage_state_path: Option<String>,
457 args: Option<Vec<String>>,
459 channel: Option<String>,
460 chromium_sandbox: Option<bool>,
461 devtools: Option<bool>,
462 downloads_path: Option<String>,
463 executable_path: Option<String>,
464 firefox_user_prefs: Option<HashMap<String, serde_json::Value>>,
465 headless: Option<bool>,
466 ignore_default_args: Option<IgnoreDefaultArgs>,
467 slow_mo: Option<f64>,
468 timeout: Option<f64>,
469 traces_dir: Option<String>,
470 strict_selectors: Option<bool>,
471 reduced_motion: Option<String>,
472 forced_colors: Option<String>,
473 service_workers: Option<String>,
474 record_har: Option<RecordHar>,
475 record_video: Option<RecordVideo>,
476}
477
478impl BrowserContextOptionsBuilder {
479 pub fn viewport(mut self, viewport: Viewport) -> Self {
481 self.viewport = Some(viewport);
482 self.no_viewport = None; self
484 }
485
486 pub fn no_viewport(mut self, no_viewport: bool) -> Self {
488 self.no_viewport = Some(no_viewport);
489 if no_viewport {
490 self.viewport = None; }
492 self
493 }
494
495 pub fn screen(mut self, screen: impl Into<Viewport>) -> Self {
501 self.screen = Some(screen.into());
502 self
503 }
504
505 pub fn user_agent(mut self, user_agent: String) -> Self {
507 self.user_agent = Some(user_agent);
508 self
509 }
510
511 pub fn locale(mut self, locale: String) -> Self {
513 self.locale = Some(locale);
514 self
515 }
516
517 pub fn timezone_id(mut self, timezone_id: String) -> Self {
519 self.timezone_id = Some(timezone_id);
520 self
521 }
522
523 pub fn geolocation(mut self, geolocation: Geolocation) -> Self {
525 self.geolocation = Some(geolocation);
526 self
527 }
528
529 pub fn http_credentials(mut self, credentials: Vec<HttpCredentials>) -> Self {
534 self.http_credentials = Some(credentials);
535 self
536 }
537
538 pub fn permissions(mut self, permissions: Vec<String>) -> Self {
540 self.permissions = Some(permissions);
541 self
542 }
543
544 pub fn proxy(mut self, proxy: ProxySettings) -> Self {
551 self.proxy = Some(proxy);
552 self
553 }
554
555 pub fn color_scheme(mut self, color_scheme: String) -> Self {
557 self.color_scheme = Some(color_scheme);
558 self
559 }
560
561 pub fn has_touch(mut self, has_touch: bool) -> Self {
563 self.has_touch = Some(has_touch);
564 self
565 }
566
567 pub fn is_mobile(mut self, is_mobile: bool) -> Self {
569 self.is_mobile = Some(is_mobile);
570 self
571 }
572
573 pub fn javascript_enabled(mut self, javascript_enabled: bool) -> Self {
575 self.javascript_enabled = Some(javascript_enabled);
576 self
577 }
578
579 pub fn offline(mut self, offline: bool) -> Self {
581 self.offline = Some(offline);
582 self
583 }
584
585 pub fn accept_downloads(mut self, accept_downloads: impl Into<AcceptDownloads>) -> Self {
588 self.accept_downloads = Some(accept_downloads.into());
589 self
590 }
591
592 pub fn bypass_csp(mut self, bypass_csp: bool) -> Self {
594 self.bypass_csp = Some(bypass_csp);
595 self
596 }
597
598 pub fn ignore_https_errors(mut self, ignore_https_errors: bool) -> Self {
600 self.ignore_https_errors = Some(ignore_https_errors);
601 self
602 }
603
604 pub fn device_scale_factor(mut self, device_scale_factor: f64) -> Self {
606 self.device_scale_factor = Some(device_scale_factor);
607 self
608 }
609
610 pub fn extra_http_headers(mut self, extra_http_headers: HashMap<String, String>) -> Self {
612 self.extra_http_headers = Some(extra_http_headers);
613 self
614 }
615
616 pub fn base_url(mut self, base_url: String) -> Self {
618 self.base_url = Some(base_url);
619 self
620 }
621
622 pub fn storage_state(mut self, storage_state: StorageState) -> Self {
632 self.storage_state = Some(storage_state);
633 self.storage_state_path = None; self
635 }
636
637 pub fn storage_state_path(mut self, path: String) -> Self {
670 self.storage_state_path = Some(path);
671 self.storage_state = None; self
673 }
674
675 pub fn args(mut self, args: Vec<String>) -> Self {
677 self.args = Some(args);
678 self
679 }
680
681 pub fn channel(mut self, channel: String) -> Self {
683 self.channel = Some(channel);
684 self
685 }
686
687 pub fn chromium_sandbox(mut self, enabled: bool) -> Self {
689 self.chromium_sandbox = Some(enabled);
690 self
691 }
692
693 #[deprecated(
695 since = "0.20.0",
696 note = "removed from the protocol in Playwright 1.58; setting it has no effect. For Chromium, pass `--auto-open-devtools-for-tabs` in `args` (persistent contexts) or `LaunchOptions::args` instead"
697 )]
698 pub fn devtools(mut self, enabled: bool) -> Self {
699 self.devtools = Some(enabled);
700 self
701 }
702
703 pub fn downloads_path(mut self, path: String) -> Self {
705 self.downloads_path = Some(path);
706 self
707 }
708
709 pub fn executable_path(mut self, path: String) -> Self {
711 self.executable_path = Some(path);
712 self
713 }
714
715 pub fn firefox_user_prefs(mut self, prefs: HashMap<String, serde_json::Value>) -> Self {
717 self.firefox_user_prefs = Some(prefs);
718 self
719 }
720
721 pub fn headless(mut self, enabled: bool) -> Self {
723 self.headless = Some(enabled);
724 self
725 }
726
727 pub fn ignore_default_args(mut self, args: IgnoreDefaultArgs) -> Self {
735 self.ignore_default_args = Some(args);
736 self
737 }
738
739 pub fn slow_mo(mut self, ms: f64) -> Self {
741 self.slow_mo = Some(ms);
742 self
743 }
744
745 pub fn timeout(mut self, ms: f64) -> Self {
747 self.timeout = Some(ms);
748 self
749 }
750
751 pub fn traces_dir(mut self, path: String) -> Self {
753 self.traces_dir = Some(path);
754 self
755 }
756
757 pub fn strict_selectors(mut self, enabled: bool) -> Self {
759 self.strict_selectors = Some(enabled);
760 self
761 }
762
763 pub fn reduced_motion(mut self, value: String) -> Self {
765 self.reduced_motion = Some(value);
766 self
767 }
768
769 pub fn forced_colors(mut self, value: String) -> Self {
771 self.forced_colors = Some(value);
772 self
773 }
774
775 pub fn service_workers(mut self, value: String) -> Self {
777 self.service_workers = Some(value);
778 self
779 }
780
781 pub fn record_har(mut self, record_har: RecordHar) -> Self {
802 self.record_har = Some(record_har);
803 self
804 }
805
806 pub fn record_video(mut self, record_video: RecordVideo) -> Self {
808 self.record_video = Some(record_video);
809 self
810 }
811
812 pub fn build(self) -> BrowserContextOptions {
814 BrowserContextOptions {
815 viewport: self.viewport,
816 no_viewport: self.no_viewport,
817 screen: self.screen,
818 user_agent: self.user_agent,
819 locale: self.locale,
820 timezone_id: self.timezone_id,
821 geolocation: self.geolocation,
822 http_credentials: self.http_credentials,
823 permissions: self.permissions,
824 proxy: self.proxy,
825 color_scheme: self.color_scheme,
826 has_touch: self.has_touch,
827 is_mobile: self.is_mobile,
828 javascript_enabled: self.javascript_enabled,
829 offline: self.offline,
830 accept_downloads: self.accept_downloads,
831 bypass_csp: self.bypass_csp,
832 ignore_https_errors: self.ignore_https_errors,
833 device_scale_factor: self.device_scale_factor,
834 extra_http_headers: self.extra_http_headers,
835 base_url: self.base_url,
836 storage_state: self.storage_state,
837 storage_state_path: self.storage_state_path,
838 args: self.args,
840 channel: self.channel,
841 chromium_sandbox: self.chromium_sandbox,
842 #[allow(deprecated)]
843 devtools: self.devtools,
844 downloads_path: self.downloads_path,
845 executable_path: self.executable_path,
846 firefox_user_prefs: self.firefox_user_prefs,
847 headless: self.headless,
848 ignore_default_args: self.ignore_default_args,
849 slow_mo: self.slow_mo,
850 timeout: self.timeout,
851 traces_dir: self.traces_dir,
852 strict_selectors: self.strict_selectors,
853 reduced_motion: self.reduced_motion,
854 forced_colors: self.forced_colors,
855 service_workers: self.service_workers,
856 record_har: self.record_har,
857 record_video: self.record_video,
858 }
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn test_browser_context_options_ignore_default_args_bool_serialization() {
868 let options = BrowserContextOptions::builder()
869 .ignore_default_args(IgnoreDefaultArgs::Bool(true))
870 .build();
871
872 let value = serde_json::to_value(&options).unwrap();
873 assert_eq!(value["ignoreDefaultArgs"], serde_json::json!(true));
874 }
875
876 #[test]
877 fn test_browser_context_options_ignore_default_args_array_serialization() {
878 let options = BrowserContextOptions::builder()
879 .ignore_default_args(IgnoreDefaultArgs::Array(vec!["--foo".to_string()]))
880 .build();
881
882 let value = serde_json::to_value(&options).unwrap();
883 assert_eq!(value["ignoreDefaultArgs"], serde_json::json!(["--foo"]));
884 }
885
886 #[test]
887 fn test_browser_context_options_ignore_default_args_absent() {
888 let options = BrowserContextOptions::builder().build();
889
890 let value = serde_json::to_value(&options).unwrap();
891 assert!(value.get("ignoreDefaultArgs").is_none());
892 }
893
894 #[test]
895 fn every_key_sent_is_a_parameter_the_driver_declares() {
896 use crate::protocol_spec::{
897 BROWSER, BROWSER_TYPE, command_parameters, keys, mixin_properties, nested_properties,
898 };
899
900 let options = BrowserContextOptions {
903 viewport: Some(Viewport {
904 width: 1,
905 height: 1,
906 }),
907 no_viewport: Some(false),
908 screen: Some(Viewport {
909 width: 1,
910 height: 1,
911 }),
912 user_agent: Some(String::new()),
913 locale: Some(String::new()),
914 timezone_id: Some(String::new()),
915 geolocation: Some(crate::protocol::Geolocation {
916 latitude: 0.0,
917 longitude: 0.0,
918 accuracy: None,
919 }),
920 http_credentials: Some(vec![crate::protocol::HttpCredentials::new("u", "p")]),
921 permissions: Some(vec![]),
922 proxy: Some(crate::protocol::ProxySettings::new("http://proxy")),
923 color_scheme: Some(String::new()),
924 has_touch: Some(false),
925 is_mobile: Some(false),
926 javascript_enabled: Some(false),
927 offline: Some(false),
928 accept_downloads: Some(AcceptDownloads::Accept),
929 bypass_csp: Some(false),
930 ignore_https_errors: Some(false),
931 device_scale_factor: Some(1.0),
932 extra_http_headers: Some(HashMap::new()),
933 base_url: Some(String::new()),
934 storage_state: Some(crate::protocol::StorageState::default()),
935 storage_state_path: Some(String::new()),
936 args: Some(vec![]),
937 channel: Some(String::new()),
938 chromium_sandbox: Some(false),
939 #[allow(deprecated)]
940 devtools: Some(false),
941 downloads_path: Some(String::new()),
942 executable_path: Some(String::new()),
943 firefox_user_prefs: Some(HashMap::new()),
944 headless: Some(true),
945 ignore_default_args: Some(IgnoreDefaultArgs::Array(vec![])),
946 slow_mo: Some(0.0),
947 timeout: Some(0.0),
948 traces_dir: Some(String::new()),
949 strict_selectors: Some(false),
950 reduced_motion: Some(String::new()),
951 forced_colors: Some(String::new()),
952 service_workers: Some(String::new()),
953 record_har: Some(RecordHar::new("h.har")),
954 record_video: Some(
955 RecordVideo::new("videos")
956 .size(Viewport {
957 width: 1,
958 height: 1,
959 })
960 .fps(30)
961 .show_actions(
962 crate::protocol::ShowActionsOptions::default()
963 .duration(1.0)
964 .style(crate::protocol::ShowActionsStyle::default().title("x")),
965 ),
966 ),
967 };
968
969 let value = serde_json::to_value(&options).unwrap();
970 let video = &value["recordVideo"];
971 assert!(keys(video).is_subset(&nested_properties("ContextOptions", "recordVideo")));
972 assert!(keys(&video["showActions"]).is_subset(&mixin_properties("ShowActionsOptions")));
973 let sent = keys(&value);
974 let mut declared = command_parameters(BROWSER, "newContext");
975 declared.extend(command_parameters(BROWSER_TYPE, "launchPersistentContext"));
976 let undeclared: Vec<&str> = sent
977 .iter()
978 .filter(|key| !declared.contains(*key))
979 .map(String::as_str)
980 .collect();
981 assert_eq!(undeclared, ["storageStatePath", "timeout"]);
985 }
986
987 #[test]
988 fn acronym_options_use_the_driver_spelling() {
989 let options = BrowserContextOptions::builder()
990 .javascript_enabled(false)
991 .bypass_csp(true)
992 .ignore_https_errors(true)
993 .base_url("http://localhost".to_string())
994 .build();
995
996 let value = serde_json::to_value(&options).unwrap();
997 assert_eq!(value["javaScriptEnabled"], serde_json::json!(false));
998 assert_eq!(value["bypassCSP"], serde_json::json!(true));
999 assert_eq!(value["ignoreHTTPSErrors"], serde_json::json!(true));
1000 assert_eq!(value["baseURL"], serde_json::json!("http://localhost"));
1001 for wrong in [
1002 "javascriptEnabled",
1003 "bypassCsp",
1004 "ignoreHttpsErrors",
1005 "baseUrl",
1006 ] {
1007 assert!(value.get(wrong).is_none(), "sent the unknown key {wrong}");
1008 }
1009 }
1010
1011 #[test]
1012 fn extra_http_headers_serialize_as_name_value_pairs() {
1013 let headers = HashMap::from([
1014 ("X-One".to_string(), "1".to_string()),
1015 ("X-Two".to_string(), "2".to_string()),
1016 ]);
1017 let options = BrowserContextOptions::builder()
1018 .extra_http_headers(headers)
1019 .build();
1020
1021 let value = serde_json::to_value(&options).unwrap();
1022 assert!(value.get("extraHttpHeaders").is_none());
1023 let mut pairs = value["extraHTTPHeaders"]
1024 .as_array()
1025 .expect("extraHTTPHeaders is a list")
1026 .clone();
1027 pairs.sort_by_key(|pair| pair["name"].as_str().unwrap_or_default().to_string());
1028 assert_eq!(
1029 pairs,
1030 vec![
1031 serde_json::json!({"name": "X-One", "value": "1"}),
1032 serde_json::json!({"name": "X-Two", "value": "2"}),
1033 ]
1034 );
1035 }
1036
1037 #[test]
1038 fn test_accept_downloads_serializes_as_protocol_string() {
1039 for (variant, expected) in [
1040 (AcceptDownloads::Accept, "accept"),
1041 (AcceptDownloads::Deny, "deny"),
1042 (AcceptDownloads::Internal, "internal"),
1043 ] {
1044 let options = BrowserContextOptions::builder()
1045 .accept_downloads(variant)
1046 .build();
1047 let value = serde_json::to_value(&options).unwrap();
1048 assert_eq!(value["acceptDownloads"], serde_json::json!(expected));
1049 }
1050 }
1051
1052 #[test]
1053 fn test_accept_downloads_bool_compatibility() {
1054 let opts = BrowserContextOptions::builder()
1055 .accept_downloads(true)
1056 .build();
1057 assert_eq!(opts.accept_downloads, Some(AcceptDownloads::Accept));
1058
1059 let opts = BrowserContextOptions::builder()
1060 .accept_downloads(false)
1061 .build();
1062 assert_eq!(opts.accept_downloads, Some(AcceptDownloads::Deny));
1063 }
1064}