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::{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}