browsr_types/
observe.rs

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}