Skip to main content

allwright/
client_tab_actions.rs

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