Skip to main content

allwright/
client_tab_actions.rs

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