1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::time::Duration;
4
5use crate::{CommandPoint, FileType};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct BrowserScreenshot {
9 pub filename: String,
10 pub data: String,
11 pub format: String,
12 pub size: usize,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ElementBox {
17 pub x: f64,
18 pub y: f64,
19 pub width: f64,
20 pub height: f64,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct BoundingBoxElement {
25 pub selector: String,
26 pub index: usize,
27 pub tag: String,
28 pub text: Option<String>,
29 pub attributes: serde_json::Value,
30 pub bounding_box: ElementBox,
31 pub center: CommandPoint,
32 pub visibility: bool,
33 pub clickable: bool,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub html: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct WaitForElementCheck {
40 pub found: bool,
41 pub visible: bool,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ClickTarget {
46 pub target_id: String,
47 pub tag: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub text: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub attributes: Option<serde_json::Map<String, Value>>,
52 pub bounding_box: ElementBox,
53 pub center: CommandPoint,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct DomElementDescriptor {
58 pub index: usize,
59 pub tag: String,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub selector: Option<String>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub role: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub name: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub text: Option<String>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub attributes: Option<serde_json::Map<String, Value>>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub bounding_box: Option<ElementBox>,
72 pub in_viewport: bool,
73 pub clickable: bool,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct DomSnapshot {
78 pub url: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub title: Option<String>,
81 pub viewport: (u32, u32),
82 pub captured_at: i64,
83 pub interactive: Vec<DomElementDescriptor>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub interactive_text: Option<String>,
86}
87
88#[derive(Debug, Clone)]
89pub struct ObservationOptions {
90 pub full_page: Option<bool>,
91 pub with_overlay: bool,
92 pub use_image: bool,
93 pub include_content: bool,
94 pub max_elements: usize,
95 pub wait_for_load: bool,
96 pub wait_timeout: Duration,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ObserveResponse {
101 #[serde(flatten)]
102 pub dom_snapshot: DomSnapshot,
103 pub state_text: String,
104 pub screenshot: Option<FileType>,
105 pub click_targets: Vec<ClickTarget>,
106}
107
108impl ObservationOptions {
109 pub fn for_command(full_page: Option<bool>, with_overlay: bool) -> Self {
110 Self {
111 full_page,
112 with_overlay,
113 use_image: true,
114 include_content: false,
115 max_elements: 140,
116 wait_for_load: true,
117 wait_timeout: Duration::from_millis(1500),
118 }
119 }
120
121 pub fn lightweight(full_page: Option<bool>, with_overlay: bool, use_image: bool) -> Self {
122 Self {
123 use_image,
124 include_content: false,
125 ..Self::for_command(full_page, with_overlay)
126 }
127 }
128}