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