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> {
116 FrameElementLocator::new(self.clone(), Selector::Css(selector.into()))
117 }
118
119 pub fn get_by_text(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
121 FrameElementLocator::new(
122 self.clone(),
123 Selector::Text {
124 text: text.into(),
125 exact: false,
126 },
127 )
128 }
129
130 pub fn get_by_text_exact(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
132 FrameElementLocator::new(
133 self.clone(),
134 Selector::Text {
135 text: text.into(),
136 exact: true,
137 },
138 )
139 }
140
141 pub fn get_by_role(&self, role: AriaRole) -> FrameRoleLocatorBuilder<'a> {
143 FrameRoleLocatorBuilder::new(self.clone(), role)
144 }
145
146 pub fn get_by_test_id(&self, test_id: impl Into<String>) -> FrameElementLocator<'a> {
148 FrameElementLocator::new(self.clone(), Selector::TestId(test_id.into()))
149 }
150
151 pub fn get_by_label(&self, label: impl Into<String>) -> FrameElementLocator<'a> {
153 FrameElementLocator::new(self.clone(), Selector::Label(label.into()))
154 }
155
156 pub fn get_by_placeholder(&self, placeholder: impl Into<String>) -> FrameElementLocator<'a> {
158 FrameElementLocator::new(self.clone(), Selector::Placeholder(placeholder.into()))
159 }
160
161 pub fn frame_locator(&self, selector: impl Into<String>) -> FrameLocator<'a> {
174 FrameLocator::with_parent(
175 self.page,
176 selector.into(),
177 self.parent_selectors.clone(),
178 self.frame_selector.clone(),
179 )
180 }
181
182 pub fn selector(&self) -> &str {
184 &self.frame_selector
185 }
186
187 pub fn parent_selectors(&self) -> &[String] {
189 &self.parent_selectors
190 }
191
192 pub(crate) fn to_js_frame_access(&self) -> String {
194 let mut js = String::new();
195
196 js.push_str("(function() {\n");
198 js.push_str(" let doc = document;\n");
199
200 for parent_selector in &self.parent_selectors {
202 js.push_str(&format!(
203 " const parent = doc.querySelector({});\n",
204 super::locator::selector::js_string_literal(parent_selector)
205 ));
206 js.push_str(" if (!parent || !parent.contentDocument) return null;\n");
207 js.push_str(" doc = parent.contentDocument;\n");
208 }
209
210 js.push_str(&format!(
212 " const frame = doc.querySelector({});\n",
213 super::locator::selector::js_string_literal(&self.frame_selector)
214 ));
215 js.push_str(" if (!frame || !frame.contentDocument) return null;\n");
216 js.push_str(" return frame.contentDocument;\n");
217 js.push_str("})()");
218 js
219 }
220}
221
222#[derive(Debug, Clone)]
227pub struct FrameElementLocator<'a> {
228 frame_locator: FrameLocator<'a>,
230 selector: Selector,
232 options: LocatorOptions,
234}
235
236impl<'a> FrameElementLocator<'a> {
237 fn new(frame_locator: FrameLocator<'a>, selector: Selector) -> Self {
239 Self {
240 frame_locator,
241 selector,
242 options: LocatorOptions::default(),
243 }
244 }
245
246 #[must_use]
248 pub fn timeout(mut self, timeout: Duration) -> Self {
249 self.options.timeout = timeout;
250 self
251 }
252
253 #[must_use]
255 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
256 FrameElementLocator {
257 frame_locator: self.frame_locator.clone(),
258 selector: Selector::Chained(
259 Box::new(self.selector.clone()),
260 Box::new(Selector::Css(selector.into())),
261 ),
262 options: self.options.clone(),
263 }
264 }
265
266 #[must_use]
268 pub fn first(&self) -> FrameElementLocator<'a> {
269 FrameElementLocator {
270 frame_locator: self.frame_locator.clone(),
271 selector: Selector::Nth {
272 base: Box::new(self.selector.clone()),
273 index: 0,
274 },
275 options: self.options.clone(),
276 }
277 }
278
279 #[must_use]
281 pub fn last(&self) -> FrameElementLocator<'a> {
282 FrameElementLocator {
283 frame_locator: self.frame_locator.clone(),
284 selector: Selector::Nth {
285 base: Box::new(self.selector.clone()),
286 index: -1,
287 },
288 options: self.options.clone(),
289 }
290 }
291
292 #[must_use]
294 pub fn nth(&self, index: i32) -> FrameElementLocator<'a> {
295 FrameElementLocator {
296 frame_locator: self.frame_locator.clone(),
297 selector: Selector::Nth {
298 base: Box::new(self.selector.clone()),
299 index,
300 },
301 options: self.options.clone(),
302 }
303 }
304
305 pub fn frame_locator(&self) -> &FrameLocator<'a> {
307 &self.frame_locator
308 }
309
310 pub fn selector(&self) -> &Selector {
312 &self.selector
313 }
314
315 pub(crate) fn options(&self) -> &LocatorOptions {
317 &self.options
318 }
319
320 fn to_js_expression(&self) -> String {
322 let frame_access = self.frame_locator.to_js_frame_access();
323 let element_selector = self.selector.to_js_expression();
324
325 format!(
326 r"(function() {{
327 const frameDoc = {frame_access};
328 if (!frameDoc) return {{ found: false, count: 0, error: 'Frame not found or not accessible' }};
329
330 // Override document for the selector expression
331 const originalDocument = document;
332 try {{
333 // Create a modified expression that uses frameDoc instead of document
334 const elements = (function() {{
335 const document = frameDoc;
336 return Array.from({element_selector});
337 }})();
338 return elements;
339 }} catch (e) {{
340 return [];
341 }}
342 }})()"
343 )
344 }
345}
346
347#[derive(Debug)]
349pub struct FrameRoleLocatorBuilder<'a> {
350 frame_locator: FrameLocator<'a>,
351 role: AriaRole,
352 name: Option<String>,
353}
354
355impl<'a> FrameRoleLocatorBuilder<'a> {
356 fn new(frame_locator: FrameLocator<'a>, role: AriaRole) -> Self {
357 Self {
358 frame_locator,
359 role,
360 name: None,
361 }
362 }
363
364 #[must_use]
366 pub fn with_name(mut self, name: impl Into<String>) -> Self {
367 self.name = Some(name.into());
368 self
369 }
370
371 pub fn build(self) -> FrameElementLocator<'a> {
373 FrameElementLocator::new(
374 self.frame_locator,
375 Selector::Role {
376 role: self.role,
377 name: self.name,
378 },
379 )
380 }
381}
382
383impl<'a> From<FrameRoleLocatorBuilder<'a>> for FrameElementLocator<'a> {
384 fn from(builder: FrameRoleLocatorBuilder<'a>) -> Self {
385 builder.build()
386 }
387}
388
389