viewpoint_core/page/frame_locator/
mod.rs1use std::time::Duration;
9
10use super::locator::{AriaRole, LocatorOptions, Selector};
11use crate::Page;
12use viewpoint_js::js;
13use viewpoint_js_core::escape_js_string_single;
14
15const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
17
18#[derive(Debug, Clone)]
59pub struct FrameLocator<'a> {
60 page: &'a Page,
62 frame_selector: String,
64 parent_selectors: Vec<String>,
66 timeout: Duration,
68}
69
70impl<'a> FrameLocator<'a> {
71 pub(crate) fn new(page: &'a Page, selector: impl Into<String>) -> Self {
73 Self {
74 page,
75 frame_selector: selector.into(),
76 parent_selectors: Vec::new(),
77 timeout: DEFAULT_TIMEOUT,
78 }
79 }
80
81 fn with_parent(
83 page: &'a Page,
84 frame_selector: String,
85 mut parent_selectors: Vec<String>,
86 parent_selector: String,
87 ) -> Self {
88 parent_selectors.push(parent_selector);
89 Self {
90 page,
91 frame_selector,
92 parent_selectors,
93 timeout: DEFAULT_TIMEOUT,
94 }
95 }
96
97 #[must_use]
99 pub fn timeout(mut self, timeout: Duration) -> Self {
100 self.timeout = timeout;
101 self
102 }
103
104 pub fn page(&self) -> &'a Page {
106 self.page
107 }
108
109 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
123 FrameElementLocator::new(self.clone(), Selector::Css(selector.into()))
124 }
125
126 pub fn get_by_text(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
128 FrameElementLocator::new(
129 self.clone(),
130 Selector::Text {
131 text: text.into(),
132 exact: false,
133 },
134 )
135 }
136
137 pub fn get_by_text_exact(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
139 FrameElementLocator::new(
140 self.clone(),
141 Selector::Text {
142 text: text.into(),
143 exact: true,
144 },
145 )
146 }
147
148 pub fn get_by_role(&self, role: AriaRole) -> FrameRoleLocatorBuilder<'a> {
150 FrameRoleLocatorBuilder::new(self.clone(), role)
151 }
152
153 pub fn get_by_test_id(&self, test_id: impl Into<String>) -> FrameElementLocator<'a> {
155 FrameElementLocator::new(self.clone(), Selector::TestId(test_id.into()))
156 }
157
158 pub fn get_by_label(&self, label: impl Into<String>) -> FrameElementLocator<'a> {
160 FrameElementLocator::new(self.clone(), Selector::Label(label.into()))
161 }
162
163 pub fn get_by_placeholder(&self, placeholder: impl Into<String>) -> FrameElementLocator<'a> {
165 FrameElementLocator::new(self.clone(), Selector::Placeholder(placeholder.into()))
166 }
167
168 pub fn frame_locator(&self, selector: impl Into<String>) -> FrameLocator<'a> {
186 FrameLocator::with_parent(
187 self.page,
188 selector.into(),
189 self.parent_selectors.clone(),
190 self.frame_selector.clone(),
191 )
192 }
193
194 pub fn selector(&self) -> &str {
196 &self.frame_selector
197 }
198
199 pub fn parent_selectors(&self) -> &[String] {
201 &self.parent_selectors
202 }
203
204 pub(crate) fn to_js_frame_access(&self) -> String {
210 let mut js = String::new();
211
212 js.push_str("(function() {\n");
214 js.push_str(" let doc = document;\n");
215
216 for parent_selector in &self.parent_selectors {
218 let escaped_selector = escape_js_string_single(parent_selector);
219 js.push_str(" const parent = doc.querySelector(");
220 js.push_str(&escaped_selector);
221 js.push_str(");\n");
222 js.push_str(" if (!parent || !parent.contentDocument) return null;\n");
223 js.push_str(" doc = parent.contentDocument;\n");
224 }
225
226 let escaped_frame_selector = escape_js_string_single(&self.frame_selector);
228 js.push_str(" const frame = doc.querySelector(");
229 js.push_str(&escaped_frame_selector);
230 js.push_str(");\n");
231 js.push_str(" if (!frame || !frame.contentDocument) return null;\n");
232 js.push_str(" return frame.contentDocument;\n");
233 js.push_str("})()");
234 js
235 }
236}
237
238#[derive(Debug, Clone)]
243pub struct FrameElementLocator<'a> {
244 frame_locator: FrameLocator<'a>,
246 selector: Selector,
248 options: LocatorOptions,
250}
251
252impl<'a> FrameElementLocator<'a> {
253 fn new(frame_locator: FrameLocator<'a>, selector: Selector) -> Self {
255 Self {
256 frame_locator,
257 selector,
258 options: LocatorOptions::default(),
259 }
260 }
261
262 #[must_use]
264 pub fn timeout(mut self, timeout: Duration) -> Self {
265 self.options.timeout = timeout;
266 self
267 }
268
269 #[must_use]
271 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
272 FrameElementLocator {
273 frame_locator: self.frame_locator.clone(),
274 selector: Selector::Chained(
275 Box::new(self.selector.clone()),
276 Box::new(Selector::Css(selector.into())),
277 ),
278 options: self.options.clone(),
279 }
280 }
281
282 #[must_use]
284 pub fn first(&self) -> FrameElementLocator<'a> {
285 FrameElementLocator {
286 frame_locator: self.frame_locator.clone(),
287 selector: Selector::Nth {
288 base: Box::new(self.selector.clone()),
289 index: 0,
290 },
291 options: self.options.clone(),
292 }
293 }
294
295 #[must_use]
297 pub fn last(&self) -> FrameElementLocator<'a> {
298 FrameElementLocator {
299 frame_locator: self.frame_locator.clone(),
300 selector: Selector::Nth {
301 base: Box::new(self.selector.clone()),
302 index: -1,
303 },
304 options: self.options.clone(),
305 }
306 }
307
308 #[must_use]
310 pub fn nth(&self, index: i32) -> FrameElementLocator<'a> {
311 FrameElementLocator {
312 frame_locator: self.frame_locator.clone(),
313 selector: Selector::Nth {
314 base: Box::new(self.selector.clone()),
315 index,
316 },
317 options: self.options.clone(),
318 }
319 }
320
321 pub fn frame_locator(&self) -> &FrameLocator<'a> {
323 &self.frame_locator
324 }
325
326 pub fn selector(&self) -> &Selector {
328 &self.selector
329 }
330
331 pub(crate) fn options(&self) -> &LocatorOptions {
333 &self.options
334 }
335
336 fn to_js_expression(&self) -> String {
338 let frame_access = self.frame_locator.to_js_frame_access();
339 let element_selector = self.selector.to_js_expression();
340
341 js! {
342 (function() {
343 const frameDoc = @{frame_access};
344 if (!frameDoc) return { found: false, count: 0, error: "Frame not found or not accessible" };
345
346 const originalDocument = document;
348 try {
349 const elements = (function() {
351 const document = frameDoc;
352 return Array.from(@{element_selector});
353 })();
354 return elements;
355 } catch (e) {
356 return [];
357 }
358 })()
359 }
360 }
361}
362
363#[derive(Debug)]
365pub struct FrameRoleLocatorBuilder<'a> {
366 frame_locator: FrameLocator<'a>,
367 role: AriaRole,
368 name: Option<String>,
369}
370
371impl<'a> FrameRoleLocatorBuilder<'a> {
372 fn new(frame_locator: FrameLocator<'a>, role: AriaRole) -> Self {
373 Self {
374 frame_locator,
375 role,
376 name: None,
377 }
378 }
379
380 #[must_use]
382 pub fn with_name(mut self, name: impl Into<String>) -> Self {
383 self.name = Some(name.into());
384 self
385 }
386
387 pub fn build(self) -> FrameElementLocator<'a> {
389 FrameElementLocator::new(
390 self.frame_locator,
391 Selector::Role {
392 role: self.role,
393 name: self.name,
394 },
395 )
396 }
397}
398
399impl<'a> From<FrameRoleLocatorBuilder<'a>> for FrameElementLocator<'a> {
400 fn from(builder: FrameRoleLocatorBuilder<'a>) -> Self {
401 builder.build()
402 }
403}