1use std::fmt;
31use std::sync::Arc;
32
33use serde_json::Value;
34use tracing::debug;
35
36use crate::error::{Error, Result};
37use crate::identifiers::{ElementId, FrameId, SessionId, TabId};
38use crate::protocol::{Command, ElementCommand, InputCommand, Request, Response, ScriptCommand};
39
40use super::Window;
41use super::keyboard::Key;
42use super::selector::By;
43
44pub(crate) struct ElementInner {
50 pub id: ElementId,
52
53 pub tab_id: TabId,
55
56 pub frame_id: FrameId,
58
59 pub session_id: SessionId,
61
62 pub window: Option<Window>,
64}
65
66#[derive(Clone)]
85pub struct Element {
86 pub(crate) inner: Arc<ElementInner>,
88}
89
90impl fmt::Debug for Element {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.debug_struct("Element")
97 .field("id", &self.inner.id)
98 .field("tab_id", &self.inner.tab_id)
99 .field("frame_id", &self.inner.frame_id)
100 .finish_non_exhaustive()
101 }
102}
103
104impl Element {
109 pub(crate) fn new(
111 id: ElementId,
112 tab_id: TabId,
113 frame_id: FrameId,
114 session_id: SessionId,
115 window: Option<Window>,
116 ) -> Self {
117 Self {
118 inner: Arc::new(ElementInner {
119 id,
120 tab_id,
121 frame_id,
122 session_id,
123 window,
124 }),
125 }
126 }
127}
128
129impl Element {
134 #[inline]
136 #[must_use]
137 pub fn id(&self) -> &ElementId {
138 &self.inner.id
139 }
140
141 #[inline]
143 #[must_use]
144 pub fn tab_id(&self) -> TabId {
145 self.inner.tab_id
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn frame_id(&self) -> FrameId {
152 self.inner.frame_id
153 }
154}
155
156impl Element {
161 pub async fn click(&self) -> Result<()> {
165 debug!(element_id = %self.inner.id, "Clicking element");
166 self.call_method("click", vec![]).await?;
167 Ok(())
168 }
169
170 pub async fn focus(&self) -> Result<()> {
172 debug!(element_id = %self.inner.id, "Focusing element");
173 self.call_method("focus", vec![]).await?;
174 Ok(())
175 }
176
177 pub async fn blur(&self) -> Result<()> {
179 debug!(element_id = %self.inner.id, "Blurring element");
180 self.call_method("blur", vec![]).await?;
181 Ok(())
182 }
183
184 pub async fn clear(&self) -> Result<()> {
188 debug!(element_id = %self.inner.id, "Clearing element");
189 self.set_property("value", Value::String(String::new()))
190 .await
191 }
192}
193
194impl Element {
199 pub async fn get_text(&self) -> Result<String> {
201 let value = self.get_property("textContent").await?;
202 Ok(value.as_str().unwrap_or("").to_string())
203 }
204
205 pub async fn get_inner_html(&self) -> Result<String> {
207 let value = self.get_property("innerHTML").await?;
208 Ok(value.as_str().unwrap_or("").to_string())
209 }
210
211 pub async fn get_value(&self) -> Result<String> {
213 let value = self.get_property("value").await?;
214 Ok(value.as_str().unwrap_or("").to_string())
215 }
216
217 pub async fn set_value(&self, value: &str) -> Result<()> {
219 self.set_property("value", Value::String(value.to_string()))
220 .await
221 }
222
223 pub async fn get_attribute(&self, name: &str) -> Result<Option<String>> {
227 let result = self
228 .call_method("getAttribute", vec![Value::String(name.to_string())])
229 .await?;
230 Ok(result.as_str().map(|s| s.to_string()))
231 }
232
233 pub async fn is_displayed(&self) -> Result<bool> {
238 let script = r#"
239 const el = arguments[0];
240 const style = window.getComputedStyle(el);
241 if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
242 const rect = el.getBoundingClientRect();
243 return rect.width > 0 && rect.height > 0;
244 "#;
245
246 let command = Command::Script(ScriptCommand::Evaluate {
247 script: script.to_string(),
248 args: vec![serde_json::json!({"elementId": self.inner.id.as_str()})],
249 });
250
251 let response = self.send_command(command).await?;
252
253 let displayed = response
254 .result
255 .as_ref()
256 .and_then(|v| v.get("value"))
257 .and_then(|v| v.as_bool())
258 .unwrap_or(false);
259
260 Ok(displayed)
261 }
262
263 pub async fn is_enabled(&self) -> Result<bool> {
267 let disabled = self.get_property("disabled").await?;
268 Ok(!disabled.as_bool().unwrap_or(false))
269 }
270}
271
272impl Element {
277 pub async fn press(&self, key: Key) -> Result<()> {
291 let (key_str, code, key_code, printable) = key.properties();
292 self.type_key(
293 key_str, code, key_code, printable, false, false, false, false,
294 )
295 .await
296 }
297
298 #[allow(clippy::too_many_arguments)]
315 pub async fn type_key(
316 &self,
317 key: &str,
318 code: &str,
319 key_code: u32,
320 printable: bool,
321 ctrl: bool,
322 shift: bool,
323 alt: bool,
324 meta: bool,
325 ) -> Result<()> {
326 let command = Command::Input(InputCommand::TypeKey {
327 element_id: self.inner.id.clone(),
328 key: key.to_string(),
329 code: code.to_string(),
330 key_code,
331 printable,
332 ctrl,
333 shift,
334 alt,
335 meta,
336 });
337
338 self.send_command(command).await?;
339 Ok(())
340 }
341
342 pub async fn type_char(&self, c: char) -> Result<()> {
346 self.type_text(&c.to_string()).await
347 }
348
349 pub async fn type_text(&self, text: &str) -> Result<()> {
360 debug!(element_id = %self.inner.id, text_len = text.len(), "Typing text");
361
362 let command = Command::Input(InputCommand::TypeText {
363 element_id: self.inner.id.clone(),
364 text: text.to_string(),
365 });
366
367 self.send_command(command).await?;
368 Ok(())
369 }
370}
371
372impl Element {
377 pub async fn mouse_click(&self, button: u8) -> Result<()> {
386 debug!(element_id = %self.inner.id, button = button, "Mouse clicking element");
387
388 let command = Command::Input(InputCommand::MouseClick {
389 element_id: Some(self.inner.id.clone()),
390 x: None,
391 y: None,
392 button,
393 });
394
395 self.send_command(command).await?;
396 Ok(())
397 }
398
399 pub async fn double_click(&self) -> Result<()> {
403 debug!(element_id = %self.inner.id, "Double clicking element");
404
405 self.call_method(
406 "dispatchEvent",
407 vec![serde_json::json!({"type": "dblclick", "bubbles": true, "cancelable": true})],
408 )
409 .await?;
410 Ok(())
411 }
412
413 pub async fn context_click(&self) -> Result<()> {
417 debug!(element_id = %self.inner.id, "Context clicking element");
418 self.mouse_click(2).await
419 }
420
421 pub async fn hover(&self) -> Result<()> {
425 debug!(element_id = %self.inner.id, "Hovering over element");
426 self.mouse_move().await
427 }
428
429 pub async fn mouse_move(&self) -> Result<()> {
431 debug!(element_id = %self.inner.id, "Moving mouse to element");
432
433 let command = Command::Input(InputCommand::MouseMove {
434 element_id: Some(self.inner.id.clone()),
435 x: None,
436 y: None,
437 });
438
439 self.send_command(command).await?;
440 Ok(())
441 }
442
443 pub async fn mouse_down(&self, button: u8) -> Result<()> {
452 debug!(element_id = %self.inner.id, button = button, "Mouse down on element");
453
454 let command = Command::Input(InputCommand::MouseDown {
455 element_id: Some(self.inner.id.clone()),
456 x: None,
457 y: None,
458 button,
459 });
460
461 self.send_command(command).await?;
462 Ok(())
463 }
464
465 pub async fn mouse_up(&self, button: u8) -> Result<()> {
474 debug!(element_id = %self.inner.id, button = button, "Mouse up on element");
475
476 let command = Command::Input(InputCommand::MouseUp {
477 element_id: Some(self.inner.id.clone()),
478 x: None,
479 y: None,
480 button,
481 });
482
483 self.send_command(command).await?;
484 Ok(())
485 }
486}
487
488impl Element {
493 pub async fn scroll_into_view(&self) -> Result<()> {
497 debug!(element_id = %self.inner.id, "Scrolling element into view");
498
499 self.call_method(
500 "scrollIntoView",
501 vec![serde_json::json!({"behavior": "smooth", "block": "center"})],
502 )
503 .await?;
504 Ok(())
505 }
506
507 pub async fn scroll_into_view_instant(&self) -> Result<()> {
509 debug!(element_id = %self.inner.id, "Scrolling element into view (instant)");
510
511 self.call_method(
512 "scrollIntoView",
513 vec![serde_json::json!({"behavior": "instant", "block": "center"})],
514 )
515 .await?;
516 Ok(())
517 }
518
519 pub async fn get_bounding_rect(&self) -> Result<(f64, f64, f64, f64)> {
525 let result = self.call_method("getBoundingClientRect", vec![]).await?;
526
527 let x = result.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
528 let y = result.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
529 let width = result.get("width").and_then(|v| v.as_f64()).unwrap_or(0.0);
530 let height = result.get("height").and_then(|v| v.as_f64()).unwrap_or(0.0);
531
532 debug!(element_id = %self.inner.id, x = x, y = y, width = width, height = height, "Got bounding rect");
533 Ok((x, y, width, height))
534 }
535}
536
537impl Element {
542 pub async fn is_checked(&self) -> Result<bool> {
544 let value = self.get_property("checked").await?;
545 Ok(value.as_bool().unwrap_or(false))
546 }
547
548 pub async fn check(&self) -> Result<()> {
552 if !self.is_checked().await? {
553 self.click().await?;
554 }
555 Ok(())
556 }
557
558 pub async fn uncheck(&self) -> Result<()> {
562 if self.is_checked().await? {
563 self.click().await?;
564 }
565 Ok(())
566 }
567
568 pub async fn toggle(&self) -> Result<()> {
570 self.click().await
571 }
572
573 pub async fn set_checked(&self, checked: bool) -> Result<()> {
575 if checked {
576 self.check().await
577 } else {
578 self.uncheck().await
579 }
580 }
581}
582
583impl Element {
588 pub async fn select_by_text(&self, text: &str) -> Result<()> {
597 let escaped = serde_json::to_string(text).unwrap_or_else(|_| format!("\"{}\"", text));
598 let script = format!(
599 r#"const select = arguments[0];
600 for (const opt of select.options) {{
601 if (opt.textContent.trim() === {escaped}) {{
602 opt.selected = true;
603 select.dispatchEvent(new Event('change', {{bubbles: true}}));
604 return true;
605 }}
606 }}
607 return false;"#
608 );
609
610 let command = Command::Script(ScriptCommand::Evaluate {
611 script,
612 args: vec![serde_json::json!({"elementId": self.inner.id.as_str()})],
613 });
614
615 let response = self.send_command(command).await?;
616
617 let found = response
618 .result
619 .as_ref()
620 .and_then(|v| v.get("value"))
621 .and_then(|v| v.as_bool())
622 .unwrap_or(false);
623
624 if !found {
625 return Err(Error::invalid_argument(format!(
626 "Option with text '{}' not found",
627 text
628 )));
629 }
630
631 Ok(())
632 }
633
634 pub async fn select_by_value(&self, value: &str) -> Result<()> {
636 self.set_property("value", Value::String(value.to_string()))
637 .await?;
638 self.call_method(
639 "dispatchEvent",
640 vec![serde_json::json!({"type": "change", "bubbles": true})],
641 )
642 .await?;
643 Ok(())
644 }
645
646 pub async fn select_by_index(&self, index: usize) -> Result<()> {
648 self.set_property("selectedIndex", Value::Number(index.into()))
649 .await?;
650 self.call_method(
651 "dispatchEvent",
652 vec![serde_json::json!({"type": "change", "bubbles": true})],
653 )
654 .await?;
655 Ok(())
656 }
657
658 pub async fn get_selected_value(&self) -> Result<Option<String>> {
660 let value = self.get_property("value").await?;
661 Ok(value.as_str().map(|s| s.to_string()))
662 }
663
664 pub async fn get_selected_index(&self) -> Result<i64> {
666 let value = self.get_property("selectedIndex").await?;
667 Ok(value.as_i64().unwrap_or(-1))
668 }
669
670 pub async fn get_selected_text(&self) -> Result<Option<String>> {
672 let options = self.find_elements(By::css("option:checked")).await?;
673 if let Some(option) = options.first() {
674 let text = option.get_text().await?;
675 return Ok(Some(text));
676 }
677 Ok(None)
678 }
679
680 pub async fn is_multiple(&self) -> Result<bool> {
682 let value = self.get_property("multiple").await?;
683 Ok(value.as_bool().unwrap_or(false))
684 }
685}
686
687impl Element {
692 pub async fn find_element(&self, by: By) -> Result<Element> {
703 let command = Command::Element(ElementCommand::Find {
704 strategy: by.strategy().to_string(),
705 value: by.value().to_string(),
706 parent_id: Some(self.inner.id.clone()),
707 });
708
709 let response = self.send_command(command).await?;
710
711 let element_id = response
712 .result
713 .as_ref()
714 .and_then(|v| v.get("elementId"))
715 .and_then(|v| v.as_str())
716 .ok_or_else(|| {
717 Error::element_not_found(
718 format!("{}:{}", by.strategy(), by.value()),
719 self.inner.tab_id,
720 self.inner.frame_id,
721 )
722 })?;
723
724 Ok(Element::new(
725 ElementId::new(element_id),
726 self.inner.tab_id,
727 self.inner.frame_id,
728 self.inner.session_id,
729 self.inner.window.clone(),
730 ))
731 }
732
733 pub async fn find_elements(&self, by: By) -> Result<Vec<Element>> {
744 let command = Command::Element(ElementCommand::FindAll {
745 strategy: by.strategy().to_string(),
746 value: by.value().to_string(),
747 parent_id: Some(self.inner.id.clone()),
748 });
749
750 let response = self.send_command(command).await?;
751
752 let element_ids = response
753 .result
754 .as_ref()
755 .and_then(|v| v.get("elementIds"))
756 .and_then(|v| v.as_array())
757 .map(|arr| {
758 arr.iter()
759 .filter_map(|v| v.as_str())
760 .map(|id| {
761 Element::new(
762 ElementId::new(id),
763 self.inner.tab_id,
764 self.inner.frame_id,
765 self.inner.session_id,
766 self.inner.window.clone(),
767 )
768 })
769 .collect()
770 })
771 .unwrap_or_default();
772
773 Ok(element_ids)
774 }
775}
776
777impl Element {
782 pub async fn get_property(&self, name: &str) -> Result<Value> {
788 let command = Command::Element(ElementCommand::GetProperty {
789 element_id: self.inner.id.clone(),
790 name: name.to_string(),
791 });
792
793 let response = self.send_command(command).await?;
794
795 Ok(response
796 .result
797 .and_then(|v| v.get("value").cloned())
798 .unwrap_or(Value::Null))
799 }
800
801 pub async fn set_property(&self, name: &str, value: Value) -> Result<()> {
808 let command = Command::Element(ElementCommand::SetProperty {
809 element_id: self.inner.id.clone(),
810 name: name.to_string(),
811 value,
812 });
813
814 self.send_command(command).await?;
815 Ok(())
816 }
817
818 pub async fn call_method(&self, name: &str, args: Vec<Value>) -> Result<Value> {
825 let command = Command::Element(ElementCommand::CallMethod {
826 element_id: self.inner.id.clone(),
827 name: name.to_string(),
828 args,
829 });
830
831 let response = self.send_command(command).await?;
832
833 Ok(response
834 .result
835 .and_then(|v| v.get("value").cloned())
836 .unwrap_or(Value::Null))
837 }
838}
839
840impl Element {
845 pub async fn screenshot(&self) -> Result<String> {
856 self.screenshot_with_format("png", None).await
857 }
858
859 pub async fn screenshot_jpeg(&self, quality: u8) -> Result<String> {
865 self.screenshot_with_format("jpeg", Some(quality.min(100)))
866 .await
867 }
868
869 async fn screenshot_with_format(&self, format: &str, quality: Option<u8>) -> Result<String> {
874 use base64::Engine;
875 use base64::engine::general_purpose::STANDARD as Base64Standard;
876 use image::GenericImageView;
877
878 let command = Command::Element(ElementCommand::CaptureScreenshot {
879 element_id: self.inner.id.clone(),
880 format: format.to_string(),
881 quality,
882 });
883
884 let response = self.send_command(command).await?;
885
886 tracing::debug!(response = ?response, "Element screenshot response");
887
888 let result = response.result.as_ref().ok_or_else(|| {
889 let error_str = response.error.as_deref().unwrap_or("none");
890 let msg_str = response.message.as_deref().unwrap_or("none");
891 Error::script_error(format!(
892 "Element screenshot failed. error={}, message={}",
893 error_str, msg_str
894 ))
895 })?;
896
897 let data = result
898 .get("data")
899 .and_then(|v| v.as_str())
900 .ok_or_else(|| Error::script_error("Screenshot response missing data field"))?;
901
902 if let Some(clip) = result.get("clip") {
904 let x = clip.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0) as u32;
905 let y = clip.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0) as u32;
906 let width = clip.get("width").and_then(|v| v.as_f64()).unwrap_or(0.0) as u32;
907 let height = clip.get("height").and_then(|v| v.as_f64()).unwrap_or(0.0) as u32;
908 let scale = clip.get("scale").and_then(|v| v.as_f64()).unwrap_or(1.0);
909
910 let x = (x as f64 * scale) as u32;
912 let y = (y as f64 * scale) as u32;
913 let width = (width as f64 * scale) as u32;
914 let height = (height as f64 * scale) as u32;
915
916 if width == 0 || height == 0 {
917 return Err(Error::script_error("Element has zero dimensions"));
918 }
919
920 let image_bytes = Base64Standard
922 .decode(data)
923 .map_err(|e| Error::script_error(format!("Failed to decode base64: {}", e)))?;
924
925 let img = image::load_from_memory(&image_bytes)
926 .map_err(|e| Error::script_error(format!("Failed to load image: {}", e)))?;
927
928 let (img_width, img_height) = img.dimensions();
930 let x = x.min(img_width.saturating_sub(1));
931 let y = y.min(img_height.saturating_sub(1));
932 let width = width.min(img_width.saturating_sub(x));
933 let height = height.min(img_height.saturating_sub(y));
934
935 let cropped = img.crop_imm(x, y, width, height);
937
938 let mut output = std::io::Cursor::new(Vec::new());
940 match format {
941 "jpeg" => {
942 use image::codecs::jpeg::JpegEncoder;
943 let q = quality.unwrap_or(85);
944 let encoder = JpegEncoder::new_with_quality(&mut output, q);
945 cropped.write_with_encoder(encoder).map_err(|e| {
946 Error::script_error(format!("Failed to encode JPEG: {}", e))
947 })?;
948 }
949 _ => {
950 cropped
951 .write_to(&mut output, image::ImageFormat::Png)
952 .map_err(|e| Error::script_error(format!("Failed to encode PNG: {}", e)))?;
953 }
954 }
955
956 Ok(Base64Standard.encode(output.into_inner()))
957 } else {
958 Ok(data.to_string())
960 }
961 }
962
963 pub async fn screenshot_bytes(&self) -> Result<Vec<u8>> {
965 use base64::Engine;
966 use base64::engine::general_purpose::STANDARD as Base64Standard;
967
968 let base64_data = self.screenshot().await?;
969 Base64Standard
970 .decode(&base64_data)
971 .map_err(|e| Error::script_error(format!("Failed to decode base64: {}", e)))
972 }
973
974 pub async fn save_screenshot(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
978 use base64::Engine;
979 use base64::engine::general_purpose::STANDARD as Base64Standard;
980
981 let path = path.as_ref();
982 let ext = path
983 .extension()
984 .and_then(|e| e.to_str())
985 .unwrap_or("png")
986 .to_lowercase();
987
988 let base64_data = match ext.as_str() {
989 "jpg" | "jpeg" => self.screenshot_jpeg(85).await?,
990 _ => self.screenshot().await?,
991 };
992
993 let bytes = Base64Standard
994 .decode(&base64_data)
995 .map_err(|e| Error::script_error(format!("Failed to decode base64: {}", e)))?;
996
997 tokio::fs::write(path, bytes).await.map_err(Error::Io)?;
998 Ok(())
999 }
1000}
1001
1002impl Element {
1007 async fn send_command(&self, command: Command) -> Result<Response> {
1009 let window = self
1010 .inner
1011 .window
1012 .as_ref()
1013 .ok_or_else(|| Error::protocol("Element has no associated window"))?;
1014
1015 let request = Request::new(self.inner.tab_id, self.inner.frame_id, command);
1016
1017 window
1018 .inner
1019 .pool
1020 .send(window.inner.session_id, request)
1021 .await
1022 }
1023}
1024
1025#[cfg(test)]
1030mod tests {
1031 use super::Element;
1032
1033 #[test]
1034 fn test_element_is_clone() {
1035 fn assert_clone<T: Clone>() {}
1036 assert_clone::<Element>();
1037 }
1038
1039 #[test]
1040 fn test_element_is_debug() {
1041 fn assert_debug<T: std::fmt::Debug>() {}
1042 assert_debug::<Element>();
1043 }
1044}