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