viewpoint_core/page/locator/queries/
mod.rs

1//! Query methods for locators.
2//!
3//! These methods query element state and properties without performing actions.
4
5use viewpoint_js::js;
6
7use super::Locator;
8use crate::error::LocatorError;
9
10impl<'a> Locator<'a> {
11    /// Get the text content of the first matching element.
12    ///
13    /// # Errors
14    ///
15    /// Returns an error if the element cannot be queried.
16    pub async fn text_content(&self) -> Result<Option<String>, LocatorError> {
17        let info = self.query_element_info().await?;
18        Ok(info.text)
19    }
20
21    /// Check if the element is visible.
22    ///
23    /// # Errors
24    ///
25    /// Returns an error if the element cannot be queried.
26    pub async fn is_visible(&self) -> Result<bool, LocatorError> {
27        let info = self.query_element_info().await?;
28        Ok(info.visible.unwrap_or(false))
29    }
30
31    /// Check if the element is checked (for checkboxes/radios).
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if the element cannot be queried.
36    pub async fn is_checked(&self) -> Result<bool, LocatorError> {
37        let selector_expr = self.selector.to_js_expression();
38        let js_code = js! {
39            (function() {
40                const elements = @{selector_expr};
41                if (elements.length === 0) return { found: false, checked: false };
42                const el = elements[0];
43                return { found: true, checked: el.checked || false };
44            })()
45        };
46
47        let result = self.evaluate_js(&js_code).await?;
48        let checked: bool = result
49            .get("checked")
50            .and_then(serde_json::Value::as_bool)
51            .unwrap_or(false);
52        Ok(checked)
53    }
54
55    /// Count matching elements.
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if the elements cannot be queried.
60    pub async fn count(&self) -> Result<usize, LocatorError> {
61        let info = self.query_element_info().await?;
62        Ok(info.count)
63    }
64
65    /// Return all matching elements as individual locators.
66    ///
67    /// Each returned locator points to a single element (via nth index).
68    ///
69    /// # Example
70    ///
71    /// ```no_run
72    /// use viewpoint_core::Page;
73    ///
74    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
75    /// let items = page.locator("li").all().await?;
76    /// for item in items {
77    ///     println!("{}", item.text_content().await?.unwrap_or_default());
78    /// }
79    /// # Ok(())
80    /// # }
81    /// ```
82    ///
83    /// # Errors
84    ///
85    /// Returns an error if the elements cannot be queried.
86    pub async fn all(&self) -> Result<Vec<Locator<'a>>, LocatorError> {
87        let count = self.count().await?;
88        let mut locators = Vec::with_capacity(count);
89        for i in 0..count {
90            locators.push(self.nth(i as i32));
91        }
92        Ok(locators)
93    }
94
95    /// Get the inner text of all matching elements.
96    ///
97    /// Returns the `innerText` property for each element, which is the rendered
98    /// text content as it appears on screen (respects CSS styling like `display: none`).
99    ///
100    /// # Example
101    ///
102    /// ```no_run
103    /// use viewpoint_core::Page;
104    ///
105    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
106    /// let texts = page.locator("li").all_inner_texts().await?;
107    /// assert_eq!(texts, vec!["Item 1", "Item 2", "Item 3"]);
108    /// # Ok(())
109    /// # }
110    /// ```
111    ///
112    /// # Errors
113    ///
114    /// Returns an error if the elements cannot be queried.
115    pub async fn all_inner_texts(&self) -> Result<Vec<String>, LocatorError> {
116        let selector_expr = self.selector.to_js_expression();
117        let js_code = js! {
118            (function() {
119                const elements = Array.from(@{selector_expr});
120                return elements.map(el => el.innerText || "");
121            })()
122        };
123
124        let result = self.evaluate_js(&js_code).await?;
125
126        result
127            .as_array()
128            .map(|arr| {
129                arr.iter()
130                    .map(|v| v.as_str().unwrap_or("").to_string())
131                    .collect()
132            })
133            .ok_or_else(|| LocatorError::EvaluationError("Expected array result".to_string()))
134    }
135
136    /// Get the text content of all matching elements.
137    ///
138    /// Returns the `textContent` property for each element, which includes all
139    /// text including hidden elements.
140    ///
141    /// # Example
142    ///
143    /// ```no_run
144    /// use viewpoint_core::Page;
145    ///
146    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
147    /// let texts = page.locator("li").all_text_contents().await?;
148    /// assert_eq!(texts, vec!["Item 1", "Item 2", "Item 3"]);
149    /// # Ok(())
150    /// # }
151    /// ```
152    ///
153    /// # Errors
154    ///
155    /// Returns an error if the elements cannot be queried.
156    pub async fn all_text_contents(&self) -> Result<Vec<String>, LocatorError> {
157        let selector_expr = self.selector.to_js_expression();
158        let js_code = js! {
159            (function() {
160                const elements = Array.from(@{selector_expr});
161                return elements.map(el => el.textContent || "");
162            })()
163        };
164
165        let result = self.evaluate_js(&js_code).await?;
166
167        result
168            .as_array()
169            .map(|arr| {
170                arr.iter()
171                    .map(|v| v.as_str().unwrap_or("").to_string())
172                    .collect()
173            })
174            .ok_or_else(|| LocatorError::EvaluationError("Expected array result".to_string()))
175    }
176
177    /// Get the inner text of the first matching element.
178    ///
179    /// Returns the `innerText` property, which is the rendered text content.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if the element cannot be queried.
184    pub async fn inner_text(&self) -> Result<String, LocatorError> {
185        let selector_expr = self.selector.to_js_expression();
186        let js_code = js! {
187            (function() {
188                const elements = @{selector_expr};
189                if (elements.length === 0) return { found: false };
190                return { found: true, text: elements[0].innerText || "" };
191            })()
192        };
193
194        let result = self.evaluate_js(&js_code).await?;
195
196        let found = result
197            .get("found")
198            .and_then(serde_json::Value::as_bool)
199            .unwrap_or(false);
200        if !found {
201            return Err(LocatorError::NotFound(format!("{:?}", self.selector)));
202        }
203
204        Ok(result
205            .get("text")
206            .and_then(|v| v.as_str())
207            .unwrap_or("")
208            .to_string())
209    }
210
211    /// Get an attribute value from the first matching element.
212    ///
213    /// # Errors
214    ///
215    /// Returns an error if the element cannot be queried.
216    pub async fn get_attribute(&self, name: &str) -> Result<Option<String>, LocatorError> {
217        let selector_expr = self.selector.to_js_expression();
218        let js_code = js! {
219            (function() {
220                const elements = @{selector_expr};
221                if (elements.length === 0) return { found: false };
222                const attr = elements[0].getAttribute(#{name});
223                return { found: true, value: attr };
224            })()
225        };
226
227        let result = self.evaluate_js(&js_code).await?;
228
229        let found = result
230            .get("found")
231            .and_then(serde_json::Value::as_bool)
232            .unwrap_or(false);
233        if !found {
234            return Err(LocatorError::NotFound(format!("{:?}", self.selector)));
235        }
236
237        Ok(result
238            .get("value")
239            .and_then(|v| if v.is_null() { None } else { v.as_str() })
240            .map(std::string::ToString::to_string))
241    }
242
243    /// Get the input value of a form element.
244    ///
245    /// Works for `<input>`, `<textarea>`, and `<select>` elements.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error if the element cannot be queried.
250    pub async fn input_value(&self) -> Result<String, LocatorError> {
251        let selector_expr = self.selector.to_js_expression();
252        let js_code = js! {
253            (function() {
254                const elements = @{selector_expr};
255                if (elements.length === 0) return { found: false };
256                const el = elements[0];
257                return { found: true, value: el.value || "" };
258            })()
259        };
260
261        let result = self.evaluate_js(&js_code).await?;
262
263        let found = result
264            .get("found")
265            .and_then(serde_json::Value::as_bool)
266            .unwrap_or(false);
267        if !found {
268            return Err(LocatorError::NotFound(format!("{:?}", self.selector)));
269        }
270
271        Ok(result
272            .get("value")
273            .and_then(|v| v.as_str())
274            .unwrap_or("")
275            .to_string())
276    }
277}