1use serde::{Deserialize, Serialize};
23use serde_json::Value;
24
25use crate::identifiers::{ElementId, InterceptId};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(untagged)]
36pub enum Command {
37 BrowsingContext(BrowsingContextCommand),
39 Element(ElementCommand),
41 Session(SessionCommand),
43 Script(ScriptCommand),
45 Input(InputCommand),
47 Network(NetworkCommand),
49 Proxy(ProxyCommand),
51 Storage(StorageCommand),
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(tag = "method", content = "params")]
62pub enum BrowsingContextCommand {
63 #[serde(rename = "browsingContext.navigate")]
65 Navigate {
66 url: String,
68 },
69
70 #[serde(rename = "browsingContext.reload")]
72 Reload,
73
74 #[serde(rename = "browsingContext.goBack")]
76 GoBack,
77
78 #[serde(rename = "browsingContext.goForward")]
80 GoForward,
81
82 #[serde(rename = "browsingContext.getTitle")]
84 GetTitle,
85
86 #[serde(rename = "browsingContext.getUrl")]
88 GetUrl,
89
90 #[serde(rename = "browsingContext.newTab")]
92 NewTab,
93
94 #[serde(rename = "browsingContext.closeTab")]
96 CloseTab,
97
98 #[serde(rename = "browsingContext.focusTab")]
100 FocusTab,
101
102 #[serde(rename = "browsingContext.focusWindow")]
104 FocusWindow,
105
106 #[serde(rename = "browsingContext.switchToFrame")]
108 SwitchToFrame {
109 #[serde(rename = "elementId")]
111 element_id: ElementId,
112 },
113
114 #[serde(rename = "browsingContext.switchToFrameByIndex")]
116 SwitchToFrameByIndex {
117 index: usize,
119 },
120
121 #[serde(rename = "browsingContext.switchToFrameByUrl")]
123 SwitchToFrameByUrl {
124 #[serde(rename = "urlPattern")]
126 url_pattern: String,
127 },
128
129 #[serde(rename = "browsingContext.switchToParentFrame")]
131 SwitchToParentFrame,
132
133 #[serde(rename = "browsingContext.getFrameCount")]
135 GetFrameCount,
136
137 #[serde(rename = "browsingContext.getAllFrames")]
139 GetAllFrames,
140
141 #[serde(rename = "browsingContext.captureScreenshot")]
143 CaptureScreenshot {
144 format: String,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 quality: Option<u8>,
149 },
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(tag = "method", content = "params")]
159pub enum ElementCommand {
160 #[serde(rename = "element.find")]
162 Find {
163 strategy: String,
165 value: String,
167 #[serde(rename = "parentId", skip_serializing_if = "Option::is_none")]
169 parent_id: Option<ElementId>,
170 },
171
172 #[serde(rename = "element.findAll")]
174 FindAll {
175 strategy: String,
177 value: String,
179 #[serde(rename = "parentId", skip_serializing_if = "Option::is_none")]
181 parent_id: Option<ElementId>,
182 },
183
184 #[serde(rename = "element.getProperty")]
186 GetProperty {
187 #[serde(rename = "elementId")]
189 element_id: ElementId,
190 name: String,
192 },
193
194 #[serde(rename = "element.setProperty")]
196 SetProperty {
197 #[serde(rename = "elementId")]
199 element_id: ElementId,
200 name: String,
202 value: Value,
204 },
205
206 #[serde(rename = "element.callMethod")]
208 CallMethod {
209 #[serde(rename = "elementId")]
211 element_id: ElementId,
212 name: String,
214 #[serde(default)]
216 args: Vec<Value>,
217 },
218
219 #[serde(rename = "element.subscribe")]
221 Subscribe {
222 strategy: String,
224 value: String,
226 #[serde(rename = "oneShot")]
228 one_shot: bool,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 timeout: Option<u64>,
232 },
233
234 #[serde(rename = "element.unsubscribe")]
236 Unsubscribe {
237 #[serde(rename = "subscriptionId")]
239 subscription_id: String,
240 },
241
242 #[serde(rename = "element.watchRemoval")]
244 WatchRemoval {
245 #[serde(rename = "elementId")]
247 element_id: ElementId,
248 },
249
250 #[serde(rename = "element.unwatchRemoval")]
252 UnwatchRemoval {
253 #[serde(rename = "elementId")]
255 element_id: ElementId,
256 },
257
258 #[serde(rename = "element.watchAttribute")]
260 WatchAttribute {
261 #[serde(rename = "elementId")]
263 element_id: ElementId,
264 #[serde(rename = "attributeName", skip_serializing_if = "Option::is_none")]
266 attribute_name: Option<String>,
267 },
268
269 #[serde(rename = "element.unwatchAttribute")]
271 UnwatchAttribute {
272 #[serde(rename = "elementId")]
274 element_id: ElementId,
275 },
276
277 #[serde(rename = "element.captureScreenshot")]
279 CaptureScreenshot {
280 #[serde(rename = "elementId")]
282 element_id: ElementId,
283 format: String,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 quality: Option<u8>,
288 },
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
297#[serde(tag = "method", content = "params")]
298pub enum SessionCommand {
299 #[serde(rename = "session.status")]
301 Status,
302
303 #[serde(rename = "session.stealLogs")]
305 StealLogs,
306
307 #[serde(rename = "session.subscribe")]
309 Subscribe {
310 events: Vec<String>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 selectors: Option<Vec<String>>,
315 },
316
317 #[serde(rename = "session.unsubscribe")]
319 Unsubscribe {
320 subscription_id: String,
322 },
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
331#[serde(tag = "method", content = "params")]
332pub enum ScriptCommand {
333 #[serde(rename = "script.evaluate")]
335 Evaluate {
336 script: String,
338 #[serde(default)]
340 args: Vec<Value>,
341 },
342
343 #[serde(rename = "script.evaluateAsync")]
345 EvaluateAsync {
346 script: String,
348 #[serde(default)]
350 args: Vec<Value>,
351 },
352
353 #[serde(rename = "script.addPreloadScript")]
355 AddPreloadScript {
356 script: String,
358 },
359
360 #[serde(rename = "script.removePreloadScript")]
362 RemovePreloadScript {
363 script_id: String,
365 },
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
374#[serde(tag = "method", content = "params")]
375pub enum InputCommand {
376 #[serde(rename = "input.typeKey")]
378 TypeKey {
379 #[serde(rename = "elementId")]
381 element_id: ElementId,
382 key: String,
384 code: String,
386 #[serde(rename = "keyCode")]
388 key_code: u32,
389 printable: bool,
391 #[serde(default)]
393 ctrl: bool,
394 #[serde(default)]
396 shift: bool,
397 #[serde(default)]
399 alt: bool,
400 #[serde(default)]
402 meta: bool,
403 },
404
405 #[serde(rename = "input.typeText")]
407 TypeText {
408 #[serde(rename = "elementId")]
410 element_id: ElementId,
411 text: String,
413 },
414
415 #[serde(rename = "input.mouseClick")]
417 MouseClick {
418 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
420 element_id: Option<ElementId>,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 x: Option<i32>,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 y: Option<i32>,
427 #[serde(default)]
429 button: u8,
430 },
431
432 #[serde(rename = "input.mouseMove")]
434 MouseMove {
435 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
437 element_id: Option<ElementId>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 x: Option<i32>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 y: Option<i32>,
444 },
445
446 #[serde(rename = "input.mouseDown")]
448 MouseDown {
449 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
451 element_id: Option<ElementId>,
452 #[serde(skip_serializing_if = "Option::is_none")]
454 x: Option<i32>,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 y: Option<i32>,
458 #[serde(default)]
460 button: u8,
461 },
462
463 #[serde(rename = "input.mouseUp")]
465 MouseUp {
466 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
468 element_id: Option<ElementId>,
469 #[serde(skip_serializing_if = "Option::is_none")]
471 x: Option<i32>,
472 #[serde(skip_serializing_if = "Option::is_none")]
474 y: Option<i32>,
475 #[serde(default)]
477 button: u8,
478 },
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize)]
487#[serde(tag = "method", content = "params")]
488pub enum NetworkCommand {
489 #[serde(rename = "network.addIntercept")]
491 AddIntercept {
492 #[serde(default, rename = "interceptRequests")]
494 intercept_requests: bool,
495 #[serde(default, rename = "interceptRequestHeaders")]
497 intercept_request_headers: bool,
498 #[serde(default, rename = "interceptRequestBody")]
500 intercept_request_body: bool,
501 #[serde(default, rename = "interceptResponses")]
503 intercept_responses: bool,
504 #[serde(default, rename = "interceptResponseBody")]
506 intercept_response_body: bool,
507 #[serde(rename = "urlPatterns", skip_serializing_if = "Option::is_none")]
509 url_patterns: Option<Vec<String>>,
510 #[serde(rename = "resourceTypes", skip_serializing_if = "Option::is_none")]
512 resource_types: Option<Vec<String>>,
513 },
514
515 #[serde(rename = "network.removeIntercept")]
517 RemoveIntercept {
518 #[serde(rename = "interceptId")]
520 intercept_id: InterceptId,
521 },
522
523 #[serde(rename = "network.setBlockRules")]
525 SetBlockRules {
526 patterns: Vec<String>,
528 },
529
530 #[serde(rename = "network.clearBlockRules")]
532 ClearBlockRules,
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize)]
541#[serde(tag = "method", content = "params")]
542pub enum ProxyCommand {
543 #[serde(rename = "proxy.setWindowProxy")]
545 SetWindowProxy {
546 #[serde(rename = "type")]
548 proxy_type: String,
549 host: String,
551 port: u16,
553 #[serde(skip_serializing_if = "Option::is_none")]
555 username: Option<String>,
556 #[serde(skip_serializing_if = "Option::is_none")]
558 password: Option<String>,
559 #[serde(rename = "proxyDns", default)]
561 proxy_dns: bool,
562 },
563
564 #[serde(rename = "proxy.clearWindowProxy")]
566 ClearWindowProxy,
567
568 #[serde(rename = "proxy.setTabProxy")]
570 SetTabProxy {
571 #[serde(rename = "type")]
573 proxy_type: String,
574 host: String,
576 port: u16,
578 #[serde(skip_serializing_if = "Option::is_none")]
580 username: Option<String>,
581 #[serde(skip_serializing_if = "Option::is_none")]
583 password: Option<String>,
584 #[serde(rename = "proxyDns", default)]
586 proxy_dns: bool,
587 },
588
589 #[serde(rename = "proxy.clearTabProxy")]
591 ClearTabProxy,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
600#[serde(tag = "method", content = "params")]
601pub enum StorageCommand {
602 #[serde(rename = "storage.getCookie")]
604 GetCookie {
605 name: String,
607 #[serde(skip_serializing_if = "Option::is_none")]
609 url: Option<String>,
610 },
611
612 #[serde(rename = "storage.setCookie")]
614 SetCookie {
615 cookie: Cookie,
617 #[serde(skip_serializing_if = "Option::is_none")]
619 url: Option<String>,
620 },
621
622 #[serde(rename = "storage.deleteCookie")]
624 DeleteCookie {
625 name: String,
627 #[serde(skip_serializing_if = "Option::is_none")]
629 url: Option<String>,
630 },
631
632 #[serde(rename = "storage.getAllCookies")]
634 GetAllCookies {
635 #[serde(skip_serializing_if = "Option::is_none")]
637 url: Option<String>,
638 },
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize)]
647pub struct Cookie {
648 pub name: String,
650 pub value: String,
652 #[serde(skip_serializing_if = "Option::is_none")]
654 pub domain: Option<String>,
655 #[serde(skip_serializing_if = "Option::is_none")]
657 pub path: Option<String>,
658 #[serde(skip_serializing_if = "Option::is_none")]
660 pub secure: Option<bool>,
661 #[serde(rename = "httpOnly", skip_serializing_if = "Option::is_none")]
663 pub http_only: Option<bool>,
664 #[serde(rename = "sameSite", skip_serializing_if = "Option::is_none")]
666 pub same_site: Option<String>,
667 #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")]
669 pub expiration_date: Option<f64>,
670}
671
672impl Cookie {
673 #[inline]
675 #[must_use]
676 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
677 Self {
678 name: name.into(),
679 value: value.into(),
680 domain: None,
681 path: None,
682 secure: None,
683 http_only: None,
684 same_site: None,
685 expiration_date: None,
686 }
687 }
688
689 #[inline]
691 #[must_use]
692 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
693 self.domain = Some(domain.into());
694 self
695 }
696
697 #[inline]
699 #[must_use]
700 pub fn with_path(mut self, path: impl Into<String>) -> Self {
701 self.path = Some(path.into());
702 self
703 }
704
705 #[inline]
707 #[must_use]
708 pub fn with_secure(mut self, secure: bool) -> Self {
709 self.secure = Some(secure);
710 self
711 }
712
713 #[inline]
715 #[must_use]
716 pub fn with_http_only(mut self, http_only: bool) -> Self {
717 self.http_only = Some(http_only);
718 self
719 }
720
721 #[inline]
723 #[must_use]
724 pub fn with_same_site(mut self, same_site: impl Into<String>) -> Self {
725 self.same_site = Some(same_site.into());
726 self
727 }
728
729 #[inline]
731 #[must_use]
732 pub fn with_expiration_date(mut self, expiration_date: f64) -> Self {
733 self.expiration_date = Some(expiration_date);
734 self
735 }
736}
737
738#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn test_browsing_context_navigate() {
748 let cmd = BrowsingContextCommand::Navigate {
749 url: "https://example.com".to_string(),
750 };
751 let json = serde_json::to_string(&cmd).expect("serialize");
752 assert!(json.contains("browsingContext.navigate"));
753 assert!(json.contains("https://example.com"));
754 }
755
756 #[test]
757 fn test_element_find() {
758 let cmd = ElementCommand::Find {
759 strategy: "css".to_string(),
760 value: "button.submit".to_string(),
761 parent_id: None,
762 };
763 let json = serde_json::to_string(&cmd).expect("serialize");
764 assert!(json.contains("element.find"));
765 assert!(json.contains("button.submit"));
766 }
767
768 #[test]
769 fn test_element_get_property() {
770 let cmd = ElementCommand::GetProperty {
771 element_id: ElementId::new("test-uuid"),
772 name: "textContent".to_string(),
773 };
774 let json = serde_json::to_string(&cmd).expect("serialize");
775 assert!(json.contains("element.getProperty"));
776 assert!(json.contains("test-uuid"));
777 assert!(json.contains("textContent"));
778 }
779
780 #[test]
781 fn test_cookie_builder() {
782 let cookie = Cookie::new("session", "abc123")
783 .with_domain(".example.com")
784 .with_path("/")
785 .with_secure(true)
786 .with_http_only(true)
787 .with_same_site("strict");
788
789 assert_eq!(cookie.name, "session");
790 assert_eq!(cookie.value, "abc123");
791 assert_eq!(cookie.domain, Some(".example.com".to_string()));
792 assert_eq!(cookie.secure, Some(true));
793 }
794
795 #[test]
796 fn test_network_add_intercept() {
797 let cmd = NetworkCommand::AddIntercept {
798 intercept_requests: true,
799 intercept_request_headers: false,
800 intercept_request_body: false,
801 intercept_responses: false,
802 intercept_response_body: false,
803 url_patterns: None,
804 resource_types: None,
805 };
806 let json = serde_json::to_string(&cmd).expect("serialize");
807 assert!(json.contains("network.addIntercept"));
808 assert!(!json.contains("urlPatterns"));
810 assert!(!json.contains("resourceTypes"));
811 }
812
813 #[test]
814 fn test_network_add_intercept_with_filters() {
815 let cmd = NetworkCommand::AddIntercept {
816 intercept_requests: true,
817 intercept_request_headers: false,
818 intercept_request_body: false,
819 intercept_responses: false,
820 intercept_response_body: false,
821 url_patterns: Some(vec!["*api*".to_string(), "*example.com*".to_string()]),
822 resource_types: Some(vec!["xhr".to_string(), "fetch".to_string()]),
823 };
824 let json = serde_json::to_string(&cmd).expect("serialize");
825 assert!(json.contains("network.addIntercept"));
826 assert!(json.contains("urlPatterns"));
827 assert!(json.contains("resourceTypes"));
828 assert!(json.contains("*api*"));
829 assert!(json.contains("xhr"));
830 }
831
832 #[test]
833 fn test_browsing_context_capture_screenshot() {
834 let cmd = BrowsingContextCommand::CaptureScreenshot {
835 format: "png".to_string(),
836 quality: None,
837 };
838 let json = serde_json::to_string(&cmd).expect("serialize");
839 assert!(json.contains("browsingContext.captureScreenshot"));
840 assert!(json.contains("\"format\":\"png\""));
841
842 let cmd_jpeg = BrowsingContextCommand::CaptureScreenshot {
843 format: "jpeg".to_string(),
844 quality: Some(85),
845 };
846 let json_jpeg = serde_json::to_string(&cmd_jpeg).expect("serialize");
847 assert!(json_jpeg.contains("\"quality\":85"));
848 }
849
850 #[test]
851 fn test_element_capture_screenshot() {
852 let cmd = ElementCommand::CaptureScreenshot {
853 element_id: ElementId::new("elem-uuid"),
854 format: "png".to_string(),
855 quality: None,
856 };
857 let json = serde_json::to_string(&cmd).expect("serialize");
858 assert!(json.contains("element.captureScreenshot"));
859 assert!(json.contains("elem-uuid"));
860 assert!(json.contains("\"format\":\"png\""));
861 }
862}