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