1use ferridriver::locator::Locator;
4use ferridriver::options::{FilterOptions, LocatorLike};
5use rquickjs::JsLifetime;
6use rquickjs::class::Trace;
7use rquickjs::function::Opt;
8
9use crate::bindings::convert::FerriResultExt;
10
11pub(crate) struct ParsedLocatorOptions {
17 pub has_text: Option<String>,
18 pub has_not_text: Option<String>,
19 pub has: Option<LocatorLike>,
20 pub has_not: Option<LocatorLike>,
21 pub visible: Option<bool>,
22}
23
24fn get_string<'js>(obj: &rquickjs::Object<'js>, key: &str) -> rquickjs::Result<Option<String>> {
26 let v: rquickjs::Value<'js> = obj.get(key)?;
27 if v.is_undefined() || v.is_null() {
28 return Ok(None);
29 }
30 match v.as_string() {
31 Some(s) => Ok(Some(s.to_string()?)),
32 None => Err(rquickjs::Error::new_from_js_message(
33 "filter options",
34 "field",
35 format!("{key}: expected string"),
36 )),
37 }
38}
39
40fn get_locator_like<'js>(
45 ctx: &rquickjs::Ctx<'js>,
46 obj: &rquickjs::Object<'js>,
47 key: &str,
48) -> rquickjs::Result<Option<LocatorLike>> {
49 let v: rquickjs::Value<'js> = obj.get(key)?;
50 if v.is_undefined() || v.is_null() {
51 return Ok(None);
52 }
53 if let Ok(class) = rquickjs::Class::<LocatorJs>::from_value(&v) {
57 let inner = class.borrow();
58 return Ok(Some(LocatorLike::Locator(inner.inner.clone())));
59 }
60 let _ = ctx;
63 if let Some(obj) = v.as_object() {
64 if let Some(sel) = get_string(obj, "selector")? {
65 return Ok(Some(LocatorLike::Selector(sel)));
66 }
67 }
68 Err(rquickjs::Error::new_from_js_message(
69 "filter options",
70 "field",
71 format!("{key}: expected Locator instance or {{ selector: string }}"),
72 ))
73}
74
75fn parse_highlight_style(
82 options: Opt<rquickjs::Value<'_>>,
83) -> rquickjs::Result<Option<ferridriver::options::HighlightStyle>> {
84 let Some(val) = options.0 else {
85 return Ok(None);
86 };
87 let Some(obj) = val.as_object() else {
88 return Ok(None);
89 };
90 let style: rquickjs::Value<'_> = obj.get("style")?;
91 if style.is_undefined() || style.is_null() {
92 return Ok(None);
93 }
94 if let Some(s) = style.as_string() {
95 return Ok(Some(ferridriver::options::HighlightStyle::Css(s.to_string()?)));
96 }
97 if let Some(map) = style.as_object() {
98 let mut entries = Vec::new();
99 for key_res in map.keys::<String>() {
100 let key = key_res?;
101 let v: rquickjs::Value<'_> = map.get(&key)?;
102 let rendered = if let Some(s) = v.as_string() {
103 s.to_string()?
104 } else if let Some(n) = v.as_number() {
105 if n.fract() == 0.0 && n.is_finite() {
108 format!("{}", n as i64)
109 } else {
110 n.to_string()
111 }
112 } else {
113 continue;
114 };
115 entries.push((key, rendered));
116 }
117 return Ok(Some(ferridriver::options::HighlightStyle::Object(entries)));
118 }
119 Ok(None)
120}
121
122fn get_bool<'js>(obj: &rquickjs::Object<'js>, key: &str) -> rquickjs::Result<Option<bool>> {
123 let v: rquickjs::Value<'js> = obj.get(key)?;
124 if v.is_undefined() || v.is_null() {
125 return Ok(None);
126 }
127 v.as_bool()
128 .map(Some)
129 .ok_or_else(|| rquickjs::Error::new_from_js_message("filter options", "field", format!("{key}: expected boolean")))
130}
131
132pub(crate) fn parse_locator_options_public<'js>(
133 ctx: &rquickjs::Ctx<'js>,
134 value: Opt<rquickjs::Value<'js>>,
135 allow_visible: bool,
136) -> rquickjs::Result<ParsedLocatorOptions> {
137 let Some(val) = value.0 else {
138 return Ok(ParsedLocatorOptions {
139 has_text: None,
140 has_not_text: None,
141 has: None,
142 has_not: None,
143 visible: None,
144 });
145 };
146 if val.is_undefined() || val.is_null() {
147 return Ok(ParsedLocatorOptions {
148 has_text: None,
149 has_not_text: None,
150 has: None,
151 has_not: None,
152 visible: None,
153 });
154 }
155 let obj = val
156 .as_object()
157 .ok_or_else(|| rquickjs::Error::new_from_js_message("locator options", "", "expected an options object"))?;
158 Ok(ParsedLocatorOptions {
159 has_text: get_string(obj, "hasText")?,
160 has_not_text: get_string(obj, "hasNotText")?,
161 has: get_locator_like(ctx, obj, "has")?,
162 has_not: get_locator_like(ctx, obj, "hasNot")?,
163 visible: if allow_visible { get_bool(obj, "visible")? } else { None },
164 })
165}
166
167pub(crate) fn is_empty_filter(opts: &FilterOptions) -> bool {
171 opts.has_text.is_none()
172 && opts.has_not_text.is_none()
173 && opts.has.is_none()
174 && opts.has_not.is_none()
175 && opts.visible.is_none()
176}
177
178#[derive(JsLifetime, Trace)]
179#[rquickjs::class(rename = "Locator")]
180pub struct LocatorJs {
181 #[qjs(skip_trace)]
182 inner: Locator,
183}
184
185impl LocatorJs {
186 #[must_use]
187 pub fn new(inner: Locator) -> Self {
188 Self { inner }
189 }
190
191 #[must_use]
195 pub fn inner_ref(&self) -> &Locator {
196 &self.inner
197 }
198}
199
200#[rquickjs::methods]
201impl LocatorJs {
202 #[qjs(get, rename = "selector")]
206 pub fn selector(&self) -> String {
207 self.inner.selector().to_string()
208 }
209
210 #[qjs(get, rename = "isStrict")]
212 pub fn is_strict(&self) -> bool {
213 self.inner.is_strict()
214 }
215
216 #[qjs(rename = "setStrict")]
218 pub fn set_strict(&self, strict: bool) -> LocatorJs {
219 LocatorJs::new(self.inner.strict(strict))
220 }
221
222 #[qjs(rename = "selectText")]
224 pub async fn select_text(&self) -> rquickjs::Result<()> {
225 self.inner.select_text().await.into_js()
226 }
227
228 #[qjs(rename = "rightClick")]
230 pub async fn right_click(&self) -> rquickjs::Result<()> {
231 self.inner.right_click().await.into_js()
232 }
233
234 #[qjs(rename = "boundingBox")]
236 pub async fn bounding_box<'js>(&self, ctx: rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
237 match self.inner.bounding_box().await.into_js()? {
238 None => Ok(rquickjs::Value::new_null(ctx)),
239 Some(b) => {
240 let obj = rquickjs::Object::new(ctx.clone())?;
241 obj.set("x", b.x)?;
242 obj.set("y", b.y)?;
243 obj.set("width", b.width)?;
244 obj.set("height", b.height)?;
245 Ok(obj.into_value())
246 },
247 }
248 }
249
250 #[qjs(rename = "locator")]
261 pub fn locator<'js>(
262 &self,
263 ctx: rquickjs::Ctx<'js>,
264 selector_or_locator: rquickjs::Value<'js>,
265 options: Opt<rquickjs::Value<'js>>,
266 ) -> rquickjs::Result<LocatorJs> {
267 let like: ferridriver::options::LocatorLike = if let Some(s) = selector_or_locator.as_string() {
271 ferridriver::options::LocatorLike::Selector(s.to_string()?)
272 } else if let Ok(class) = rquickjs::Class::<LocatorJs>::from_value(&selector_or_locator) {
273 ferridriver::options::LocatorLike::Locator(class.borrow().inner.clone())
274 } else if let Some(obj) = selector_or_locator.as_object() {
275 match get_string(obj, "selector")? {
276 Some(sel) => ferridriver::options::LocatorLike::Selector(sel),
277 None => {
278 return Err(rquickjs::Error::new_from_js_message(
279 "Locator",
280 "locator",
281 "expected a selector string or Locator instance",
282 ));
283 },
284 }
285 } else {
286 return Err(rquickjs::Error::new_from_js_message(
287 "Locator",
288 "locator",
289 "expected a selector string or Locator instance",
290 ));
291 };
292
293 let opts = parse_locator_options_public(&ctx, options, false)?;
298 let filter_opts = ferridriver::options::FilterOptions {
299 has_text: opts.has_text,
300 has_not_text: opts.has_not_text,
301 has: opts.has,
302 has_not: opts.has_not,
303 visible: opts.visible,
304 };
305 let filter = if is_empty_filter(&filter_opts) {
306 None
307 } else {
308 Some(filter_opts)
309 };
310 Ok(LocatorJs::new(self.inner.locator(like, filter)))
311 }
312
313 #[qjs(rename = "filter")]
317 pub fn filter<'js>(
318 &self,
319 ctx: rquickjs::Ctx<'js>,
320 options: Opt<rquickjs::Value<'js>>,
321 ) -> rquickjs::Result<LocatorJs> {
322 let parsed = parse_locator_options_public(&ctx, options, true)?;
323 let opts = FilterOptions {
324 has_text: parsed.has_text,
325 has_not_text: parsed.has_not_text,
326 has: parsed.has,
327 has_not: parsed.has_not,
328 visible: parsed.visible,
329 };
330 Ok(LocatorJs::new(self.inner.filter(&opts)))
331 }
332
333 #[qjs(rename = "and")]
338 pub fn and<'js>(&self, ctx: rquickjs::Ctx<'js>, other: rquickjs::Value<'js>) -> rquickjs::Result<LocatorJs> {
339 let _ = ctx;
340 let class = rquickjs::Class::<LocatorJs>::from_value(&other)
341 .map_err(|_| rquickjs::Error::new_from_js_message("Locator", "and", "expected a Locator instance"))?;
342 Ok(LocatorJs::new(self.inner.and(&class.borrow().inner)))
343 }
344
345 #[qjs(rename = "or")]
348 pub fn or<'js>(&self, ctx: rquickjs::Ctx<'js>, other: rquickjs::Value<'js>) -> rquickjs::Result<LocatorJs> {
349 let _ = ctx;
350 let class = rquickjs::Class::<LocatorJs>::from_value(&other)
351 .map_err(|_| rquickjs::Error::new_from_js_message("Locator", "or", "expected a Locator instance"))?;
352 Ok(LocatorJs::new(self.inner.or(&class.borrow().inner)))
353 }
354
355 #[qjs(rename = "elementHandle")]
358 pub async fn element_handle(&self) -> rquickjs::Result<crate::bindings::element_handle::ElementHandleJs> {
359 let inner = self.inner.element_handle().await.into_js()?;
360 Ok(crate::bindings::element_handle::ElementHandleJs::new(inner))
361 }
362
363 #[qjs(rename = "elementHandles")]
365 pub async fn element_handles(&self) -> rquickjs::Result<Vec<crate::bindings::element_handle::ElementHandleJs>> {
366 let inner = self.inner.element_handles().await.into_js()?;
367 Ok(
368 inner
369 .into_iter()
370 .map(crate::bindings::element_handle::ElementHandleJs::new)
371 .collect(),
372 )
373 }
374
375 #[qjs(rename = "getByRole")]
376 pub fn get_by_role(
377 &self,
378 role: String,
379 options: rquickjs::function::Opt<rquickjs::Value<'_>>,
380 ) -> rquickjs::Result<LocatorJs> {
381 let opts = crate::bindings::page::parse_role_options(options)?;
382 Ok(LocatorJs::new(self.inner.get_by_role(&role, &opts)))
383 }
384
385 #[qjs(rename = "getByText")]
386 pub fn get_by_text(
387 &self,
388 text: rquickjs::Value<'_>,
389 options: rquickjs::function::Opt<rquickjs::Value<'_>>,
390 ) -> rquickjs::Result<LocatorJs> {
391 let t = crate::bindings::page::string_or_regex_from_js(text)?;
392 let opts = crate::bindings::page::parse_text_options(options);
393 Ok(LocatorJs::new(self.inner.get_by_text(&t, &opts)))
394 }
395
396 #[qjs(rename = "getByLabel")]
397 pub fn get_by_label(
398 &self,
399 text: rquickjs::Value<'_>,
400 options: rquickjs::function::Opt<rquickjs::Value<'_>>,
401 ) -> rquickjs::Result<LocatorJs> {
402 let t = crate::bindings::page::string_or_regex_from_js(text)?;
403 let opts = crate::bindings::page::parse_text_options(options);
404 Ok(LocatorJs::new(self.inner.get_by_label(&t, &opts)))
405 }
406
407 #[qjs(rename = "getByPlaceholder")]
408 pub fn get_by_placeholder(
409 &self,
410 text: rquickjs::Value<'_>,
411 options: rquickjs::function::Opt<rquickjs::Value<'_>>,
412 ) -> rquickjs::Result<LocatorJs> {
413 let t = crate::bindings::page::string_or_regex_from_js(text)?;
414 let opts = crate::bindings::page::parse_text_options(options);
415 Ok(LocatorJs::new(self.inner.get_by_placeholder(&t, &opts)))
416 }
417
418 #[qjs(rename = "getByAltText")]
419 pub fn get_by_alt_text(
420 &self,
421 text: rquickjs::Value<'_>,
422 options: rquickjs::function::Opt<rquickjs::Value<'_>>,
423 ) -> rquickjs::Result<LocatorJs> {
424 let t = crate::bindings::page::string_or_regex_from_js(text)?;
425 let opts = crate::bindings::page::parse_text_options(options);
426 Ok(LocatorJs::new(self.inner.get_by_alt_text(&t, &opts)))
427 }
428
429 #[qjs(rename = "getByTitle")]
430 pub fn get_by_title(
431 &self,
432 text: rquickjs::Value<'_>,
433 options: rquickjs::function::Opt<rquickjs::Value<'_>>,
434 ) -> rquickjs::Result<LocatorJs> {
435 let t = crate::bindings::page::string_or_regex_from_js(text)?;
436 let opts = crate::bindings::page::parse_text_options(options);
437 Ok(LocatorJs::new(self.inner.get_by_title(&t, &opts)))
438 }
439
440 #[qjs(rename = "getByTestId")]
441 pub fn get_by_test_id(&self, test_id: rquickjs::Value<'_>) -> rquickjs::Result<LocatorJs> {
442 let t = crate::bindings::page::string_or_regex_from_js(test_id)?;
443 Ok(LocatorJs::new(self.inner.get_by_test_id(&t)))
444 }
445
446 #[qjs(rename = "contentFrame")]
448 pub fn content_frame(&self) -> crate::bindings::frame_locator::FrameLocatorJs {
449 crate::bindings::frame_locator::FrameLocatorJs::new(self.inner.content_frame())
450 }
451
452 #[qjs(rename = "frameLocator")]
454 pub fn frame_locator(&self, selector: String) -> crate::bindings::frame_locator::FrameLocatorJs {
455 crate::bindings::frame_locator::FrameLocatorJs::new(self.inner.frame_locator(&selector))
456 }
457
458 #[qjs(rename = "page")]
462 pub fn page(&self, ctx: rquickjs::Ctx<'_>) -> crate::bindings::page::PageJs {
463 crate::bindings::page::pagejs_for_ctx(&ctx, self.inner.page().clone())
464 }
465
466 #[qjs(rename = "first")]
467 pub fn first(&self) -> LocatorJs {
468 LocatorJs::new(self.inner.first())
469 }
470
471 #[qjs(rename = "last")]
472 pub fn last(&self) -> LocatorJs {
473 LocatorJs::new(self.inner.last())
474 }
475
476 #[qjs(rename = "nth")]
477 pub fn nth(&self, index: i32) -> LocatorJs {
478 LocatorJs::new(self.inner.nth(index))
479 }
480
481 #[qjs(rename = "click")]
484 pub async fn click<'js>(
485 &self,
486 ctx: rquickjs::Ctx<'js>,
487 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
488 ) -> rquickjs::Result<()> {
489 let opts = crate::bindings::convert::parse_click_options(&ctx, options)?;
490 self.inner.click(opts).await.into_js()
491 }
492
493 #[qjs(rename = "dblclick")]
494 pub async fn dblclick<'js>(
495 &self,
496 ctx: rquickjs::Ctx<'js>,
497 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
498 ) -> rquickjs::Result<()> {
499 let opts = crate::bindings::convert::parse_dblclick_options(&ctx, options)?;
500 self.inner.dblclick(opts).await.into_js()
501 }
502
503 #[qjs(rename = "fill")]
504 pub async fn fill<'js>(
505 &self,
506 ctx: rquickjs::Ctx<'js>,
507 value: String,
508 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
509 ) -> rquickjs::Result<()> {
510 let opts = crate::bindings::convert::parse_fill_options(&ctx, options)?;
511 self.inner.fill(&value, opts).await.into_js()
512 }
513
514 #[qjs(rename = "clear")]
515 pub async fn clear(&self) -> rquickjs::Result<()> {
516 self.inner.clear().await.into_js()
517 }
518
519 #[qjs(rename = "highlight")]
527 pub async fn highlight(
528 &self,
529 options: Opt<rquickjs::Value<'_>>,
530 ) -> rquickjs::Result<crate::bindings::disposable::DisposableJs> {
531 let style = parse_highlight_style(options)?;
532 let disposable = self.inner.highlight(style).await.into_js()?;
533 Ok(crate::bindings::disposable::DisposableJs::new(disposable))
534 }
535
536 #[qjs(rename = "hideHighlight")]
539 pub async fn hide_highlight(&self) -> rquickjs::Result<()> {
540 self.inner.hide_highlight().await.into_js()
541 }
542
543 #[qjs(rename = "type")]
544 pub async fn type_<'js>(
545 &self,
546 ctx: rquickjs::Ctx<'js>,
547 text: String,
548 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
549 ) -> rquickjs::Result<()> {
550 let opts = crate::bindings::convert::parse_type_options(&ctx, options)?;
551 self.inner.r#type(&text, opts).await.into_js()
552 }
553
554 #[qjs(rename = "pressSequentially")]
555 pub async fn press_sequentially<'js>(
556 &self,
557 ctx: rquickjs::Ctx<'js>,
558 text: String,
559 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
560 ) -> rquickjs::Result<()> {
561 let opts = crate::bindings::convert::parse_type_options(&ctx, options)?;
562 self.inner.press_sequentially(&text, opts).await.into_js()
563 }
564
565 #[qjs(rename = "press")]
566 pub async fn press<'js>(
567 &self,
568 ctx: rquickjs::Ctx<'js>,
569 key: String,
570 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
571 ) -> rquickjs::Result<()> {
572 let opts = crate::bindings::convert::parse_press_options(&ctx, options)?;
573 self.inner.press(&key, opts).await.into_js()
574 }
575
576 #[qjs(rename = "hover")]
577 pub async fn hover<'js>(
578 &self,
579 ctx: rquickjs::Ctx<'js>,
580 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
581 ) -> rquickjs::Result<()> {
582 let opts = crate::bindings::convert::parse_hover_options(&ctx, options)?;
583 self.inner.hover(opts).await.into_js()
584 }
585
586 #[qjs(rename = "tap")]
587 pub async fn tap<'js>(
588 &self,
589 ctx: rquickjs::Ctx<'js>,
590 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
591 ) -> rquickjs::Result<()> {
592 let opts = crate::bindings::convert::parse_tap_options(&ctx, options)?;
593 self.inner.tap(opts).await.into_js()
594 }
595
596 #[qjs(rename = "focus")]
597 pub async fn focus(&self) -> rquickjs::Result<()> {
598 self.inner.focus().await.into_js()
599 }
600
601 #[qjs(rename = "blur")]
602 pub async fn blur(&self) -> rquickjs::Result<()> {
603 self.inner.blur().await.into_js()
604 }
605
606 #[qjs(rename = "check")]
607 pub async fn check<'js>(
608 &self,
609 ctx: rquickjs::Ctx<'js>,
610 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
611 ) -> rquickjs::Result<()> {
612 let opts = crate::bindings::convert::parse_check_options(&ctx, options)?;
613 self.inner.check(opts).await.into_js()
614 }
615
616 #[qjs(rename = "uncheck")]
617 pub async fn uncheck<'js>(
618 &self,
619 ctx: rquickjs::Ctx<'js>,
620 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
621 ) -> rquickjs::Result<()> {
622 let opts = crate::bindings::convert::parse_check_options(&ctx, options)?;
623 self.inner.uncheck(opts).await.into_js()
624 }
625
626 #[qjs(rename = "setChecked")]
627 pub async fn set_checked<'js>(
628 &self,
629 ctx: rquickjs::Ctx<'js>,
630 checked: bool,
631 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
632 ) -> rquickjs::Result<()> {
633 let opts = crate::bindings::convert::parse_check_options(&ctx, options)?;
634 self.inner.set_checked(checked, opts).await.into_js()
635 }
636
637 #[qjs(rename = "selectOption")]
638 pub async fn select_option<'js>(
639 &self,
640 ctx: rquickjs::Ctx<'js>,
641 values: rquickjs::Value<'js>,
642 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
643 ) -> rquickjs::Result<Vec<String>> {
644 let values = crate::bindings::convert::parse_select_option_values(&ctx, values)?;
645 let opts = crate::bindings::convert::parse_select_option_options(&ctx, options)?;
646 self.inner.select_option(values, opts).await.into_js()
647 }
648
649 #[qjs(rename = "setInputFiles")]
653 pub async fn set_input_files<'js>(
654 &self,
655 ctx: rquickjs::Ctx<'js>,
656 files: rquickjs::Value<'js>,
657 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
658 ) -> rquickjs::Result<()> {
659 let files = crate::bindings::convert::parse_input_files(&ctx, files)?;
660 let opts = crate::bindings::convert::parse_set_input_files_options(&ctx, options)?;
661 self.inner.set_input_files(files, opts).await.into_js()
662 }
663
664 #[qjs(rename = "drop")]
669 pub async fn drop<'js>(
670 &self,
671 ctx: rquickjs::Ctx<'js>,
672 payload: rquickjs::Value<'js>,
673 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
674 ) -> rquickjs::Result<()> {
675 let payload = crate::bindings::convert::parse_drop_payload(&ctx, payload)?;
676 let opts = crate::bindings::convert::parse_drop_options(&ctx, options)?;
677 self.inner.drop(payload, opts).await.into_js()
678 }
679
680 #[qjs(rename = "scrollIntoViewIfNeeded")]
681 pub async fn scroll_into_view_if_needed(&self) -> rquickjs::Result<()> {
682 self.inner.scroll_into_view_if_needed().await.into_js()
683 }
684
685 #[qjs(rename = "waitFor")]
689 pub async fn wait_for<'js>(
690 &self,
691 ctx: rquickjs::Ctx<'js>,
692 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
693 ) -> rquickjs::Result<()> {
694 #[derive(serde::Deserialize, Default)]
695 #[serde(rename_all = "camelCase", default)]
696 struct JsWaitOpts {
697 state: Option<String>,
698 timeout: Option<u64>,
699 }
700 let parsed: JsWaitOpts = match options.0 {
701 Some(v) if !v.is_undefined() && !v.is_null() => crate::bindings::convert::serde_from_js(&ctx, v)?,
702 _ => JsWaitOpts::default(),
703 };
704 self
705 .inner
706 .wait_for(ferridriver::options::WaitOptions {
707 state: parsed.state,
708 timeout: parsed.timeout,
709 })
710 .await
711 .into_js()
712 }
713
714 #[qjs(rename = "dispatchEvent")]
717 pub async fn dispatch_event<'js>(
718 &self,
719 ctx: rquickjs::Ctx<'js>,
720 event_type: String,
721 event_init: rquickjs::function::Opt<rquickjs::Value<'js>>,
722 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
723 ) -> rquickjs::Result<()> {
724 let init_json = match event_init.0 {
725 Some(v) if !v.is_undefined() && !v.is_null() => {
726 Some(crate::bindings::convert::serde_from_js::<serde_json::Value>(&ctx, v)?)
727 },
728 _ => None,
729 };
730 let opts = crate::bindings::convert::parse_dispatch_event_options(&ctx, options)?;
731 self.inner.dispatch_event(&event_type, init_json, opts).await.into_js()
732 }
733
734 #[qjs(rename = "count")]
737 pub async fn count(&self) -> rquickjs::Result<i32> {
738 self
739 .inner
740 .count()
741 .await
742 .into_js()
743 .map(|c| i32::try_from(c).unwrap_or(i32::MAX))
744 }
745
746 #[qjs(rename = "normalize")]
750 pub async fn normalize(&self) -> rquickjs::Result<LocatorJs> {
751 self.inner.normalize().await.into_js().map(LocatorJs::new)
752 }
753
754 #[qjs(rename = "screenshot")]
757 pub async fn screenshot(&self) -> rquickjs::Result<Vec<u8>> {
758 self.inner.screenshot().await.into_js()
759 }
760
761 #[qjs(rename = "textContent")]
762 pub async fn text_content(&self) -> rquickjs::Result<Option<String>> {
763 self.inner.text_content().await.into_js()
764 }
765
766 #[qjs(rename = "innerText")]
767 pub async fn inner_text(&self) -> rquickjs::Result<String> {
768 self.inner.inner_text().await.into_js()
769 }
770
771 #[qjs(rename = "innerHTML")]
772 pub async fn inner_html(&self) -> rquickjs::Result<String> {
773 self.inner.inner_html().await.into_js()
774 }
775
776 #[qjs(rename = "inputValue")]
777 pub async fn input_value(&self) -> rquickjs::Result<String> {
778 self.inner.input_value().await.into_js()
779 }
780
781 #[qjs(rename = "ariaSnapshot")]
784 pub async fn aria_snapshot<'js>(
785 &self,
786 ctx: rquickjs::Ctx<'js>,
787 options: rquickjs::function::Opt<rquickjs::Value<'js>>,
788 ) -> rquickjs::Result<String> {
789 let core_opts = match options.0 {
790 Some(v) if !v.is_undefined() && !v.is_null() => {
791 #[derive(serde::Deserialize, Default)]
792 #[serde(rename_all = "camelCase", default)]
793 struct JsAria {
794 mode: Option<String>,
795 depth: Option<i32>,
796 timeout: Option<u64>,
797 }
798 let p: JsAria = crate::bindings::convert::serde_from_js(&ctx, v)?;
799 ferridriver::options::AriaSnapshotOptions {
800 mode: Some(ferridriver::options::AriaSnapshotMode::from_opt_str(p.mode.as_deref())),
801 depth: p.depth,
802 timeout: p.timeout,
803 }
804 },
805 _ => ferridriver::options::AriaSnapshotOptions::default(),
806 };
807 self.inner.aria_snapshot(core_opts).await.into_js()
808 }
809
810 #[qjs(rename = "getAttribute")]
811 pub async fn get_attribute(&self, name: String) -> rquickjs::Result<Option<String>> {
812 self.inner.get_attribute(&name).await.into_js()
813 }
814
815 #[qjs(rename = "isVisible")]
816 pub async fn is_visible(&self) -> rquickjs::Result<bool> {
817 self.inner.is_visible().await.into_js()
818 }
819
820 #[qjs(rename = "isHidden")]
821 pub async fn is_hidden(&self) -> rquickjs::Result<bool> {
822 self.inner.is_hidden().await.into_js()
823 }
824
825 #[qjs(rename = "isEnabled")]
826 pub async fn is_enabled(&self) -> rquickjs::Result<bool> {
827 self.inner.is_enabled().await.into_js()
828 }
829
830 #[qjs(rename = "isDisabled")]
831 pub async fn is_disabled(&self) -> rquickjs::Result<bool> {
832 self.inner.is_disabled().await.into_js()
833 }
834
835 #[qjs(rename = "isChecked")]
836 pub async fn is_checked(&self) -> rquickjs::Result<bool> {
837 self.inner.is_checked().await.into_js()
838 }
839
840 #[qjs(rename = "isEditable")]
841 pub async fn is_editable(&self) -> rquickjs::Result<bool> {
842 self.inner.is_editable().await.into_js()
843 }
844
845 #[qjs(rename = "isAttached")]
846 pub async fn is_attached(&self) -> rquickjs::Result<bool> {
847 self.inner.is_attached().await.into_js()
848 }
849
850 #[qjs(rename = "dragTo")]
861 pub async fn drag_to<'js>(
862 &self,
863 ctx: rquickjs::Ctx<'js>,
864 target: rquickjs::Class<'js, LocatorJs>,
865 options: Opt<rquickjs::Value<'js>>,
866 ) -> rquickjs::Result<()> {
867 let target_inner = target.borrow().inner.clone();
868 let opts = crate::bindings::page::parse_drag_options(&ctx, options)?;
869 self.inner.drag_to(&target_inner, opts).await.into_js()
870 }
871
872 #[qjs(rename = "allTextContents")]
875 pub async fn all_text_contents(&self) -> rquickjs::Result<Vec<String>> {
876 self.inner.all_text_contents().await.into_js()
877 }
878
879 #[qjs(rename = "allInnerTexts")]
880 pub async fn all_inner_texts(&self) -> rquickjs::Result<Vec<String>> {
881 self.inner.all_inner_texts().await.into_js()
882 }
883
884 #[qjs(rename = "evaluate")]
888 pub async fn evaluate<'js>(
889 &self,
890 ctx: rquickjs::Ctx<'js>,
891 page_function: rquickjs::Value<'js>,
892 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
893 ) -> rquickjs::Result<rquickjs::Value<'js>> {
894 let (source, is_fn) = crate::bindings::convert::extract_page_function(&ctx, page_function)?;
895 let serialized = crate::bindings::convert::quickjs_arg_to_serialized(&ctx, arg.0)?;
896 let result = self.inner.evaluate(&source, serialized, is_fn, None).await.into_js()?;
897 crate::bindings::convert::serialized_value_to_quickjs(&ctx, &result)
898 }
899
900 #[qjs(rename = "evaluateHandle")]
902 pub async fn evaluate_handle<'js>(
903 &self,
904 ctx: rquickjs::Ctx<'js>,
905 page_function: rquickjs::Value<'js>,
906 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
907 ) -> rquickjs::Result<crate::bindings::js_handle::JSHandleJs> {
908 let (source, is_fn) = crate::bindings::convert::extract_page_function(&ctx, page_function)?;
909 let serialized = crate::bindings::convert::quickjs_arg_to_serialized(&ctx, arg.0)?;
910 let handle = self
911 .inner
912 .evaluate_handle(&source, serialized, is_fn, None)
913 .await
914 .into_js()?;
915 Ok(crate::bindings::js_handle::JSHandleJs::new(handle))
916 }
917
918 #[qjs(rename = "evaluateAll")]
920 pub async fn evaluate_all<'js>(
921 &self,
922 ctx: rquickjs::Ctx<'js>,
923 page_function: rquickjs::Value<'js>,
924 arg: rquickjs::function::Opt<rquickjs::Value<'js>>,
925 ) -> rquickjs::Result<rquickjs::Value<'js>> {
926 let (source, is_fn) = crate::bindings::convert::extract_page_function(&ctx, page_function)?;
927 let serialized = crate::bindings::convert::quickjs_arg_to_serialized(&ctx, arg.0)?;
928 let result = self.inner.evaluate_all(&source, serialized, is_fn).await.into_js()?;
929 crate::bindings::convert::serialized_value_to_quickjs(&ctx, &result)
930 }
931}