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}