playwright_core/protocol/locator.rs
1// Locator - Lazy element selector with auto-waiting
2//
3// Locators are the central piece of Playwright's auto-waiting and retry-ability.
4// They represent a way to find element(s) on the page at any given moment.
5//
6// Key characteristics:
7// - Lazy: Don't execute until an action is performed
8// - Retryable: Auto-wait for elements to match actionability checks
9// - Chainable: Can create sub-locators via first(), last(), nth(), locator()
10//
11// Architecture:
12// - Locator is NOT a ChannelOwner - it's a lightweight wrapper
13// - Stores selector string and reference to Frame
14// - Delegates all operations to Frame with strict=true
15//
16// See: https://playwright.dev/docs/api/class-locator
17
18use crate::error::Result;
19use crate::protocol::Frame;
20use std::sync::Arc;
21
22/// Locator represents a way to find element(s) on the page at any given moment.
23///
24/// Locators are lazy - they don't execute queries until an action is performed.
25/// This enables auto-waiting and retry-ability for robust test automation.
26///
27/// # Examples
28///
29/// ```ignore
30/// use playwright_core::protocol::{Playwright, SelectOption};
31///
32/// #[tokio::main]
33/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
34/// let playwright = Playwright::launch().await?;
35/// let browser = playwright.chromium().launch().await?;
36/// let page = browser.new_page().await?;
37///
38/// // Demonstrate set_checked() - checkbox interaction
39/// let _ = page.goto(
40/// "data:text/html,<input type='checkbox' id='cb'>",
41/// None
42/// ).await;
43/// let checkbox = page.locator("#cb").await;
44/// checkbox.set_checked(true, None).await?;
45/// assert!(checkbox.is_checked().await?);
46/// checkbox.set_checked(false, None).await?;
47/// assert!(!checkbox.is_checked().await?);
48///
49/// // Demonstrate select_option() - select by value, label, and index
50/// let _ = page.goto(
51/// "data:text/html,<select id='fruits'>\
52/// <option value='apple'>Apple</option>\
53/// <option value='banana'>Banana</option>\
54/// <option value='cherry'>Cherry</option>\
55/// </select>",
56/// None
57/// ).await;
58/// let select = page.locator("#fruits").await;
59/// select.select_option("banana", None).await?;
60/// assert_eq!(select.input_value(None).await?, "banana");
61/// select.select_option(SelectOption::Label("Apple".to_string()), None).await?;
62/// assert_eq!(select.input_value(None).await?, "apple");
63/// select.select_option(SelectOption::Index(2), None).await?;
64/// assert_eq!(select.input_value(None).await?, "cherry");
65///
66/// // Demonstrate select_option_multiple() - multi-select
67/// let _ = page.goto(
68/// "data:text/html,<select id='colors' multiple>\
69/// <option value='red'>Red</option>\
70/// <option value='green'>Green</option>\
71/// <option value='blue'>Blue</option>\
72/// <option value='yellow'>Yellow</option>\
73/// </select>",
74/// None
75/// ).await;
76/// let multi = page.locator("#colors").await;
77/// let selected = multi.select_option_multiple(&["red", "blue"], None).await?;
78/// assert_eq!(selected.len(), 2);
79/// assert!(selected.contains(&"red".to_string()));
80/// assert!(selected.contains(&"blue".to_string()));
81///
82/// // Demonstrate screenshot() - element screenshot
83/// let _ = page.goto(
84/// "data:text/html,<h1 id='title'>Hello World</h1>",
85/// None
86/// ).await;
87/// let heading = page.locator("#title").await;
88/// let screenshot = heading.screenshot(None).await?;
89/// assert!(!screenshot.is_empty());
90///
91/// browser.close().await?;
92/// Ok(())
93/// }
94/// ```
95///
96/// See: <https://playwright.dev/docs/api/class-locator>
97#[derive(Clone)]
98pub struct Locator {
99 frame: Arc<Frame>,
100 selector: String,
101}
102
103impl Locator {
104 /// Creates a new Locator (internal use only)
105 ///
106 /// Use `page.locator()` or `frame.locator()` to create locators in application code.
107 pub(crate) fn new(frame: Arc<Frame>, selector: String) -> Self {
108 Self { frame, selector }
109 }
110
111 /// Returns the selector string for this locator
112 pub fn selector(&self) -> &str {
113 &self.selector
114 }
115
116 /// Creates a locator for the first matching element.
117 ///
118 /// See: <https://playwright.dev/docs/api/class-locator#locator-first>
119 pub fn first(&self) -> Locator {
120 Locator::new(
121 Arc::clone(&self.frame),
122 format!("{} >> nth=0", self.selector),
123 )
124 }
125
126 /// Creates a locator for the last matching element.
127 ///
128 /// See: <https://playwright.dev/docs/api/class-locator#locator-last>
129 pub fn last(&self) -> Locator {
130 Locator::new(
131 Arc::clone(&self.frame),
132 format!("{} >> nth=-1", self.selector),
133 )
134 }
135
136 /// Creates a locator for the nth matching element (0-indexed).
137 ///
138 /// See: <https://playwright.dev/docs/api/class-locator#locator-nth>
139 pub fn nth(&self, index: i32) -> Locator {
140 Locator::new(
141 Arc::clone(&self.frame),
142 format!("{} >> nth={}", self.selector, index),
143 )
144 }
145
146 /// Creates a sub-locator within this locator's subtree.
147 ///
148 /// See: <https://playwright.dev/docs/api/class-locator#locator-locator>
149 pub fn locator(&self, selector: &str) -> Locator {
150 Locator::new(
151 Arc::clone(&self.frame),
152 format!("{} >> {}", self.selector, selector),
153 )
154 }
155
156 /// Returns the number of elements matching this locator.
157 ///
158 /// See: <https://playwright.dev/docs/api/class-locator#locator-count>
159 pub async fn count(&self) -> Result<usize> {
160 self.frame.locator_count(&self.selector).await
161 }
162
163 /// Returns the text content of the element.
164 ///
165 /// See: <https://playwright.dev/docs/api/class-locator#locator-text-content>
166 pub async fn text_content(&self) -> Result<Option<String>> {
167 self.frame.locator_text_content(&self.selector).await
168 }
169
170 /// Returns the inner text of the element (visible text).
171 ///
172 /// See: <https://playwright.dev/docs/api/class-locator#locator-inner-text>
173 pub async fn inner_text(&self) -> Result<String> {
174 self.frame.locator_inner_text(&self.selector).await
175 }
176
177 /// Returns the inner HTML of the element.
178 ///
179 /// See: <https://playwright.dev/docs/api/class-locator#locator-inner-html>
180 pub async fn inner_html(&self) -> Result<String> {
181 self.frame.locator_inner_html(&self.selector).await
182 }
183
184 /// Returns the value of the specified attribute.
185 ///
186 /// See: <https://playwright.dev/docs/api/class-locator#locator-get-attribute>
187 pub async fn get_attribute(&self, name: &str) -> Result<Option<String>> {
188 self.frame.locator_get_attribute(&self.selector, name).await
189 }
190
191 /// Returns whether the element is visible.
192 ///
193 /// See: <https://playwright.dev/docs/api/class-locator#locator-is-visible>
194 pub async fn is_visible(&self) -> Result<bool> {
195 self.frame.locator_is_visible(&self.selector).await
196 }
197
198 /// Returns whether the element is enabled.
199 ///
200 /// See: <https://playwright.dev/docs/api/class-locator#locator-is-enabled>
201 pub async fn is_enabled(&self) -> Result<bool> {
202 self.frame.locator_is_enabled(&self.selector).await
203 }
204
205 /// Returns whether the checkbox or radio button is checked.
206 ///
207 /// See: <https://playwright.dev/docs/api/class-locator#locator-is-checked>
208 pub async fn is_checked(&self) -> Result<bool> {
209 self.frame.locator_is_checked(&self.selector).await
210 }
211
212 /// Returns whether the element is editable.
213 ///
214 /// See: <https://playwright.dev/docs/api/class-locator#locator-is-editable>
215 pub async fn is_editable(&self) -> Result<bool> {
216 self.frame.locator_is_editable(&self.selector).await
217 }
218
219 /// Returns whether the element is focused (currently has focus).
220 ///
221 /// See: <https://playwright.dev/docs/api/class-locator#locator-is-focused>
222 pub async fn is_focused(&self) -> Result<bool> {
223 self.frame.locator_is_focused(&self.selector).await
224 }
225
226 // Action methods
227
228 /// Clicks the element.
229 ///
230 /// See: <https://playwright.dev/docs/api/class-locator#locator-click>
231 pub async fn click(&self, options: Option<crate::protocol::ClickOptions>) -> Result<()> {
232 self.frame.locator_click(&self.selector, options).await
233 }
234
235 /// Double clicks the element.
236 ///
237 /// See: <https://playwright.dev/docs/api/class-locator#locator-dblclick>
238 pub async fn dblclick(&self, options: Option<crate::protocol::ClickOptions>) -> Result<()> {
239 self.frame.locator_dblclick(&self.selector, options).await
240 }
241
242 /// Fills the element with text.
243 ///
244 /// See: <https://playwright.dev/docs/api/class-locator#locator-fill>
245 pub async fn fill(
246 &self,
247 text: &str,
248 options: Option<crate::protocol::FillOptions>,
249 ) -> Result<()> {
250 self.frame.locator_fill(&self.selector, text, options).await
251 }
252
253 /// Clears the element's value.
254 ///
255 /// See: <https://playwright.dev/docs/api/class-locator#locator-clear>
256 pub async fn clear(&self, options: Option<crate::protocol::FillOptions>) -> Result<()> {
257 self.frame.locator_clear(&self.selector, options).await
258 }
259
260 /// Presses a key on the element.
261 ///
262 /// See: <https://playwright.dev/docs/api/class-locator#locator-press>
263 pub async fn press(
264 &self,
265 key: &str,
266 options: Option<crate::protocol::PressOptions>,
267 ) -> Result<()> {
268 self.frame.locator_press(&self.selector, key, options).await
269 }
270
271 /// Ensures the checkbox or radio button is checked.
272 ///
273 /// This method is idempotent - if already checked, does nothing.
274 ///
275 /// See: <https://playwright.dev/docs/api/class-locator#locator-check>
276 pub async fn check(&self, options: Option<crate::protocol::CheckOptions>) -> Result<()> {
277 self.frame.locator_check(&self.selector, options).await
278 }
279
280 /// Ensures the checkbox is unchecked.
281 ///
282 /// This method is idempotent - if already unchecked, does nothing.
283 ///
284 /// See: <https://playwright.dev/docs/api/class-locator#locator-uncheck>
285 pub async fn uncheck(&self, options: Option<crate::protocol::CheckOptions>) -> Result<()> {
286 self.frame.locator_uncheck(&self.selector, options).await
287 }
288
289 /// Sets the checkbox or radio button to the specified checked state.
290 ///
291 /// This is a convenience method that calls `check()` if `checked` is true,
292 /// or `uncheck()` if `checked` is false.
293 ///
294 /// See: <https://playwright.dev/docs/api/class-locator#locator-set-checked>
295 pub async fn set_checked(
296 &self,
297 checked: bool,
298 options: Option<crate::protocol::CheckOptions>,
299 ) -> Result<()> {
300 if checked {
301 self.check(options).await
302 } else {
303 self.uncheck(options).await
304 }
305 }
306
307 /// Hovers the mouse over the element.
308 ///
309 /// See: <https://playwright.dev/docs/api/class-locator#locator-hover>
310 pub async fn hover(&self, options: Option<crate::protocol::HoverOptions>) -> Result<()> {
311 self.frame.locator_hover(&self.selector, options).await
312 }
313
314 /// Returns the value of the input, textarea, or select element.
315 ///
316 /// See: <https://playwright.dev/docs/api/class-locator#locator-input-value>
317 pub async fn input_value(&self, _options: Option<()>) -> Result<String> {
318 self.frame.locator_input_value(&self.selector).await
319 }
320
321 /// Selects one or more options in a select element.
322 ///
323 /// Returns an array of option values that have been successfully selected.
324 ///
325 /// See: <https://playwright.dev/docs/api/class-locator#locator-select-option>
326 pub async fn select_option(
327 &self,
328 value: impl Into<crate::protocol::SelectOption>,
329 options: Option<crate::protocol::SelectOptions>,
330 ) -> Result<Vec<String>> {
331 self.frame
332 .locator_select_option(&self.selector, value.into(), options)
333 .await
334 }
335
336 /// Selects multiple options in a select element.
337 ///
338 /// Returns an array of option values that have been successfully selected.
339 ///
340 /// See: <https://playwright.dev/docs/api/class-locator#locator-select-option>
341 pub async fn select_option_multiple(
342 &self,
343 values: &[impl Into<crate::protocol::SelectOption> + Clone],
344 options: Option<crate::protocol::SelectOptions>,
345 ) -> Result<Vec<String>> {
346 let select_options: Vec<crate::protocol::SelectOption> =
347 values.iter().map(|v| v.clone().into()).collect();
348 self.frame
349 .locator_select_option_multiple(&self.selector, select_options, options)
350 .await
351 }
352
353 /// Sets the file path(s) to upload to a file input element.
354 ///
355 /// See: <https://playwright.dev/docs/api/class-locator#locator-set-input-files>
356 pub async fn set_input_files(
357 &self,
358 file: &std::path::PathBuf,
359 _options: Option<()>,
360 ) -> Result<()> {
361 self.frame
362 .locator_set_input_files(&self.selector, file)
363 .await
364 }
365
366 /// Sets multiple file paths to upload to a file input element.
367 ///
368 /// See: <https://playwright.dev/docs/api/class-locator#locator-set-input-files>
369 pub async fn set_input_files_multiple(
370 &self,
371 files: &[&std::path::PathBuf],
372 _options: Option<()>,
373 ) -> Result<()> {
374 self.frame
375 .locator_set_input_files_multiple(&self.selector, files)
376 .await
377 }
378
379 /// Sets a file to upload using FilePayload (explicit name, mimeType, buffer).
380 ///
381 /// See: <https://playwright.dev/docs/api/class-locator#locator-set-input-files>
382 pub async fn set_input_files_payload(
383 &self,
384 file: crate::protocol::FilePayload,
385 _options: Option<()>,
386 ) -> Result<()> {
387 self.frame
388 .locator_set_input_files_payload(&self.selector, file)
389 .await
390 }
391
392 /// Sets multiple files to upload using FilePayload.
393 ///
394 /// See: <https://playwright.dev/docs/api/class-locator#locator-set-input-files>
395 pub async fn set_input_files_payload_multiple(
396 &self,
397 files: &[crate::protocol::FilePayload],
398 _options: Option<()>,
399 ) -> Result<()> {
400 self.frame
401 .locator_set_input_files_payload_multiple(&self.selector, files)
402 .await
403 }
404
405 /// Takes a screenshot of the element and returns the image bytes.
406 ///
407 /// This method uses strict mode - it will fail if the selector matches multiple elements.
408 /// Use `first()`, `last()`, or `nth()` to refine the selector to a single element.
409 ///
410 /// See: <https://playwright.dev/docs/api/class-locator#locator-screenshot>
411 pub async fn screenshot(
412 &self,
413 options: Option<crate::protocol::ScreenshotOptions>,
414 ) -> Result<Vec<u8>> {
415 // Query for the element using strict mode (should return exactly one)
416 let element = self
417 .frame
418 .query_selector(&self.selector)
419 .await?
420 .ok_or_else(|| {
421 crate::error::Error::ElementNotFound(format!(
422 "Element not found: {}",
423 self.selector
424 ))
425 })?;
426
427 // Delegate to ElementHandle.screenshot()
428 element.screenshot(options).await
429 }
430}
431
432impl std::fmt::Debug for Locator {
433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434 f.debug_struct("Locator")
435 .field("selector", &self.selector)
436 .finish()
437 }
438}