viewpoint_core/page/frame_locator/
mod.rs1use std::time::Duration;
9
10use super::locator::{AriaRole, LocatorOptions, Selector};
11use crate::Page;
12
13const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
15
16#[derive(Debug, Clone)]
57pub struct FrameLocator<'a> {
58 page: &'a Page,
60 frame_selector: String,
62 parent_selectors: Vec<String>,
64 timeout: Duration,
66}
67
68impl<'a> FrameLocator<'a> {
69 pub(crate) fn new(page: &'a Page, selector: impl Into<String>) -> Self {
71 Self {
72 page,
73 frame_selector: selector.into(),
74 parent_selectors: Vec::new(),
75 timeout: DEFAULT_TIMEOUT,
76 }
77 }
78
79 fn with_parent(
81 page: &'a Page,
82 frame_selector: String,
83 mut parent_selectors: Vec<String>,
84 parent_selector: String,
85 ) -> Self {
86 parent_selectors.push(parent_selector);
87 Self {
88 page,
89 frame_selector,
90 parent_selectors,
91 timeout: DEFAULT_TIMEOUT,
92 }
93 }
94
95 #[must_use]
97 pub fn timeout(mut self, timeout: Duration) -> Self {
98 self.timeout = timeout;
99 self
100 }
101
102 pub fn page(&self) -> &'a Page {
104 self.page
105 }
106
107 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
121 FrameElementLocator::new(self.clone(), Selector::Css(selector.into()))
122 }
123
124 pub fn get_by_text(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
126 FrameElementLocator::new(
127 self.clone(),
128 Selector::Text {
129 text: text.into(),
130 exact: false,
131 },
132 )
133 }
134
135 pub fn get_by_text_exact(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
137 FrameElementLocator::new(
138 self.clone(),
139 Selector::Text {
140 text: text.into(),
141 exact: true,
142 },
143 )
144 }
145
146 pub fn get_by_role(&self, role: AriaRole) -> FrameRoleLocatorBuilder<'a> {
148 FrameRoleLocatorBuilder::new(self.clone(), role)
149 }
150
151 pub fn get_by_test_id(&self, test_id: impl Into<String>) -> FrameElementLocator<'a> {
153 FrameElementLocator::new(self.clone(), Selector::TestId(test_id.into()))
154 }
155
156 pub fn get_by_label(&self, label: impl Into<String>) -> FrameElementLocator<'a> {
158 FrameElementLocator::new(self.clone(), Selector::Label(label.into()))
159 }
160
161 pub fn get_by_placeholder(&self, placeholder: impl Into<String>) -> FrameElementLocator<'a> {
163 FrameElementLocator::new(self.clone(), Selector::Placeholder(placeholder.into()))
164 }
165
166 pub fn frame_locator(&self, selector: impl Into<String>) -> FrameLocator<'a> {
184 FrameLocator::with_parent(
185 self.page,
186 selector.into(),
187 self.parent_selectors.clone(),
188 self.frame_selector.clone(),
189 )
190 }
191
192 pub fn selector(&self) -> &str {
194 &self.frame_selector
195 }
196
197 pub fn parent_selectors(&self) -> &[String] {
199 &self.parent_selectors
200 }
201
202 pub(crate) fn to_js_frame_access(&self) -> String {
204 let mut js = String::new();
205
206 js.push_str("(function() {\n");
208 js.push_str(" let doc = document;\n");
209
210 for parent_selector in &self.parent_selectors {
212 js.push_str(&format!(
213 " const parent = doc.querySelector({});\n",
214 super::locator::selector::js_string_literal(parent_selector)
215 ));
216 js.push_str(" if (!parent || !parent.contentDocument) return null;\n");
217 js.push_str(" doc = parent.contentDocument;\n");
218 }
219
220 js.push_str(&format!(
222 " const frame = doc.querySelector({});\n",
223 super::locator::selector::js_string_literal(&self.frame_selector)
224 ));
225 js.push_str(" if (!frame || !frame.contentDocument) return null;\n");
226 js.push_str(" return frame.contentDocument;\n");
227 js.push_str("})()");
228 js
229 }
230}
231
232#[derive(Debug, Clone)]
237pub struct FrameElementLocator<'a> {
238 frame_locator: FrameLocator<'a>,
240 selector: Selector,
242 options: LocatorOptions,
244}
245
246impl<'a> FrameElementLocator<'a> {
247 fn new(frame_locator: FrameLocator<'a>, selector: Selector) -> Self {
249 Self {
250 frame_locator,
251 selector,
252 options: LocatorOptions::default(),
253 }
254 }
255
256 #[must_use]
258 pub fn timeout(mut self, timeout: Duration) -> Self {
259 self.options.timeout = timeout;
260 self
261 }
262
263 #[must_use]
265 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
266 FrameElementLocator {
267 frame_locator: self.frame_locator.clone(),
268 selector: Selector::Chained(
269 Box::new(self.selector.clone()),
270 Box::new(Selector::Css(selector.into())),
271 ),
272 options: self.options.clone(),
273 }
274 }
275
276 #[must_use]
278 pub fn first(&self) -> FrameElementLocator<'a> {
279 FrameElementLocator {
280 frame_locator: self.frame_locator.clone(),
281 selector: Selector::Nth {
282 base: Box::new(self.selector.clone()),
283 index: 0,
284 },
285 options: self.options.clone(),
286 }
287 }
288
289 #[must_use]
291 pub fn last(&self) -> FrameElementLocator<'a> {
292 FrameElementLocator {
293 frame_locator: self.frame_locator.clone(),
294 selector: Selector::Nth {
295 base: Box::new(self.selector.clone()),
296 index: -1,
297 },
298 options: self.options.clone(),
299 }
300 }
301
302 #[must_use]
304 pub fn nth(&self, index: i32) -> FrameElementLocator<'a> {
305 FrameElementLocator {
306 frame_locator: self.frame_locator.clone(),
307 selector: Selector::Nth {
308 base: Box::new(self.selector.clone()),
309 index,
310 },
311 options: self.options.clone(),
312 }
313 }
314
315 pub fn frame_locator(&self) -> &FrameLocator<'a> {
317 &self.frame_locator
318 }
319
320 pub fn selector(&self) -> &Selector {
322 &self.selector
323 }
324
325 pub(crate) fn options(&self) -> &LocatorOptions {
327 &self.options
328 }
329
330 fn to_js_expression(&self) -> String {
332 let frame_access = self.frame_locator.to_js_frame_access();
333 let element_selector = self.selector.to_js_expression();
334
335 format!(
336 r"(function() {{
337 const frameDoc = {frame_access};
338 if (!frameDoc) return {{ found: false, count: 0, error: 'Frame not found or not accessible' }};
339
340 // Override document for the selector expression
341 const originalDocument = document;
342 try {{
343 // Create a modified expression that uses frameDoc instead of document
344 const elements = (function() {{
345 const document = frameDoc;
346 return Array.from({element_selector});
347 }})();
348 return elements;
349 }} catch (e) {{
350 return [];
351 }}
352 }})()"
353 )
354 }
355}
356
357#[derive(Debug)]
359pub struct FrameRoleLocatorBuilder<'a> {
360 frame_locator: FrameLocator<'a>,
361 role: AriaRole,
362 name: Option<String>,
363}
364
365impl<'a> FrameRoleLocatorBuilder<'a> {
366 fn new(frame_locator: FrameLocator<'a>, role: AriaRole) -> Self {
367 Self {
368 frame_locator,
369 role,
370 name: None,
371 }
372 }
373
374 #[must_use]
376 pub fn with_name(mut self, name: impl Into<String>) -> Self {
377 self.name = Some(name.into());
378 self
379 }
380
381 pub fn build(self) -> FrameElementLocator<'a> {
383 FrameElementLocator::new(
384 self.frame_locator,
385 Selector::Role {
386 role: self.role,
387 name: self.name,
388 },
389 )
390 }
391}
392
393impl<'a> From<FrameRoleLocatorBuilder<'a>> for FrameElementLocator<'a> {
394 fn from(builder: FrameRoleLocatorBuilder<'a>) -> Self {
395 builder.build()
396 }
397}