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