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)]
46pub struct FrameLocator<'a> {
47 page: &'a Page,
49 frame_selector: String,
51 parent_selectors: Vec<String>,
53 timeout: Duration,
55}
56
57impl<'a> FrameLocator<'a> {
58 pub(crate) fn new(page: &'a Page, selector: impl Into<String>) -> Self {
60 Self {
61 page,
62 frame_selector: selector.into(),
63 parent_selectors: Vec::new(),
64 timeout: DEFAULT_TIMEOUT,
65 }
66 }
67
68 fn with_parent(
70 page: &'a Page,
71 frame_selector: String,
72 mut parent_selectors: Vec<String>,
73 parent_selector: String,
74 ) -> Self {
75 parent_selectors.push(parent_selector);
76 Self {
77 page,
78 frame_selector,
79 parent_selectors,
80 timeout: DEFAULT_TIMEOUT,
81 }
82 }
83
84 #[must_use]
86 pub fn timeout(mut self, timeout: Duration) -> Self {
87 self.timeout = timeout;
88 self
89 }
90
91 pub fn page(&self) -> &'a Page {
93 self.page
94 }
95
96 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
105 FrameElementLocator::new(self.clone(), Selector::Css(selector.into()))
106 }
107
108 pub fn get_by_text(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
110 FrameElementLocator::new(
111 self.clone(),
112 Selector::Text {
113 text: text.into(),
114 exact: false,
115 },
116 )
117 }
118
119 pub fn get_by_text_exact(&self, text: impl Into<String>) -> FrameElementLocator<'a> {
121 FrameElementLocator::new(
122 self.clone(),
123 Selector::Text {
124 text: text.into(),
125 exact: true,
126 },
127 )
128 }
129
130 pub fn get_by_role(&self, role: AriaRole) -> FrameRoleLocatorBuilder<'a> {
132 FrameRoleLocatorBuilder::new(self.clone(), role)
133 }
134
135 pub fn get_by_test_id(&self, test_id: impl Into<String>) -> FrameElementLocator<'a> {
137 FrameElementLocator::new(self.clone(), Selector::TestId(test_id.into()))
138 }
139
140 pub fn get_by_label(&self, label: impl Into<String>) -> FrameElementLocator<'a> {
142 FrameElementLocator::new(self.clone(), Selector::Label(label.into()))
143 }
144
145 pub fn get_by_placeholder(&self, placeholder: impl Into<String>) -> FrameElementLocator<'a> {
147 FrameElementLocator::new(self.clone(), Selector::Placeholder(placeholder.into()))
148 }
149
150 pub fn frame_locator(&self, selector: impl Into<String>) -> FrameLocator<'a> {
163 FrameLocator::with_parent(
164 self.page,
165 selector.into(),
166 self.parent_selectors.clone(),
167 self.frame_selector.clone(),
168 )
169 }
170
171 pub fn selector(&self) -> &str {
173 &self.frame_selector
174 }
175
176 pub fn parent_selectors(&self) -> &[String] {
178 &self.parent_selectors
179 }
180
181 pub(crate) fn to_js_frame_access(&self) -> String {
183 let mut js = String::new();
184
185 js.push_str("(function() {\n");
187 js.push_str(" let doc = document;\n");
188
189 for parent_selector in &self.parent_selectors {
191 js.push_str(&format!(
192 " const parent = doc.querySelector({});\n",
193 super::locator::selector::js_string_literal(parent_selector)
194 ));
195 js.push_str(" if (!parent || !parent.contentDocument) return null;\n");
196 js.push_str(" doc = parent.contentDocument;\n");
197 }
198
199 js.push_str(&format!(
201 " const frame = doc.querySelector({});\n",
202 super::locator::selector::js_string_literal(&self.frame_selector)
203 ));
204 js.push_str(" if (!frame || !frame.contentDocument) return null;\n");
205 js.push_str(" return frame.contentDocument;\n");
206 js.push_str("})()");
207 js
208 }
209}
210
211#[derive(Debug, Clone)]
216pub struct FrameElementLocator<'a> {
217 frame_locator: FrameLocator<'a>,
219 selector: Selector,
221 options: LocatorOptions,
223}
224
225impl<'a> FrameElementLocator<'a> {
226 fn new(frame_locator: FrameLocator<'a>, selector: Selector) -> Self {
228 Self {
229 frame_locator,
230 selector,
231 options: LocatorOptions::default(),
232 }
233 }
234
235 #[must_use]
237 pub fn timeout(mut self, timeout: Duration) -> Self {
238 self.options.timeout = timeout;
239 self
240 }
241
242 #[must_use]
244 pub fn locator(&self, selector: impl Into<String>) -> FrameElementLocator<'a> {
245 FrameElementLocator {
246 frame_locator: self.frame_locator.clone(),
247 selector: Selector::Chained(
248 Box::new(self.selector.clone()),
249 Box::new(Selector::Css(selector.into())),
250 ),
251 options: self.options.clone(),
252 }
253 }
254
255 #[must_use]
257 pub fn first(&self) -> FrameElementLocator<'a> {
258 FrameElementLocator {
259 frame_locator: self.frame_locator.clone(),
260 selector: Selector::Nth {
261 base: Box::new(self.selector.clone()),
262 index: 0,
263 },
264 options: self.options.clone(),
265 }
266 }
267
268 #[must_use]
270 pub fn last(&self) -> FrameElementLocator<'a> {
271 FrameElementLocator {
272 frame_locator: self.frame_locator.clone(),
273 selector: Selector::Nth {
274 base: Box::new(self.selector.clone()),
275 index: -1,
276 },
277 options: self.options.clone(),
278 }
279 }
280
281 #[must_use]
283 pub fn nth(&self, index: i32) -> FrameElementLocator<'a> {
284 FrameElementLocator {
285 frame_locator: self.frame_locator.clone(),
286 selector: Selector::Nth {
287 base: Box::new(self.selector.clone()),
288 index,
289 },
290 options: self.options.clone(),
291 }
292 }
293
294 pub fn frame_locator(&self) -> &FrameLocator<'a> {
296 &self.frame_locator
297 }
298
299 pub fn selector(&self) -> &Selector {
301 &self.selector
302 }
303
304 pub(crate) fn options(&self) -> &LocatorOptions {
306 &self.options
307 }
308
309 fn to_js_expression(&self) -> String {
311 let frame_access = self.frame_locator.to_js_frame_access();
312 let element_selector = self.selector.to_js_expression();
313
314 format!(
315 r"(function() {{
316 const frameDoc = {frame_access};
317 if (!frameDoc) return {{ found: false, count: 0, error: 'Frame not found or not accessible' }};
318
319 // Override document for the selector expression
320 const originalDocument = document;
321 try {{
322 // Create a modified expression that uses frameDoc instead of document
323 const elements = (function() {{
324 const document = frameDoc;
325 return Array.from({element_selector});
326 }})();
327 return elements;
328 }} catch (e) {{
329 return [];
330 }}
331 }})()"
332 )
333 }
334}
335
336#[derive(Debug)]
338pub struct FrameRoleLocatorBuilder<'a> {
339 frame_locator: FrameLocator<'a>,
340 role: AriaRole,
341 name: Option<String>,
342}
343
344impl<'a> FrameRoleLocatorBuilder<'a> {
345 fn new(frame_locator: FrameLocator<'a>, role: AriaRole) -> Self {
346 Self {
347 frame_locator,
348 role,
349 name: None,
350 }
351 }
352
353 #[must_use]
355 pub fn with_name(mut self, name: impl Into<String>) -> Self {
356 self.name = Some(name.into());
357 self
358 }
359
360 pub fn build(self) -> FrameElementLocator<'a> {
362 FrameElementLocator::new(
363 self.frame_locator,
364 Selector::Role {
365 role: self.role,
366 name: self.name,
367 },
368 )
369 }
370}
371
372impl<'a> From<FrameRoleLocatorBuilder<'a>> for FrameElementLocator<'a> {
373 fn from(builder: FrameRoleLocatorBuilder<'a>) -> Self {
374 builder.build()
375 }
376}
377
378