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}