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,
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: {}",
63                        error.message
64                    )));
65                }
66                Some(TabEvent::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()).await
80    }
81
82    pub async fn focus_with_options(
83        &self,
84        css_selector: impl Into<String>,
85        options: CommandOptions,
86    ) -> Result<ElementResult> {
87        let css_selector = normalize_selector_for_transport(&css_selector.into());
88        let mut state = self.inner.state.lock().await;
89        let handle = self.ensure_handle(&mut state).await?;
90        ensure_tab_open(handle, &self.inner.session_id)?;
91
92        handle
93            .command_tx
94            .send(TabSessionCommand {
95                browser_session_id: self.inner.browser_session_id.clone(),
96                tab_session_id: self.inner.session_id.clone(),
97                command: Some(TabCommand::FocusElement(FocusElementCommand {
98                    css_selector,
99                    retry_options: command_retry_options(options.timeout_ms),
100                })),
101            })
102            .await
103            .map_err(|_| Error::new("failed to send FocusElementCommand"))?;
104
105        loop {
106            let event = handle
107                .events
108                .message()
109                .await?
110                .ok_or_else(|| Error::new("tab session closed while waiting for focus result"))?;
111            match event.event {
112                Some(TabEvent::Attached(_)) => {}
113                Some(TabEvent::ElementFocused(focused)) => {
114                    return Ok(ElementResult {
115                        selector: focused.css_selector,
116                        note: focused.note,
117                    });
118                }
119                Some(TabEvent::Error(error)) => {
120                    return Err(Error::new(format!(
121                        "tab session error while focusing: {}",
122                        error.message
123                    )));
124                }
125                Some(TabEvent::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(TabSessionCommand {
160                browser_session_id: self.inner.browser_session_id.clone(),
161                tab_session_id: self.inner.session_id.clone(),
162                command: Some(TabCommand::FillElement(FillElementCommand {
163                    css_selector,
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 = handle
172                .events
173                .message()
174                .await?
175                .ok_or_else(|| Error::new("tab session closed while waiting for fill result"))?;
176            match event.event {
177                Some(TabEvent::Attached(_)) => {}
178                Some(TabEvent::ElementFilled(filled)) => {
179                    return Ok(FillResult {
180                        selector: filled.css_selector,
181                        value: filled.value,
182                        note: filled.note,
183                    });
184                }
185                Some(TabEvent::Error(error)) => {
186                    return Err(Error::new(format!(
187                        "tab session error while filling: {}",
188                        error.message
189                    )));
190                }
191                Some(TabEvent::Closed(_)) => {
192                    handle.closed = true;
193                    return Err(Error::new(format!(
194                        "tab session {} closed while waiting for fill result",
195                        self.inner.session_id
196                    )));
197                }
198                _ => {}
199            }
200        }
201    }
202
203    pub async fn hover(&self, css_selector: impl Into<String>) -> Result<ElementResult> {
204        self.hover_with_options(css_selector, CommandOptions::default()).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(TabSessionCommand {
219                browser_session_id: self.inner.browser_session_id.clone(),
220                tab_session_id: self.inner.session_id.clone(),
221                command: Some(TabCommand::HoverElement(HoverElementCommand {
222                    css_selector,
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 = handle
230                .events
231                .message()
232                .await?
233                .ok_or_else(|| Error::new("tab session closed while waiting for hover result"))?;
234            match event.event {
235                Some(TabEvent::Attached(_)) => {}
236                Some(TabEvent::ElementHovered(hovered)) => {
237                    return Ok(ElementResult {
238                        selector: hovered.css_selector,
239                        note: hovered.note,
240                    });
241                }
242                Some(TabEvent::Error(error)) => {
243                    return Err(Error::new(format!(
244                        "tab session error while hovering: {}",
245                        error.message
246                    )));
247                }
248                Some(TabEvent::Closed(_)) => {
249                    handle.closed = true;
250                    return Err(Error::new(format!(
251                        "tab session {} closed while waiting for hover result",
252                        self.inner.session_id
253                    )));
254                }
255                _ => {}
256            }
257        }
258    }
259
260    pub async fn press(
261        &self,
262        css_selector: impl Into<String>,
263        key: impl Into<String>,
264    ) -> Result<PressResult> {
265        self.press_with_options(css_selector, key, PressOptions::default())
266            .await
267    }
268
269    pub async fn press_with_options(
270        &self,
271        css_selector: impl Into<String>,
272        key: impl Into<String>,
273        options: PressOptions,
274    ) -> Result<PressResult> {
275        let css_selector = normalize_selector_for_transport(&css_selector.into());
276        let mut state = self.inner.state.lock().await;
277        let handle = self.ensure_handle(&mut state).await?;
278        ensure_tab_open(handle, &self.inner.session_id)?;
279        handle
280            .command_tx
281            .send(TabSessionCommand {
282                browser_session_id: self.inner.browser_session_id.clone(),
283                tab_session_id: self.inner.session_id.clone(),
284                command: Some(TabCommand::PressKey(PressKeyCommand {
285                    css_selector,
286                    key: key.into(),
287                    text: options.text,
288                    retry_options: command_retry_options(options.timeout_ms),
289                })),
290            })
291            .await
292            .map_err(|_| Error::new("failed to send PressKeyCommand"))?;
293        loop {
294            let event = handle
295                .events
296                .message()
297                .await?
298                .ok_or_else(|| Error::new("tab session closed while waiting for press result"))?;
299            match event.event {
300                Some(TabEvent::Attached(_)) => {}
301                Some(TabEvent::KeyPressed(pressed)) => {
302                    return Ok(PressResult {
303                        selector: pressed.css_selector,
304                        key: pressed.key,
305                        note: pressed.note,
306                    });
307                }
308                Some(TabEvent::Error(error)) => {
309                    return Err(Error::new(format!(
310                        "tab session error while pressing key: {}",
311                        error.message
312                    )));
313                }
314                Some(TabEvent::Closed(_)) => {
315                    handle.closed = true;
316                    return Err(Error::new(format!(
317                        "tab session {} closed while waiting for press result",
318                        self.inner.session_id
319                    )));
320                }
321                _ => {}
322            }
323        }
324    }
325}