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
142#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(tag = "method", content = "params")]
149pub enum ElementCommand {
150 #[serde(rename = "element.find")]
152 Find {
153 selector: String,
155 #[serde(rename = "parentId", skip_serializing_if = "Option::is_none")]
157 parent_id: Option<ElementId>,
158 },
159
160 #[serde(rename = "element.findAll")]
162 FindAll {
163 selector: String,
165 #[serde(rename = "parentId", skip_serializing_if = "Option::is_none")]
167 parent_id: Option<ElementId>,
168 },
169
170 #[serde(rename = "element.getProperty")]
172 GetProperty {
173 #[serde(rename = "elementId")]
175 element_id: ElementId,
176 name: String,
178 },
179
180 #[serde(rename = "element.setProperty")]
182 SetProperty {
183 #[serde(rename = "elementId")]
185 element_id: ElementId,
186 name: String,
188 value: Value,
190 },
191
192 #[serde(rename = "element.callMethod")]
194 CallMethod {
195 #[serde(rename = "elementId")]
197 element_id: ElementId,
198 name: String,
200 #[serde(default)]
202 args: Vec<Value>,
203 },
204
205 #[serde(rename = "element.subscribe")]
207 Subscribe {
208 selector: String,
210 #[serde(rename = "oneShot")]
212 one_shot: bool,
213 },
214
215 #[serde(rename = "element.unsubscribe")]
217 Unsubscribe {
218 #[serde(rename = "subscriptionId")]
220 subscription_id: String,
221 },
222
223 #[serde(rename = "element.watchRemoval")]
225 WatchRemoval {
226 #[serde(rename = "elementId")]
228 element_id: ElementId,
229 },
230
231 #[serde(rename = "element.unwatchRemoval")]
233 UnwatchRemoval {
234 #[serde(rename = "elementId")]
236 element_id: ElementId,
237 },
238
239 #[serde(rename = "element.watchAttribute")]
241 WatchAttribute {
242 #[serde(rename = "elementId")]
244 element_id: ElementId,
245 #[serde(rename = "attributeName", skip_serializing_if = "Option::is_none")]
247 attribute_name: Option<String>,
248 },
249
250 #[serde(rename = "element.unwatchAttribute")]
252 UnwatchAttribute {
253 #[serde(rename = "elementId")]
255 element_id: ElementId,
256 },
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(tag = "method", content = "params")]
266pub enum SessionCommand {
267 #[serde(rename = "session.status")]
269 Status,
270
271 #[serde(rename = "session.stealLogs")]
273 StealLogs,
274
275 #[serde(rename = "session.subscribe")]
277 Subscribe {
278 events: Vec<String>,
280 #[serde(skip_serializing_if = "Option::is_none")]
282 selectors: Option<Vec<String>>,
283 },
284
285 #[serde(rename = "session.unsubscribe")]
287 Unsubscribe {
288 subscription_id: String,
290 },
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
299#[serde(tag = "method", content = "params")]
300pub enum ScriptCommand {
301 #[serde(rename = "script.evaluate")]
303 Evaluate {
304 script: String,
306 #[serde(default)]
308 args: Vec<Value>,
309 },
310
311 #[serde(rename = "script.evaluateAsync")]
313 EvaluateAsync {
314 script: String,
316 #[serde(default)]
318 args: Vec<Value>,
319 },
320
321 #[serde(rename = "script.addPreloadScript")]
323 AddPreloadScript {
324 script: String,
326 },
327
328 #[serde(rename = "script.removePreloadScript")]
330 RemovePreloadScript {
331 script_id: String,
333 },
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
342#[serde(tag = "method", content = "params")]
343pub enum InputCommand {
344 #[serde(rename = "input.typeKey")]
346 TypeKey {
347 #[serde(rename = "elementId")]
349 element_id: ElementId,
350 key: String,
352 code: String,
354 #[serde(rename = "keyCode")]
356 key_code: u32,
357 printable: bool,
359 #[serde(default)]
361 ctrl: bool,
362 #[serde(default)]
364 shift: bool,
365 #[serde(default)]
367 alt: bool,
368 #[serde(default)]
370 meta: bool,
371 },
372
373 #[serde(rename = "input.typeText")]
375 TypeText {
376 #[serde(rename = "elementId")]
378 element_id: ElementId,
379 text: String,
381 },
382
383 #[serde(rename = "input.mouseClick")]
385 MouseClick {
386 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
388 element_id: Option<ElementId>,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 x: Option<i32>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 y: Option<i32>,
395 #[serde(default)]
397 button: u8,
398 },
399
400 #[serde(rename = "input.mouseMove")]
402 MouseMove {
403 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
405 element_id: Option<ElementId>,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 x: Option<i32>,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 y: Option<i32>,
412 },
413
414 #[serde(rename = "input.mouseDown")]
416 MouseDown {
417 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
419 element_id: Option<ElementId>,
420 #[serde(skip_serializing_if = "Option::is_none")]
422 x: Option<i32>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 y: Option<i32>,
426 #[serde(default)]
428 button: u8,
429 },
430
431 #[serde(rename = "input.mouseUp")]
433 MouseUp {
434 #[serde(rename = "elementId", skip_serializing_if = "Option::is_none")]
436 element_id: Option<ElementId>,
437 #[serde(skip_serializing_if = "Option::is_none")]
439 x: Option<i32>,
440 #[serde(skip_serializing_if = "Option::is_none")]
442 y: Option<i32>,
443 #[serde(default)]
445 button: u8,
446 },
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
455#[serde(tag = "method", content = "params")]
456pub enum NetworkCommand {
457 #[serde(rename = "network.addIntercept")]
459 AddIntercept {
460 #[serde(default, rename = "interceptRequests")]
462 intercept_requests: bool,
463 #[serde(default, rename = "interceptRequestHeaders")]
465 intercept_request_headers: bool,
466 #[serde(default, rename = "interceptRequestBody")]
468 intercept_request_body: bool,
469 #[serde(default, rename = "interceptResponses")]
471 intercept_responses: bool,
472 #[serde(default, rename = "interceptResponseBody")]
474 intercept_response_body: bool,
475 },
476
477 #[serde(rename = "network.removeIntercept")]
479 RemoveIntercept {
480 #[serde(rename = "interceptId")]
482 intercept_id: InterceptId,
483 },
484
485 #[serde(rename = "network.setBlockRules")]
487 SetBlockRules {
488 patterns: Vec<String>,
490 },
491
492 #[serde(rename = "network.clearBlockRules")]
494 ClearBlockRules,
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize)]
503#[serde(tag = "method", content = "params")]
504pub enum ProxyCommand {
505 #[serde(rename = "proxy.setWindowProxy")]
507 SetWindowProxy {
508 #[serde(rename = "type")]
510 proxy_type: String,
511 host: String,
513 port: u16,
515 #[serde(skip_serializing_if = "Option::is_none")]
517 username: Option<String>,
518 #[serde(skip_serializing_if = "Option::is_none")]
520 password: Option<String>,
521 #[serde(rename = "proxyDns", default)]
523 proxy_dns: bool,
524 },
525
526 #[serde(rename = "proxy.clearWindowProxy")]
528 ClearWindowProxy,
529
530 #[serde(rename = "proxy.setTabProxy")]
532 SetTabProxy {
533 #[serde(rename = "type")]
535 proxy_type: String,
536 host: String,
538 port: u16,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 username: Option<String>,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 password: Option<String>,
546 #[serde(rename = "proxyDns", default)]
548 proxy_dns: bool,
549 },
550
551 #[serde(rename = "proxy.clearTabProxy")]
553 ClearTabProxy,
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
562#[serde(tag = "method", content = "params")]
563pub enum StorageCommand {
564 #[serde(rename = "storage.getCookie")]
566 GetCookie {
567 name: String,
569 #[serde(skip_serializing_if = "Option::is_none")]
571 url: Option<String>,
572 },
573
574 #[serde(rename = "storage.setCookie")]
576 SetCookie {
577 cookie: Cookie,
579 #[serde(skip_serializing_if = "Option::is_none")]
581 url: Option<String>,
582 },
583
584 #[serde(rename = "storage.deleteCookie")]
586 DeleteCookie {
587 name: String,
589 #[serde(skip_serializing_if = "Option::is_none")]
591 url: Option<String>,
592 },
593
594 #[serde(rename = "storage.getAllCookies")]
596 GetAllCookies {
597 #[serde(skip_serializing_if = "Option::is_none")]
599 url: Option<String>,
600 },
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
609pub struct Cookie {
610 pub name: String,
612 pub value: String,
614 #[serde(skip_serializing_if = "Option::is_none")]
616 pub domain: Option<String>,
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub path: Option<String>,
620 #[serde(skip_serializing_if = "Option::is_none")]
622 pub secure: Option<bool>,
623 #[serde(rename = "httpOnly", skip_serializing_if = "Option::is_none")]
625 pub http_only: Option<bool>,
626 #[serde(rename = "sameSite", skip_serializing_if = "Option::is_none")]
628 pub same_site: Option<String>,
629 #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")]
631 pub expiration_date: Option<f64>,
632}
633
634impl Cookie {
635 #[inline]
637 #[must_use]
638 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
639 Self {
640 name: name.into(),
641 value: value.into(),
642 domain: None,
643 path: None,
644 secure: None,
645 http_only: None,
646 same_site: None,
647 expiration_date: None,
648 }
649 }
650
651 #[inline]
653 #[must_use]
654 pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
655 self.domain = Some(domain.into());
656 self
657 }
658
659 #[inline]
661 #[must_use]
662 pub fn with_path(mut self, path: impl Into<String>) -> Self {
663 self.path = Some(path.into());
664 self
665 }
666
667 #[inline]
669 #[must_use]
670 pub fn with_secure(mut self, secure: bool) -> Self {
671 self.secure = Some(secure);
672 self
673 }
674
675 #[inline]
677 #[must_use]
678 pub fn with_http_only(mut self, http_only: bool) -> Self {
679 self.http_only = Some(http_only);
680 self
681 }
682
683 #[inline]
685 #[must_use]
686 pub fn with_same_site(mut self, same_site: impl Into<String>) -> Self {
687 self.same_site = Some(same_site.into());
688 self
689 }
690
691 #[inline]
693 #[must_use]
694 pub fn with_expiration_date(mut self, expiration_date: f64) -> Self {
695 self.expiration_date = Some(expiration_date);
696 self
697 }
698}
699
700#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_browsing_context_navigate() {
710 let cmd = BrowsingContextCommand::Navigate {
711 url: "https://example.com".to_string(),
712 };
713 let json = serde_json::to_string(&cmd).expect("serialize");
714 assert!(json.contains("browsingContext.navigate"));
715 assert!(json.contains("https://example.com"));
716 }
717
718 #[test]
719 fn test_element_find() {
720 let cmd = ElementCommand::Find {
721 selector: "button.submit".to_string(),
722 parent_id: None,
723 };
724 let json = serde_json::to_string(&cmd).expect("serialize");
725 assert!(json.contains("element.find"));
726 assert!(json.contains("button.submit"));
727 }
728
729 #[test]
730 fn test_element_get_property() {
731 let cmd = ElementCommand::GetProperty {
732 element_id: ElementId::new("test-uuid"),
733 name: "textContent".to_string(),
734 };
735 let json = serde_json::to_string(&cmd).expect("serialize");
736 assert!(json.contains("element.getProperty"));
737 assert!(json.contains("test-uuid"));
738 assert!(json.contains("textContent"));
739 }
740
741 #[test]
742 fn test_cookie_builder() {
743 let cookie = Cookie::new("session", "abc123")
744 .with_domain(".example.com")
745 .with_path("/")
746 .with_secure(true)
747 .with_http_only(true)
748 .with_same_site("strict");
749
750 assert_eq!(cookie.name, "session");
751 assert_eq!(cookie.value, "abc123");
752 assert_eq!(cookie.domain, Some(".example.com".to_string()));
753 assert_eq!(cookie.secure, Some(true));
754 }
755
756 #[test]
757 fn test_network_add_intercept() {
758 let cmd = NetworkCommand::AddIntercept {
759 intercept_requests: true,
760 intercept_request_headers: false,
761 intercept_request_body: false,
762 intercept_responses: false,
763 intercept_response_body: false,
764 };
765 let json = serde_json::to_string(&cmd).expect("serialize");
766 assert!(json.contains("network.addIntercept"));
767 }
768}