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 },
508
509 #[serde(rename = "network.removeIntercept")]
511 RemoveIntercept {
512 #[serde(rename = "interceptId")]
514 intercept_id: InterceptId,
515 },
516
517 #[serde(rename = "network.setBlockRules")]
519 SetBlockRules {
520 patterns: Vec<String>,
522 },
523
524 #[serde(rename = "network.clearBlockRules")]
526 ClearBlockRules,
527}
528
529#[derive(Debug, Clone, Serialize, Deserialize)]
535#[serde(tag = "method", content = "params")]
536pub enum ProxyCommand {
537 #[serde(rename = "proxy.setWindowProxy")]
539 SetWindowProxy {
540 #[serde(rename = "type")]
542 proxy_type: String,
543 host: String,
545 port: u16,
547 #[serde(skip_serializing_if = "Option::is_none")]
549 username: Option<String>,
550 #[serde(skip_serializing_if = "Option::is_none")]
552 password: Option<String>,
553 #[serde(rename = "proxyDns", default)]
555 proxy_dns: bool,
556 },
557
558 #[serde(rename = "proxy.clearWindowProxy")]
560 ClearWindowProxy,
561
562 #[serde(rename = "proxy.setTabProxy")]
564 SetTabProxy {
565 #[serde(rename = "type")]
567 proxy_type: String,
568 host: String,
570 port: u16,
572 #[serde(skip_serializing_if = "Option::is_none")]
574 username: Option<String>,
575 #[serde(skip_serializing_if = "Option::is_none")]
577 password: Option<String>,
578 #[serde(rename = "proxyDns", default)]
580 proxy_dns: bool,
581 },
582
583 #[serde(rename = "proxy.clearTabProxy")]
585 ClearTabProxy,
586}
587
588#[derive(Debug, Clone, Serialize, Deserialize)]
594#[serde(tag = "method", content = "params")]
595pub enum StorageCommand {
596 #[serde(rename = "storage.getCookie")]
598 GetCookie {
599 name: String,
601 #[serde(skip_serializing_if = "Option::is_none")]
603 url: Option<String>,
604 },
605
606 #[serde(rename = "storage.setCookie")]
608 SetCookie {
609 cookie: Cookie,
611 #[serde(skip_serializing_if = "Option::is_none")]
613 url: Option<String>,
614 },
615
616 #[serde(rename = "storage.deleteCookie")]
618 DeleteCookie {
619 name: String,
621 #[serde(skip_serializing_if = "Option::is_none")]
623 url: Option<String>,
624 },
625
626 #[serde(rename = "storage.getAllCookies")]
628 GetAllCookies {
629 #[serde(skip_serializing_if = "Option::is_none")]
631 url: Option<String>,
632 },
633}
634
635#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct Cookie {
642 pub name: String,
644 pub value: String,
646 #[serde(skip_serializing_if = "Option::is_none")]
648 pub domain: Option<String>,
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub path: Option<String>,
652 #[serde(skip_serializing_if = "Option::is_none")]
654 pub secure: Option<bool>,
655 #[serde(rename = "httpOnly", skip_serializing_if = "Option::is_none")]
657 pub http_only: Option<bool>,
658 #[serde(rename = "sameSite", skip_serializing_if = "Option::is_none")]
660 pub same_site: Option<String>,
661 #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")]
663 pub expiration_date: Option<f64>,
664}
665
666impl Cookie {
667 #[inline]
669 #[must_use]
670 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
671 Self {
672 name: name.into(),
673 value: value.into(),
674 domain: None,
675 path: None,
676 secure: None,
677 http_only: None,
678 same_site: None,
679 expiration_date: None,
680 }
681 }
682
683 #[inline]
685 #[must_use]
686 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
687 self.domain = Some(domain.into());
688 self
689 }
690
691 #[inline]
693 #[must_use]
694 pub fn with_path(mut self, path: impl Into<String>) -> Self {
695 self.path = Some(path.into());
696 self
697 }
698
699 #[inline]
701 #[must_use]
702 pub fn with_secure(mut self, secure: bool) -> Self {
703 self.secure = Some(secure);
704 self
705 }
706
707 #[inline]
709 #[must_use]
710 pub fn with_http_only(mut self, http_only: bool) -> Self {
711 self.http_only = Some(http_only);
712 self
713 }
714
715 #[inline]
717 #[must_use]
718 pub fn with_same_site(mut self, same_site: impl Into<String>) -> Self {
719 self.same_site = Some(same_site.into());
720 self
721 }
722
723 #[inline]
725 #[must_use]
726 pub fn with_expiration_date(mut self, expiration_date: f64) -> Self {
727 self.expiration_date = Some(expiration_date);
728 self
729 }
730}
731
732#[cfg(test)]
737mod tests {
738 use super::*;
739
740 #[test]
741 fn test_browsing_context_navigate() {
742 let cmd = BrowsingContextCommand::Navigate {
743 url: "https://example.com".to_string(),
744 };
745 let json = serde_json::to_string(&cmd).expect("serialize");
746 assert!(json.contains("browsingContext.navigate"));
747 assert!(json.contains("https://example.com"));
748 }
749
750 #[test]
751 fn test_element_find() {
752 let cmd = ElementCommand::Find {
753 strategy: "css".to_string(),
754 value: "button.submit".to_string(),
755 parent_id: None,
756 };
757 let json = serde_json::to_string(&cmd).expect("serialize");
758 assert!(json.contains("element.find"));
759 assert!(json.contains("button.submit"));
760 }
761
762 #[test]
763 fn test_element_get_property() {
764 let cmd = ElementCommand::GetProperty {
765 element_id: ElementId::new("test-uuid"),
766 name: "textContent".to_string(),
767 };
768 let json = serde_json::to_string(&cmd).expect("serialize");
769 assert!(json.contains("element.getProperty"));
770 assert!(json.contains("test-uuid"));
771 assert!(json.contains("textContent"));
772 }
773
774 #[test]
775 fn test_cookie_builder() {
776 let cookie = Cookie::new("session", "abc123")
777 .with_domain(".example.com")
778 .with_path("/")
779 .with_secure(true)
780 .with_http_only(true)
781 .with_same_site("strict");
782
783 assert_eq!(cookie.name, "session");
784 assert_eq!(cookie.value, "abc123");
785 assert_eq!(cookie.domain, Some(".example.com".to_string()));
786 assert_eq!(cookie.secure, Some(true));
787 }
788
789 #[test]
790 fn test_network_add_intercept() {
791 let cmd = NetworkCommand::AddIntercept {
792 intercept_requests: true,
793 intercept_request_headers: false,
794 intercept_request_body: false,
795 intercept_responses: false,
796 intercept_response_body: false,
797 };
798 let json = serde_json::to_string(&cmd).expect("serialize");
799 assert!(json.contains("network.addIntercept"));
800 }
801
802 #[test]
803 fn test_browsing_context_capture_screenshot() {
804 let cmd = BrowsingContextCommand::CaptureScreenshot {
805 format: "png".to_string(),
806 quality: None,
807 };
808 let json = serde_json::to_string(&cmd).expect("serialize");
809 assert!(json.contains("browsingContext.captureScreenshot"));
810 assert!(json.contains("\"format\":\"png\""));
811
812 let cmd_jpeg = BrowsingContextCommand::CaptureScreenshot {
813 format: "jpeg".to_string(),
814 quality: Some(85),
815 };
816 let json_jpeg = serde_json::to_string(&cmd_jpeg).expect("serialize");
817 assert!(json_jpeg.contains("\"quality\":85"));
818 }
819
820 #[test]
821 fn test_element_capture_screenshot() {
822 let cmd = ElementCommand::CaptureScreenshot {
823 element_id: ElementId::new("elem-uuid"),
824 format: "png".to_string(),
825 quality: None,
826 };
827 let json = serde_json::to_string(&cmd).expect("serialize");
828 assert!(json.contains("element.captureScreenshot"));
829 assert!(json.contains("elem-uuid"));
830 assert!(json.contains("\"format\":\"png\""));
831 }
832}