1use crate::proto::context_session_command::Command as ContextCommand;
2use crate::proto::context_session_event::Event as ContextEvent;
3use crate::proto::{
4 ClickElementCommand, ContextSessionCommand, FillElementCommand, FocusElementCommand,
5 HoverElementCommand, PressKeyCommand,
6};
7
8use super::command::command_retry_options;
9use super::selectors::normalize_selector_for_transport;
10use super::tab::ensure_tab_open;
11use super::types::{ClickOptions, CommandOptions, Error, PressOptions, Result, Tab};
12
13impl Tab {
14 pub async fn click(&self, css_selector: impl Into<String>) -> Result<()> {
15 self.click_with_options(css_selector, ClickOptions::default())
16 .await
17 }
18
19 pub async fn click_with_options(
20 &self,
21 css_selector: impl Into<String>,
22 options: impl Into<ClickOptions>,
23 ) -> Result<()> {
24 let options = options.into();
25 let css_selector = normalize_selector_for_transport(&css_selector.into());
26 let mut state = self.inner.state.lock().await;
27 let handle = self.ensure_handle(&mut state).await?;
28 ensure_tab_open(handle, &self.inner.session_id)?;
29
30 handle
31 .command_tx
32 .send(ContextSessionCommand {
33 surface_session_id: self.inner.surface_session_id.clone(),
34 context_session_id: self.inner.session_id.clone(),
35 command: Some(ContextCommand::ClickElement(ClickElementCommand {
36 css_selector: css_selector.clone(),
37 retry_options: command_retry_options(options.timeout_ms),
38 button: Some(options.button.as_str().to_string()),
39 click_count: options.click_count,
40 })),
41 })
42 .await
43 .map_err(|_| Error::new("failed to send ClickElementCommand"))?;
44
45 loop {
46 let event =
47 handle.events.message().await?.ok_or_else(|| {
48 Error::new("tab session closed while waiting for click result")
49 })?;
50
51 match event.event {
52 Some(ContextEvent::Attached(_)) => {}
53 Some(ContextEvent::ElementClicked(_)) => return Ok(()),
54 Some(ContextEvent::Error(error)) => {
55 return Err(Error::new(format!(
56 "tab session error while clicking locator {:?}: {}",
57 css_selector, error.message,
58 )));
59 }
60 Some(ContextEvent::Closed(_)) => {
61 handle.closed = true;
62 return Err(Error::new(format!(
63 "tab session {} closed while waiting for click result",
64 self.inner.session_id
65 )));
66 }
67 _ => {}
68 }
69 }
70 }
71
72 pub async fn dblclick(&self, css_selector: impl Into<String>) -> Result<()> {
73 self.dblclick_with_options(css_selector, ClickOptions::default())
74 .await
75 }
76
77 pub async fn dblclick_with_options(
78 &self,
79 css_selector: impl Into<String>,
80 mut options: ClickOptions,
81 ) -> Result<()> {
82 options.click_count = Some(2);
83 self.click_with_options(css_selector, options).await
84 }
85
86 pub async fn focus(&self, css_selector: impl Into<String>) -> Result<()> {
87 self.focus_with_options(css_selector, CommandOptions::default())
88 .await
89 }
90
91 pub async fn focus_with_options(
92 &self,
93 css_selector: impl Into<String>,
94 options: CommandOptions,
95 ) -> Result<()> {
96 let css_selector = normalize_selector_for_transport(&css_selector.into());
97 let mut state = self.inner.state.lock().await;
98 let handle = self.ensure_handle(&mut state).await?;
99 ensure_tab_open(handle, &self.inner.session_id)?;
100
101 handle
102 .command_tx
103 .send(ContextSessionCommand {
104 surface_session_id: self.inner.surface_session_id.clone(),
105 context_session_id: self.inner.session_id.clone(),
106 command: Some(ContextCommand::FocusElement(FocusElementCommand {
107 css_selector: css_selector.clone(),
108 retry_options: command_retry_options(options.timeout_ms),
109 })),
110 })
111 .await
112 .map_err(|_| Error::new("failed to send FocusElementCommand"))?;
113
114 loop {
115 let event =
116 handle.events.message().await?.ok_or_else(|| {
117 Error::new("tab session closed while waiting for focus result")
118 })?;
119 match event.event {
120 Some(ContextEvent::Attached(_)) => {}
121 Some(ContextEvent::ElementFocused(_)) => return Ok(()),
122 Some(ContextEvent::Error(error)) => {
123 return Err(Error::new(format!(
124 "tab session error while focusing locator {:?}: {}",
125 css_selector, error.message,
126 )));
127 }
128 Some(ContextEvent::Closed(_)) => {
129 handle.closed = true;
130 return Err(Error::new(format!(
131 "tab session {} closed while waiting for focus result",
132 self.inner.session_id
133 )));
134 }
135 _ => {}
136 }
137 }
138 }
139
140 pub async fn fill(
141 &self,
142 css_selector: impl Into<String>,
143 value: impl Into<String>,
144 ) -> Result<()> {
145 self.fill_with_options(css_selector, value, CommandOptions::default())
146 .await
147 }
148
149 pub async fn fill_with_options(
150 &self,
151 css_selector: impl Into<String>,
152 value: impl Into<String>,
153 options: CommandOptions,
154 ) -> Result<()> {
155 let css_selector = normalize_selector_for_transport(&css_selector.into());
156 let mut state = self.inner.state.lock().await;
157 let handle = self.ensure_handle(&mut state).await?;
158 ensure_tab_open(handle, &self.inner.session_id)?;
159
160 handle
161 .command_tx
162 .send(ContextSessionCommand {
163 surface_session_id: self.inner.surface_session_id.clone(),
164 context_session_id: self.inner.session_id.clone(),
165 command: Some(ContextCommand::FillElement(FillElementCommand {
166 css_selector: css_selector.clone(),
167 value: value.into(),
168 retry_options: command_retry_options(options.timeout_ms),
169 })),
170 })
171 .await
172 .map_err(|_| Error::new("failed to send FillElementCommand"))?;
173 loop {
174 let event =
175 handle.events.message().await?.ok_or_else(|| {
176 Error::new("tab session closed while waiting for fill result")
177 })?;
178 match event.event {
179 Some(ContextEvent::Attached(_)) => {}
180 Some(ContextEvent::ElementFilled(_)) => return Ok(()),
181 Some(ContextEvent::Error(error)) => {
182 return Err(Error::new(format!(
183 "tab session error while filling locator {:?}: {}",
184 css_selector, error.message,
185 )));
186 }
187 Some(ContextEvent::Closed(_)) => {
188 handle.closed = true;
189 return Err(Error::new(format!(
190 "tab session {} closed while waiting for fill result",
191 self.inner.session_id
192 )));
193 }
194 _ => {}
195 }
196 }
197 }
198
199 pub async fn hover(&self, css_selector: impl Into<String>) -> Result<()> {
200 self.hover_with_options(css_selector, CommandOptions::default())
201 .await
202 }
203
204 pub async fn hover_with_options(
205 &self,
206 css_selector: impl Into<String>,
207 options: CommandOptions,
208 ) -> Result<()> {
209 let css_selector = normalize_selector_for_transport(&css_selector.into());
210 let mut state = self.inner.state.lock().await;
211 let handle = self.ensure_handle(&mut state).await?;
212 ensure_tab_open(handle, &self.inner.session_id)?;
213 handle
214 .command_tx
215 .send(ContextSessionCommand {
216 surface_session_id: self.inner.surface_session_id.clone(),
217 context_session_id: self.inner.session_id.clone(),
218 command: Some(ContextCommand::HoverElement(HoverElementCommand {
219 css_selector: css_selector.clone(),
220 retry_options: command_retry_options(options.timeout_ms),
221 })),
222 })
223 .await
224 .map_err(|_| Error::new("failed to send HoverElementCommand"))?;
225 loop {
226 let event =
227 handle.events.message().await?.ok_or_else(|| {
228 Error::new("tab session closed while waiting for hover result")
229 })?;
230 match event.event {
231 Some(ContextEvent::Attached(_)) => {}
232 Some(ContextEvent::ElementHovered(_)) => return Ok(()),
233 Some(ContextEvent::Error(error)) => {
234 return Err(Error::new(format!(
235 "tab session error while hovering locator {:?}: {}",
236 css_selector, error.message,
237 )));
238 }
239 Some(ContextEvent::Closed(_)) => {
240 handle.closed = true;
241 return Err(Error::new(format!(
242 "tab session {} closed while waiting for hover result",
243 self.inner.session_id
244 )));
245 }
246 _ => {}
247 }
248 }
249 }
250
251 pub async fn press(
252 &self,
253 css_selector: impl Into<String>,
254 key: impl Into<String>,
255 ) -> Result<()> {
256 self.press_with_options(css_selector, key, PressOptions::default())
257 .await
258 }
259
260 pub async fn press_with_options(
261 &self,
262 css_selector: impl Into<String>,
263 key: impl Into<String>,
264 options: PressOptions,
265 ) -> Result<()> {
266 let css_selector = normalize_selector_for_transport(&css_selector.into());
267 let mut state = self.inner.state.lock().await;
268 let handle = self.ensure_handle(&mut state).await?;
269 ensure_tab_open(handle, &self.inner.session_id)?;
270 handle
271 .command_tx
272 .send(ContextSessionCommand {
273 surface_session_id: self.inner.surface_session_id.clone(),
274 context_session_id: self.inner.session_id.clone(),
275 command: Some(ContextCommand::PressKey(PressKeyCommand {
276 css_selector,
277 key: key.into(),
278 text: options.text,
279 retry_options: command_retry_options(options.timeout_ms),
280 })),
281 })
282 .await
283 .map_err(|_| Error::new("failed to send PressKeyCommand"))?;
284 loop {
285 let event =
286 handle.events.message().await?.ok_or_else(|| {
287 Error::new("tab session closed while waiting for press result")
288 })?;
289 match event.event {
290 Some(ContextEvent::Attached(_)) => {}
291 Some(ContextEvent::KeyPressed(_)) => return Ok(()),
292 Some(ContextEvent::Error(error)) => {
293 return Err(Error::new(format!(
294 "tab session error while pressing key: {}",
295 error.message
296 )));
297 }
298 Some(ContextEvent::Closed(_)) => {
299 handle.closed = true;
300 return Err(Error::new(format!(
301 "tab session {} closed while waiting for press result",
302 self.inner.session_id
303 )));
304 }
305 _ => {}
306 }
307 }
308 }
309}