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}