viewpoint_core/page/locator/
mod.rs

1//! Locator system for element selection.
2//!
3//! Locators are lazy handles that store selection criteria but don't query the DOM
4//! until an action is performed. This enables auto-waiting and chainable refinement.
5//!
6//! # Example
7//!
8//! ```ignore
9//! // CSS selector
10//! let button = page.locator("button.submit");
11//!
12//! // Text locator
13//! let heading = page.get_by_text("Welcome");
14//!
15//! // Role locator
16//! let submit = page.get_by_role(AriaRole::Button).with_name("Submit");
17//!
18//! // Chained locators
19//! let item = page.locator(".list").locator(".item").first();
20//! ```
21
22mod actions;
23mod selector;
24
25use std::time::Duration;
26
27pub use selector::{AriaRole, Selector, TextOptions};
28
29use crate::Page;
30
31/// Default timeout for locator operations.
32const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
33
34/// A locator for finding elements on a page.
35///
36/// Locators are lightweight handles that store selection criteria. They don't
37/// query the DOM until an action is performed, enabling auto-waiting.
38#[derive(Debug, Clone)]
39pub struct Locator<'a> {
40    /// Reference to the page.
41    page: &'a Page,
42    /// The selector for finding elements.
43    selector: Selector,
44    /// Locator options.
45    options: LocatorOptions,
46}
47
48/// Options for locator behavior.
49#[derive(Debug, Clone)]
50pub struct LocatorOptions {
51    /// Timeout for operations.
52    pub timeout: Duration,
53}
54
55impl Default for LocatorOptions {
56    fn default() -> Self {
57        Self {
58            timeout: DEFAULT_TIMEOUT,
59        }
60    }
61}
62
63impl<'a> Locator<'a> {
64    /// Create a new locator.
65    pub(crate) fn new(page: &'a Page, selector: Selector) -> Self {
66        Self {
67            page,
68            selector,
69            options: LocatorOptions::default(),
70        }
71    }
72
73    /// Create a new locator with custom options.
74    #[allow(dead_code)] // Available for future use
75    pub(crate) fn with_options(page: &'a Page, selector: Selector, options: LocatorOptions) -> Self {
76        Self {
77            page,
78            selector,
79            options,
80        }
81    }
82
83    /// Get the page this locator belongs to.
84    pub fn page(&self) -> &'a Page {
85        self.page
86    }
87
88    /// Get the selector.
89    pub fn selector(&self) -> &Selector {
90        &self.selector
91    }
92
93    /// Get the options.
94    pub fn options(&self) -> &LocatorOptions {
95        &self.options
96    }
97
98    /// Set a custom timeout for this locator.
99    #[must_use]
100    pub fn timeout(mut self, timeout: Duration) -> Self {
101        self.options.timeout = timeout;
102        self
103    }
104
105    /// Create a child locator that further filters elements.
106    ///
107    /// # Example
108    ///
109    /// ```ignore
110    /// let items = page.locator(".list").locator(".item");
111    /// ```
112    #[must_use]
113    pub fn locator(&self, selector: impl Into<String>) -> Locator<'a> {
114        Locator {
115            page: self.page,
116            selector: Selector::Chained(
117                Box::new(self.selector.clone()),
118                Box::new(Selector::Css(selector.into())),
119            ),
120            options: self.options.clone(),
121        }
122    }
123
124    /// Select the first matching element.
125    #[must_use]
126    pub fn first(&self) -> Locator<'a> {
127        Locator {
128            page: self.page,
129            selector: Selector::Nth {
130                base: Box::new(self.selector.clone()),
131                index: 0,
132            },
133            options: self.options.clone(),
134        }
135    }
136
137    /// Select the last matching element.
138    #[must_use]
139    pub fn last(&self) -> Locator<'a> {
140        Locator {
141            page: self.page,
142            selector: Selector::Nth {
143                base: Box::new(self.selector.clone()),
144                index: -1,
145            },
146            options: self.options.clone(),
147        }
148    }
149
150    /// Select the nth matching element (0-indexed).
151    #[must_use]
152    pub fn nth(&self, index: i32) -> Locator<'a> {
153        Locator {
154            page: self.page,
155            selector: Selector::Nth {
156                base: Box::new(self.selector.clone()),
157                index,
158            },
159            options: self.options.clone(),
160        }
161    }
162
163    /// Convert the selector to a JavaScript expression for CDP evaluation.
164    #[allow(dead_code)] // Available for future use
165    pub(crate) fn to_js_selector(&self) -> String {
166        self.selector.to_js_expression()
167    }
168}