1use crate::error::{Error, Result};
7use crate::protocol::page::{GotoOptions, Response, WaitUntil};
8use crate::protocol::{parse_result, serialize_argument, serialize_null};
9use crate::server::channel::Channel;
10use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
11use crate::server::connection::ConnectionExt;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::any::Any;
15use std::sync::{Arc, Mutex, RwLock};
16
17#[derive(Debug, Clone, Default, Serialize)]
22#[serde(rename_all = "camelCase")]
23#[non_exhaustive]
24pub struct ContentOptions {
25 #[serde(skip_serializing_if = "Option::is_none")]
29 pub include_shadow: Option<bool>,
30}
31
32impl ContentOptions {
33 pub fn include_shadow(mut self, include_shadow: bool) -> Self {
35 self.include_shadow = Some(include_shadow);
36 self
37 }
38}
39
40#[derive(Clone)]
49pub struct Frame {
50 base: ChannelOwnerImpl,
51 url: Arc<RwLock<String>>,
54 name: Arc<str>,
57 parent_frame_guid: Option<Arc<str>>,
60 is_detached: Arc<RwLock<bool>>,
63 page: Arc<Mutex<Option<crate::protocol::Page>>>,
68}
69
70impl Frame {
71 pub fn new(
76 parent: Arc<dyn ChannelOwner>,
77 type_name: String,
78 guid: Arc<str>,
79 initializer: Value,
80 ) -> Result<Self> {
81 let base = ChannelOwnerImpl::new(
82 ParentOrConnection::Parent(parent),
83 type_name,
84 guid,
85 initializer.clone(),
86 );
87
88 let initial_url = initializer
90 .get("url")
91 .and_then(|v| v.as_str())
92 .unwrap_or("about:blank")
93 .to_string();
94
95 let url = Arc::new(RwLock::new(initial_url));
96
97 let name: Arc<str> = Arc::from(
99 initializer
100 .get("name")
101 .and_then(|v| v.as_str())
102 .unwrap_or(""),
103 );
104
105 let parent_frame_guid: Option<Arc<str>> = initializer
107 .get("parentFrame")
108 .and_then(|v| v.get("guid"))
109 .and_then(|v| v.as_str())
110 .map(Arc::from);
111
112 Ok(Self {
113 base,
114 url,
115 name,
116 parent_frame_guid,
117 is_detached: Arc::new(RwLock::new(false)),
118 page: Arc::new(Mutex::new(None)),
119 })
120 }
121
122 pub(crate) fn set_page(&self, page: crate::protocol::Page) {
127 if let Ok(mut guard) = self.page.lock() {
128 *guard = Some(page);
129 }
130 }
131
132 pub fn page(&self) -> Option<crate::protocol::Page> {
141 if let Some(page) = self.page.lock().ok().and_then(|guard| guard.clone()) {
142 return Some(page);
143 }
144 let page = self.resolve_page()?;
150 self.set_page(page.clone());
151 Some(page)
152 }
153
154 fn resolve_page(&self) -> Option<crate::protocol::Page> {
157 let mut root = self.clone();
158 while let Some(parent) = root.parent_frame() {
159 root = parent;
160 }
161 let root_guid = root.guid().to_string();
162
163 self.base
164 .connection()
165 .all_objects_sync()
166 .into_iter()
167 .filter(|object| object.type_name() == "Page")
168 .find(|object| {
169 object.initializer()["mainFrame"]["guid"].as_str() == Some(root_guid.as_str())
170 })?
171 .as_any()
172 .downcast_ref::<crate::protocol::Page>()
173 .cloned()
174 }
175
176 pub fn name(&self) -> &str {
182 &self.name
183 }
184
185 pub fn parent_frame(&self) -> Option<crate::protocol::Frame> {
189 self.frame_by_guid(self.parent_frame_guid.as_deref()?)
195 }
196
197 fn frame_by_guid(&self, guid: &str) -> Option<crate::protocol::Frame> {
199 self.base
200 .connection()
201 .all_objects_sync()
202 .into_iter()
203 .find(|object| object.type_name() == "Frame" && object.guid() == guid)?
204 .as_any()
205 .downcast_ref::<crate::protocol::Frame>()
206 .cloned()
207 }
208
209 pub fn is_detached(&self) -> bool {
216 self.is_detached.read().map(|v| *v).unwrap_or(false)
217 }
218
219 pub fn child_frames(&self) -> Vec<crate::protocol::Frame> {
232 let my_guid = self.guid().to_string();
233 let conn = self.base.connection();
234
235 conn.all_objects_sync()
238 .into_iter()
239 .filter_map(|obj| {
240 if obj.type_name() != "Frame" {
242 return None;
243 }
244 let parent_guid = obj
246 .initializer()
247 .get("parentFrame")
248 .and_then(|v| v.get("guid"))
249 .and_then(|v| v.as_str())?;
250
251 if parent_guid == my_guid {
252 obj.as_any()
253 .downcast_ref::<crate::protocol::Frame>()
254 .cloned()
255 } else {
256 None
257 }
258 })
259 .collect()
260 }
261
262 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
303 pub async fn evaluate_handle(
304 &self,
305 expression: &str,
306 ) -> Result<Arc<crate::protocol::ElementHandle>> {
307 let params = serde_json::json!({
311 "expression": expression,
312 "arg": {"value": {"v": "undefined"}, "handles": []}
313 });
314
315 #[derive(Deserialize)]
317 struct HandleRef {
318 guid: String,
319 }
320 #[derive(Deserialize)]
321 struct EvaluateHandleResponse {
322 handle: HandleRef,
323 }
324
325 let response: EvaluateHandleResponse = self
326 .channel()
327 .send("evaluateExpressionHandle", params)
328 .await?;
329
330 let guid = &response.handle.guid;
331
332 let handle = self
334 .base
335 .connection()
336 .wait_for_typed::<crate::protocol::ElementHandle>(guid)
337 .await?;
338
339 Ok(Arc::new(handle))
340 }
341
342 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
365 pub async fn evaluate_handle_js(
366 &self,
367 expression: &str,
368 ) -> Result<std::sync::Arc<crate::protocol::JSHandle>> {
369 let params = serde_json::json!({
374 "expression": expression,
375 "arg": {"value": {"v": "undefined"}, "handles": []}
376 });
377
378 #[derive(Deserialize)]
380 struct HandleRef {
381 guid: String,
382 }
383 #[derive(Deserialize)]
384 struct EvaluateHandleResponse {
385 handle: HandleRef,
386 }
387
388 let response: EvaluateHandleResponse = self
389 .channel()
390 .send("evaluateExpressionHandle", params)
391 .await?;
392
393 let guid = &response.handle.guid;
394
395 let handle = crate::protocol::JSHandle::wait_for(&self.base.connection(), guid).await?;
396
397 Ok(std::sync::Arc::new(handle))
398 }
399
400 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
412 pub(crate) async fn wait_for_function_internal(
413 &self,
414 expression: &str,
415 selector: Option<&str>,
416 options: impl Into<Option<crate::protocol::WaitForFunctionOptions>>,
417 ) -> Result<Option<std::sync::Arc<crate::protocol::JSHandle>>> {
418 let options = options.into().unwrap_or_default();
419
420 let timeout = options
424 .timeout
425 .or_else(|| self.page().map(|p| p.default_timeout_ms()))
426 .unwrap_or(crate::DEFAULT_TIMEOUT_MS);
427
428 let mut params = serde_json::json!({
429 "expression": expression,
430 "arg": {"value": {"v": "undefined"}, "handles": []},
431 "timeout": timeout,
432 });
433 if let Some(interval) = options.polling_interval {
434 params["pollingInterval"] = serde_json::json!(interval);
435 }
436 if let Some(selector) = selector {
437 params["selector"] = serde_json::json!(selector);
438 params["strict"] = serde_json::json!(true);
439 }
440
441 #[derive(Deserialize)]
442 struct HandleRef {
443 guid: String,
444 }
445 #[derive(Deserialize)]
446 struct WaitForFunctionResponse {
447 handle: Option<HandleRef>,
448 }
449
450 let response: WaitForFunctionResponse =
451 self.channel().send("waitForFunction", params).await?;
452
453 let Some(handle_ref) = response.handle else {
454 return Ok(None);
455 };
456
457 let handle =
458 crate::protocol::JSHandle::wait_for(&self.base.connection(), &handle_ref.guid).await?;
459 Ok(Some(std::sync::Arc::new(handle)))
460 }
461
462 pub async fn wait_for_function(
474 &self,
475 expression: &str,
476 options: impl Into<Option<crate::protocol::WaitForFunctionOptions>>,
477 ) -> Result<std::sync::Arc<crate::protocol::JSHandle>> {
478 self.wait_for_function_internal(expression, None, options)
479 .await?
480 .ok_or_else(|| {
481 crate::error::Error::ProtocolError(
482 "waitForFunction returned no handle for a selector-less wait".to_string(),
483 )
484 })
485 }
486
487 pub(crate) async fn evaluate_with_fn_arg(
495 &self,
496 expression: &str,
497 binding_name: &str,
498 ) -> Result<Value> {
499 let params = serde_json::json!({
500 "expression": expression,
501 "arg": { "value": { "fn": binding_name }, "handles": [] },
502 });
503
504 #[derive(Deserialize)]
505 struct EvaluateResult {
506 value: serde_json::Value,
507 }
508
509 let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
510 Ok(parse_result(&result.value))
511 }
512
513 pub fn locator(&self, selector: impl Into<String>) -> crate::protocol::Locator {
528 let page = self
529 .page()
530 .expect("Frame::locator(): the frame's page is gone (was it closed?)");
531 crate::protocol::Locator::new(Arc::new(self.clone()), selector.into(), page)
532 }
533
534 pub fn frame_locator<'a>(
547 &self,
548 selector: impl Into<Option<&'a str>>,
549 ) -> crate::protocol::FrameLocator {
550 let page = self
551 .page()
552 .expect("Frame::frame_locator(): the frame's page is gone (was it closed?)");
553 let frame = Arc::new(self.clone());
554 match selector.into() {
555 Some(selector) => crate::protocol::FrameLocator::new(frame, selector.to_string(), page),
556 None => crate::protocol::FrameLocator::any_frame(frame, page),
557 }
558 }
559
560 pub fn get_by_text(&self, text: &str, exact: bool) -> crate::protocol::Locator {
564 self.locator(crate::protocol::locator::get_by_text_selector(text, exact))
565 }
566
567 pub fn get_by_label(&self, text: &str, exact: bool) -> crate::protocol::Locator {
571 self.locator(crate::protocol::locator::get_by_label_selector(text, exact))
572 }
573
574 pub fn get_by_placeholder(&self, text: &str, exact: bool) -> crate::protocol::Locator {
578 self.locator(crate::protocol::locator::get_by_placeholder_selector(
579 text, exact,
580 ))
581 }
582
583 pub fn get_by_alt_text(&self, text: &str, exact: bool) -> crate::protocol::Locator {
587 self.locator(crate::protocol::locator::get_by_alt_text_selector(
588 text, exact,
589 ))
590 }
591
592 pub fn get_by_title(&self, text: &str, exact: bool) -> crate::protocol::Locator {
596 self.locator(crate::protocol::locator::get_by_title_selector(text, exact))
597 }
598
599 pub fn get_by_test_id(&self, test_id: &str) -> crate::protocol::Locator {
606 use crate::server::channel_owner::ChannelOwner;
607 let attr = self.connection().selectors().test_id_attribute();
608 self.locator(crate::protocol::locator::get_by_test_id_selector_with_attr(
609 test_id, &attr,
610 ))
611 }
612
613 pub fn get_by_role(
617 &self,
618 role: crate::protocol::locator::AriaRole,
619 options: Option<crate::protocol::locator::GetByRoleOptions>,
620 ) -> crate::protocol::Locator {
621 self.locator(crate::protocol::locator::get_by_role_selector(
622 role, options,
623 ))
624 }
625
626 fn channel(&self) -> &Channel {
628 self.base.channel()
629 }
630
631 pub fn url(&self) -> String {
637 self.url.read().unwrap().clone()
638 }
639
640 #[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid(), url = %url, status = tracing::field::Empty))]
654 pub async fn goto(
655 &self,
656 url: &str,
657 options: impl Into<Option<GotoOptions>>,
658 ) -> Result<Option<Response>> {
659 let options = options.into();
660 let mut params = serde_json::json!({
662 "url": url,
663 });
664
665 if let Some(opts) = options {
667 if let Some(timeout) = opts.timeout {
668 params["timeout"] = serde_json::json!(timeout.as_millis() as u64);
669 } else {
670 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
672 }
673 if let Some(wait_until) = opts.wait_until {
674 params["waitUntil"] = serde_json::json!(wait_until.as_str());
675 }
676 } else {
677 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
679 }
680
681 #[derive(Deserialize)]
684 struct GotoResponse {
685 response: Option<ResponseReference>,
686 }
687
688 #[derive(Deserialize)]
689 struct ResponseReference {
690 #[serde(deserialize_with = "crate::server::connection::deserialize_arc_str")]
691 guid: Arc<str>,
692 }
693
694 let goto_result: GotoResponse = self.channel().send("goto", params).await?;
695
696 if let Some(response_ref) = goto_result.response {
698 let response_arc = self
700 .connection()
701 .wait_for_object(&response_ref.guid)
702 .await?;
703
704 let initializer = response_arc.initializer();
707
708 let status = initializer["status"].as_u64().ok_or_else(|| {
710 crate::error::Error::ProtocolError("Response missing status".to_string())
711 })? as u16;
712
713 let headers = initializer["headers"]
715 .as_array()
716 .ok_or_else(|| {
717 crate::error::Error::ProtocolError("Response missing headers".to_string())
718 })?
719 .iter()
720 .filter_map(|h| {
721 let name = h["name"].as_str()?;
722 let value = h["value"].as_str()?;
723 Some((name.to_string(), value.to_string()))
724 })
725 .collect();
726
727 tracing::Span::current().record("status", status);
728 Ok(Some(Response::new(
729 initializer["url"]
730 .as_str()
731 .ok_or_else(|| {
732 crate::error::Error::ProtocolError("Response missing url".to_string())
733 })?
734 .to_string(),
735 status,
736 initializer["statusText"].as_str().unwrap_or("").to_string(),
737 headers,
738 Some(response_arc),
739 )))
740 } else {
741 Ok(None)
744 }
745 }
746
747 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
751 pub async fn title(&self) -> Result<String> {
752 #[derive(Deserialize)]
753 struct TitleResponse {
754 value: String,
755 }
756
757 let response: TitleResponse = self.channel().send("title", serde_json::json!({})).await?;
758 Ok(response.value)
759 }
760
761 pub fn webmcp(&self) -> crate::protocol::WebMCP {
767 crate::protocol::WebMCP::new(self.clone())
768 }
769
770 pub async fn content(&self) -> Result<String> {
774 self.content_with_options(ContentOptions::default()).await
775 }
776
777 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
782 pub async fn content_with_options(&self, options: ContentOptions) -> Result<String> {
783 #[derive(Deserialize)]
784 struct ContentResponse {
785 value: String,
786 }
787
788 let response: ContentResponse = self.channel().send("content", options).await?;
789 Ok(response.value)
790 }
791
792 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
796 pub async fn set_content(
797 &self,
798 html: &str,
799 options: impl Into<Option<GotoOptions>>,
800 ) -> Result<()> {
801 let options = options.into();
802 let mut params = serde_json::json!({
803 "html": html,
804 });
805
806 if let Some(opts) = options {
807 if let Some(timeout) = opts.timeout {
808 params["timeout"] = serde_json::json!(timeout.as_millis() as u64);
809 } else {
810 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
811 }
812 if let Some(wait_until) = opts.wait_until {
813 params["waitUntil"] = serde_json::json!(wait_until.as_str());
814 }
815 } else {
816 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
817 }
818
819 self.channel().send_no_result("setContent", params).await
820 }
821
822 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
830 pub async fn wait_for_load_state(&self, state: Option<WaitUntil>) -> Result<()> {
831 let target_state = state.unwrap_or(WaitUntil::Load);
832
833 let js_check = match target_state {
834 WaitUntil::Load => "document.readyState === 'complete'",
836 WaitUntil::DomContentLoaded => "document.readyState !== 'loading'",
838 WaitUntil::NetworkIdle => "document.readyState === 'complete'",
841 WaitUntil::Commit => "document.readyState !== 'loading'",
843 };
844
845 let timeout_ms = crate::DEFAULT_TIMEOUT_MS as u64;
846 let poll_interval = std::time::Duration::from_millis(50);
847 let start = std::time::Instant::now();
848
849 loop {
850 #[derive(Deserialize)]
851 struct EvalResponse {
852 value: serde_json::Value,
853 }
854
855 let result: EvalResponse = self
856 .channel()
857 .send(
858 "evaluateExpression",
859 serde_json::json!({
860 "expression": js_check,
861 "isFunction": false,
862 "arg": crate::protocol::serialize_null(),
863 }),
864 )
865 .await?;
866
867 let is_ready = result
869 .value
870 .as_object()
871 .and_then(|m| m.get("b"))
872 .and_then(|v| v.as_bool())
873 .unwrap_or(false);
874
875 if is_ready {
876 return Ok(());
877 }
878
879 if start.elapsed().as_millis() as u64 >= timeout_ms {
880 return Err(crate::error::Error::Timeout(format!(
881 "wait_for_load_state({}) timed out after {}ms",
882 target_state.as_str(),
883 timeout_ms
884 )));
885 }
886
887 tokio::time::sleep(poll_interval).await;
888 }
889 }
890
891 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid(), url = %url))]
898 pub async fn wait_for_url(
899 &self,
900 url: &str,
901 options: impl Into<Option<GotoOptions>>,
902 ) -> Result<()> {
903 let options = options.into();
904 let timeout_ms = options
905 .as_ref()
906 .and_then(|o| o.timeout)
907 .map(|d| d.as_millis() as u64)
908 .unwrap_or(crate::DEFAULT_TIMEOUT_MS as u64);
909
910 let matcher = if url.contains('*') {
914 Some(crate::protocol::glob::GlobMatcher::new(url))
915 } else {
916 None
917 };
918
919 let poll_interval = std::time::Duration::from_millis(50);
920 let start = std::time::Instant::now();
921
922 loop {
923 let current_url = self.url();
924
925 let matches = match &matcher {
926 Some(matcher) => matcher.as_ref().is_some_and(|m| m.matches(¤t_url)),
928 None => current_url == url,
929 };
930
931 if matches {
932 if let Some(ref opts) = options
934 && let Some(wait_until) = opts.wait_until
935 {
936 self.wait_for_load_state(Some(wait_until)).await?;
937 }
938 return Ok(());
939 }
940
941 if start.elapsed().as_millis() as u64 >= timeout_ms {
942 return Err(crate::error::Error::Timeout(format!(
943 "wait_for_url({}) timed out after {}ms, current URL: {}",
944 url, timeout_ms, current_url
945 )));
946 }
947
948 tokio::time::sleep(poll_interval).await;
949 }
950 }
951
952 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
956 pub async fn query_selector(
957 &self,
958 selector: &str,
959 ) -> Result<Option<Arc<crate::protocol::ElementHandle>>> {
960 let response: serde_json::Value = self
961 .channel()
962 .send(
963 "querySelector",
964 serde_json::json!({
965 "selector": selector
966 }),
967 )
968 .await?;
969
970 if response.as_object().map(|o| o.is_empty()).unwrap_or(true) {
972 return Ok(None);
973 }
974
975 let element_value = if let Some(elem) = response.get("element") {
977 elem
978 } else if let Some(elem) = response.get("handle") {
979 elem
980 } else {
981 &response
983 };
984
985 if element_value.is_null() {
986 return Ok(None);
987 }
988
989 self.element_handle_from(element_value).await.map(Some)
990 }
991
992 async fn element_handle_from(
995 &self,
996 element: &serde_json::Value,
997 ) -> Result<Arc<crate::protocol::ElementHandle>> {
998 let guid = element["guid"].as_str().ok_or_else(|| {
999 crate::error::Error::ProtocolError("Element GUID missing".to_string())
1000 })?;
1001 let handle: crate::protocol::ElementHandle = self
1002 .base
1003 .connection()
1004 .get_typed::<crate::protocol::ElementHandle>(guid)
1005 .await?;
1006 Ok(Arc::new(handle))
1007 }
1008
1009 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
1013 pub async fn query_selector_all(
1014 &self,
1015 selector: &str,
1016 ) -> Result<Vec<Arc<crate::protocol::ElementHandle>>> {
1017 #[derive(Deserialize)]
1018 struct QueryAllResponse {
1019 elements: Vec<serde_json::Value>,
1020 }
1021
1022 let response: QueryAllResponse = self
1023 .channel()
1024 .send(
1025 "querySelectorAll",
1026 serde_json::json!({
1027 "selector": selector
1028 }),
1029 )
1030 .await?;
1031
1032 let connection = self.base.connection();
1034 let mut handles = Vec::new();
1035
1036 for element_value in response.elements {
1037 let guid = element_value["guid"].as_str().ok_or_else(|| {
1038 crate::error::Error::ProtocolError("Element GUID missing".to_string())
1039 })?;
1040
1041 let handle: crate::protocol::ElementHandle = connection
1042 .get_typed::<crate::protocol::ElementHandle>(guid)
1043 .await?;
1044
1045 handles.push(Arc::new(handle));
1046 }
1047
1048 Ok(handles)
1049 }
1050
1051 pub(crate) async fn locator_count(&self, selector: &str) -> Result<usize> {
1056 #[derive(Deserialize)]
1058 struct QueryAllResponse {
1059 elements: Vec<serde_json::Value>,
1060 }
1061
1062 let response: QueryAllResponse = self
1063 .channel()
1064 .send(
1065 "querySelectorAll",
1066 serde_json::json!({
1067 "selector": selector
1068 }),
1069 )
1070 .await?;
1071
1072 Ok(response.elements.len())
1073 }
1074
1075 pub(crate) async fn locator_text_content(&self, selector: &str) -> Result<Option<String>> {
1077 #[derive(Deserialize)]
1078 struct TextContentResponse {
1079 value: Option<String>,
1080 }
1081
1082 let response: TextContentResponse = self
1083 .channel()
1084 .send(
1085 "textContent",
1086 serde_json::json!({
1087 "selector": selector,
1088 "strict": true,
1089 "timeout": crate::DEFAULT_TIMEOUT_MS
1090 }),
1091 )
1092 .await?;
1093
1094 Ok(response.value)
1095 }
1096
1097 pub(crate) async fn locator_inner_text(&self, selector: &str) -> Result<String> {
1099 #[derive(Deserialize)]
1100 struct InnerTextResponse {
1101 value: String,
1102 }
1103
1104 let response: InnerTextResponse = self
1105 .channel()
1106 .send(
1107 "innerText",
1108 serde_json::json!({
1109 "selector": selector,
1110 "strict": true,
1111 "timeout": crate::DEFAULT_TIMEOUT_MS
1112 }),
1113 )
1114 .await?;
1115
1116 Ok(response.value)
1117 }
1118
1119 pub(crate) async fn locator_inner_html(&self, selector: &str) -> Result<String> {
1121 #[derive(Deserialize)]
1122 struct InnerHTMLResponse {
1123 value: String,
1124 }
1125
1126 let response: InnerHTMLResponse = self
1127 .channel()
1128 .send(
1129 "innerHTML",
1130 serde_json::json!({
1131 "selector": selector,
1132 "strict": true,
1133 "timeout": crate::DEFAULT_TIMEOUT_MS
1134 }),
1135 )
1136 .await?;
1137
1138 Ok(response.value)
1139 }
1140
1141 pub(crate) async fn locator_get_attribute(
1143 &self,
1144 selector: &str,
1145 name: &str,
1146 ) -> Result<Option<String>> {
1147 #[derive(Deserialize)]
1148 struct GetAttributeResponse {
1149 value: Option<String>,
1150 }
1151
1152 let response: GetAttributeResponse = self
1153 .channel()
1154 .send(
1155 "getAttribute",
1156 serde_json::json!({
1157 "selector": selector,
1158 "name": name,
1159 "strict": true,
1160 "timeout": crate::DEFAULT_TIMEOUT_MS
1161 }),
1162 )
1163 .await?;
1164
1165 Ok(response.value)
1166 }
1167
1168 pub(crate) async fn locator_is_visible(&self, selector: &str) -> Result<bool> {
1170 #[derive(Deserialize)]
1171 struct IsVisibleResponse {
1172 value: bool,
1173 }
1174
1175 let response: IsVisibleResponse = self
1176 .channel()
1177 .send(
1178 "isVisible",
1179 serde_json::json!({
1180 "selector": selector,
1181 "strict": true,
1182 "timeout": crate::DEFAULT_TIMEOUT_MS
1183 }),
1184 )
1185 .await?;
1186
1187 Ok(response.value)
1188 }
1189
1190 pub(crate) async fn locator_is_enabled(&self, selector: &str) -> Result<bool> {
1192 #[derive(Deserialize)]
1193 struct IsEnabledResponse {
1194 value: bool,
1195 }
1196
1197 let response: IsEnabledResponse = self
1198 .channel()
1199 .send(
1200 "isEnabled",
1201 serde_json::json!({
1202 "selector": selector,
1203 "strict": true,
1204 "timeout": crate::DEFAULT_TIMEOUT_MS
1205 }),
1206 )
1207 .await?;
1208
1209 Ok(response.value)
1210 }
1211
1212 pub(crate) async fn locator_is_checked(&self, selector: &str) -> Result<bool> {
1214 #[derive(Deserialize)]
1215 struct IsCheckedResponse {
1216 value: bool,
1217 }
1218
1219 let response: IsCheckedResponse = self
1220 .channel()
1221 .send(
1222 "isChecked",
1223 serde_json::json!({
1224 "selector": selector,
1225 "strict": true,
1226 "timeout": crate::DEFAULT_TIMEOUT_MS
1227 }),
1228 )
1229 .await?;
1230
1231 Ok(response.value)
1232 }
1233
1234 pub(crate) async fn locator_is_editable(&self, selector: &str) -> Result<bool> {
1236 #[derive(Deserialize)]
1237 struct IsEditableResponse {
1238 value: bool,
1239 }
1240
1241 let response: IsEditableResponse = self
1242 .channel()
1243 .send(
1244 "isEditable",
1245 serde_json::json!({
1246 "selector": selector,
1247 "strict": true,
1248 "timeout": crate::DEFAULT_TIMEOUT_MS
1249 }),
1250 )
1251 .await?;
1252
1253 Ok(response.value)
1254 }
1255
1256 pub(crate) async fn locator_is_hidden(&self, selector: &str) -> Result<bool> {
1258 #[derive(Deserialize)]
1259 struct IsHiddenResponse {
1260 value: bool,
1261 }
1262
1263 let response: IsHiddenResponse = self
1264 .channel()
1265 .send(
1266 "isHidden",
1267 serde_json::json!({
1268 "selector": selector,
1269 "strict": true,
1270 "timeout": crate::DEFAULT_TIMEOUT_MS
1271 }),
1272 )
1273 .await?;
1274
1275 Ok(response.value)
1276 }
1277
1278 pub(crate) async fn locator_is_disabled(&self, selector: &str) -> Result<bool> {
1280 #[derive(Deserialize)]
1281 struct IsDisabledResponse {
1282 value: bool,
1283 }
1284
1285 let response: IsDisabledResponse = self
1286 .channel()
1287 .send(
1288 "isDisabled",
1289 serde_json::json!({
1290 "selector": selector,
1291 "strict": true,
1292 "timeout": crate::DEFAULT_TIMEOUT_MS
1293 }),
1294 )
1295 .await?;
1296
1297 Ok(response.value)
1298 }
1299
1300 pub(crate) async fn locator_is_focused(&self, selector: &str) -> Result<bool> {
1306 #[derive(Deserialize)]
1307 struct EvaluateResult {
1308 value: serde_json::Value,
1309 }
1310
1311 let script = r#"selector => {
1314 const elements = document.querySelectorAll(selector);
1315 if (elements.length === 0) return false;
1316 const element = elements[0];
1317 return document.activeElement === element;
1318 }"#;
1319
1320 let params = serde_json::json!({
1321 "expression": script,
1322 "arg": {
1323 "value": {"s": selector},
1324 "handles": []
1325 }
1326 });
1327
1328 let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
1329
1330 if let serde_json::Value::Object(map) = &result.value
1332 && let Some(b) = map.get("b").and_then(|v| v.as_bool())
1333 {
1334 return Ok(b);
1335 }
1336
1337 Ok(result.value.to_string().to_lowercase().contains("true"))
1339 }
1340
1341 pub(crate) async fn locator_click(
1345 &self,
1346 selector: &str,
1347 options: Option<crate::protocol::ClickOptions>,
1348 ) -> Result<()> {
1349 let mut params = serde_json::json!({
1350 "selector": selector,
1351 "strict": true
1352 });
1353
1354 if let Some(opts) = options {
1355 let opts_json = opts.to_json();
1356 if let Some(obj) = params.as_object_mut()
1357 && let Some(opts_obj) = opts_json.as_object()
1358 {
1359 obj.extend(opts_obj.clone());
1360 }
1361 } else {
1362 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1363 }
1364
1365 self.channel()
1366 .send_no_result("click", params)
1367 .await
1368 .map_err(|e| match e {
1369 Error::Timeout(msg) => {
1370 Error::Timeout(format!("{} (selector: '{}')", msg, selector))
1371 }
1372 other => other,
1373 })
1374 }
1375
1376 pub(crate) async fn locator_dblclick(
1378 &self,
1379 selector: &str,
1380 options: Option<crate::protocol::ClickOptions>,
1381 ) -> Result<()> {
1382 let mut params = serde_json::json!({
1383 "selector": selector,
1384 "strict": true
1385 });
1386
1387 if let Some(opts) = options {
1388 let opts_json = opts.to_json();
1389 if let Some(obj) = params.as_object_mut()
1390 && let Some(opts_obj) = opts_json.as_object()
1391 {
1392 obj.extend(opts_obj.clone());
1393 }
1394 } else {
1395 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1396 }
1397
1398 self.channel().send_no_result("dblclick", params).await
1399 }
1400
1401 pub(crate) async fn locator_fill(
1403 &self,
1404 selector: &str,
1405 text: &str,
1406 options: Option<crate::protocol::FillOptions>,
1407 ) -> Result<()> {
1408 let mut params = serde_json::json!({
1409 "selector": selector,
1410 "value": text,
1411 "strict": true
1412 });
1413
1414 if let Some(opts) = options {
1415 let opts_json = opts.to_json();
1416 if let Some(obj) = params.as_object_mut()
1417 && let Some(opts_obj) = opts_json.as_object()
1418 {
1419 obj.extend(opts_obj.clone());
1420 }
1421 } else {
1422 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1423 }
1424
1425 self.channel().send_no_result("fill", params).await
1426 }
1427
1428 pub(crate) async fn locator_clear(
1430 &self,
1431 selector: &str,
1432 options: Option<crate::protocol::FillOptions>,
1433 ) -> Result<()> {
1434 let mut params = serde_json::json!({
1435 "selector": selector,
1436 "value": "",
1437 "strict": true
1438 });
1439
1440 if let Some(opts) = options {
1441 let opts_json = opts.to_json();
1442 if let Some(obj) = params.as_object_mut()
1443 && let Some(opts_obj) = opts_json.as_object()
1444 {
1445 obj.extend(opts_obj.clone());
1446 }
1447 } else {
1448 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1449 }
1450
1451 self.channel().send_no_result("fill", params).await
1452 }
1453
1454 pub(crate) async fn locator_press(
1456 &self,
1457 selector: &str,
1458 key: &str,
1459 options: Option<crate::protocol::PressOptions>,
1460 ) -> Result<()> {
1461 let mut params = serde_json::json!({
1462 "selector": selector,
1463 "key": key,
1464 "strict": true
1465 });
1466
1467 if let Some(opts) = options {
1468 let opts_json = opts.to_json();
1469 if let Some(obj) = params.as_object_mut()
1470 && let Some(opts_obj) = opts_json.as_object()
1471 {
1472 obj.extend(opts_obj.clone());
1473 }
1474 } else {
1475 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1476 }
1477
1478 self.channel().send_no_result("press", params).await
1479 }
1480
1481 pub(crate) async fn locator_focus(&self, selector: &str) -> Result<()> {
1483 self.channel()
1484 .send_no_result(
1485 "focus",
1486 serde_json::json!({
1487 "selector": selector,
1488 "strict": true,
1489 "timeout": crate::DEFAULT_TIMEOUT_MS
1490 }),
1491 )
1492 .await
1493 }
1494
1495 pub(crate) async fn locator_blur(&self, selector: &str) -> Result<()> {
1497 self.channel()
1498 .send_no_result(
1499 "blur",
1500 serde_json::json!({
1501 "selector": selector,
1502 "strict": true,
1503 "timeout": crate::DEFAULT_TIMEOUT_MS
1504 }),
1505 )
1506 .await
1507 }
1508
1509 pub(crate) async fn locator_press_sequentially(
1513 &self,
1514 selector: &str,
1515 text: &str,
1516 options: Option<crate::protocol::PressSequentiallyOptions>,
1517 ) -> Result<()> {
1518 let mut params = serde_json::json!({
1519 "selector": selector,
1520 "text": text,
1521 "strict": true
1522 });
1523
1524 if let Some(opts) = options {
1525 let opts_json = opts.to_json();
1526 if let Some(obj) = params.as_object_mut()
1527 && let Some(opts_obj) = opts_json.as_object()
1528 {
1529 obj.extend(opts_obj.clone());
1530 }
1531 } else {
1532 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1533 }
1534
1535 self.channel().send_no_result("type", params).await
1536 }
1537
1538 pub(crate) async fn locator_all_inner_texts(&self, selector: &str) -> Result<Vec<String>> {
1540 #[derive(serde::Deserialize)]
1541 struct EvaluateResult {
1542 value: serde_json::Value,
1543 }
1544
1545 let params = serde_json::json!({
1548 "selector": selector,
1549 "expression": "ee => ee.map(e => e.innerText)",
1550 "isFunction": true,
1551 "arg": {
1552 "value": {"v": "null"},
1553 "handles": []
1554 }
1555 });
1556
1557 let result: EvaluateResult = self.channel().send("evalOnSelectorAll", params).await?;
1558
1559 Self::parse_string_array(result.value)
1560 }
1561
1562 pub(crate) async fn locator_all_text_contents(&self, selector: &str) -> Result<Vec<String>> {
1564 #[derive(serde::Deserialize)]
1565 struct EvaluateResult {
1566 value: serde_json::Value,
1567 }
1568
1569 let params = serde_json::json!({
1572 "selector": selector,
1573 "expression": "ee => ee.map(e => e.textContent || '')",
1574 "isFunction": true,
1575 "arg": {
1576 "value": {"v": "null"},
1577 "handles": []
1578 }
1579 });
1580
1581 let result: EvaluateResult = self.channel().send("evalOnSelectorAll", params).await?;
1582
1583 Self::parse_string_array(result.value)
1584 }
1585
1586 pub(crate) async fn locator_tap(
1593 &self,
1594 selector: &str,
1595 options: Option<crate::protocol::TapOptions>,
1596 ) -> Result<()> {
1597 let mut params = serde_json::json!({
1598 "selector": selector,
1599 "strict": true
1600 });
1601
1602 if let Some(opts) = options {
1603 let opts_json = opts.to_json();
1604 if let Some(obj) = params.as_object_mut()
1605 && let Some(opts_obj) = opts_json.as_object()
1606 {
1607 obj.extend(opts_obj.clone());
1608 }
1609 } else {
1610 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1611 }
1612
1613 self.channel().send_no_result("tap", params).await
1614 }
1615
1616 pub(crate) async fn locator_drag_to(
1622 &self,
1623 source_selector: &str,
1624 target_selector: &str,
1625 options: Option<crate::protocol::DragToOptions>,
1626 ) -> Result<()> {
1627 let mut params = serde_json::json!({
1628 "source": source_selector,
1629 "target": target_selector,
1630 "strict": true
1631 });
1632
1633 if let Some(opts) = options {
1634 let opts_json = opts.to_json();
1635 if let Some(obj) = params.as_object_mut()
1636 && let Some(opts_obj) = opts_json.as_object()
1637 {
1638 obj.extend(opts_obj.clone());
1639 }
1640 } else {
1641 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1642 }
1643
1644 self.channel().send_no_result("dragAndDrop", params).await
1645 }
1646
1647 pub(crate) async fn locator_drop(
1651 &self,
1652 selector: &str,
1653 options: crate::protocol::DropOptions,
1654 ) -> Result<()> {
1655 let mut params = serde_json::json!({
1656 "selector": selector,
1657 "strict": true,
1658 });
1659
1660 let opts_json = options.to_json();
1661 if let Some(obj) = params.as_object_mut()
1662 && let Some(opts_obj) = opts_json.as_object()
1663 {
1664 obj.extend(opts_obj.clone());
1665 }
1666
1667 self.channel().send_no_result("drop", params).await
1668 }
1669
1670 pub(crate) async fn locator_wait_for(
1677 &self,
1678 selector: &str,
1679 options: Option<crate::protocol::WaitForOptions>,
1680 ) -> Result<()> {
1681 let mut params = serde_json::json!({
1682 "selector": selector,
1683 "strict": true
1684 });
1685
1686 if let Some(opts) = options {
1687 let opts_json = opts.to_json();
1688 if let Some(obj) = params.as_object_mut()
1689 && let Some(opts_obj) = opts_json.as_object()
1690 {
1691 obj.extend(opts_obj.clone());
1692 }
1693 } else {
1694 params["state"] = serde_json::json!("visible");
1696 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1697 }
1698
1699 let _: serde_json::Value = self.channel().send("waitForSelector", params).await?;
1701 Ok(())
1702 }
1703
1704 pub(crate) async fn locator_wait_for_element(
1710 &self,
1711 selector: &str,
1712 timeout: f64,
1713 ) -> Result<Arc<crate::protocol::ElementHandle>> {
1714 let response: serde_json::Value = self
1715 .channel()
1716 .send(
1717 "waitForSelector",
1718 serde_json::json!({
1719 "selector": selector,
1720 "strict": true,
1721 "state": "attached",
1722 "timeout": timeout,
1723 }),
1724 )
1725 .await?;
1726 let element = response.get("element").ok_or_else(|| {
1727 crate::error::Error::ProtocolError("waitForSelector returned no element".to_string())
1728 })?;
1729 self.element_handle_from(element).await
1730 }
1731
1732 pub(crate) async fn locator_evaluate<T: serde::Serialize>(
1739 &self,
1740 selector: &str,
1741 expression: &str,
1742 arg: Option<T>,
1743 ) -> Result<serde_json::Value> {
1744 let serialized_arg = match arg {
1745 Some(a) => serialize_argument(&a),
1746 None => serialize_null(),
1747 };
1748
1749 let params = serde_json::json!({
1750 "selector": selector,
1751 "expression": expression,
1752 "isFunction": true,
1753 "arg": serialized_arg,
1754 "strict": true
1755 });
1756
1757 #[derive(Deserialize)]
1758 struct EvaluateResult {
1759 value: serde_json::Value,
1760 }
1761
1762 let result: EvaluateResult = self.channel().send("evalOnSelector", params).await?;
1763 Ok(parse_result(&result.value))
1764 }
1765
1766 pub(crate) async fn locator_evaluate_all<T: serde::Serialize>(
1773 &self,
1774 selector: &str,
1775 expression: &str,
1776 arg: Option<T>,
1777 ) -> Result<serde_json::Value> {
1778 let serialized_arg = match arg {
1779 Some(a) => serialize_argument(&a),
1780 None => serialize_null(),
1781 };
1782
1783 let params = serde_json::json!({
1784 "selector": selector,
1785 "expression": expression,
1786 "isFunction": true,
1787 "arg": serialized_arg
1788 });
1789
1790 #[derive(Deserialize)]
1791 struct EvaluateResult {
1792 value: serde_json::Value,
1793 }
1794
1795 let result: EvaluateResult = self.channel().send("evalOnSelectorAll", params).await?;
1796 Ok(parse_result(&result.value))
1797 }
1798
1799 fn parse_string_array(value: serde_json::Value) -> Result<Vec<String>> {
1804 let array = if let Some(arr) = value.get("a").and_then(|v| v.as_array()) {
1806 arr.clone()
1807 } else if let Some(arr) = value.as_array() {
1808 arr.clone()
1809 } else {
1810 return Ok(Vec::new());
1811 };
1812
1813 let mut result = Vec::with_capacity(array.len());
1814 for item in &array {
1815 let s = if let Some(s) = item.get("s").and_then(|v| v.as_str()) {
1817 s.to_string()
1818 } else if let Some(s) = item.as_str() {
1819 s.to_string()
1820 } else if item.is_null() {
1821 String::new()
1822 } else {
1823 item.to_string()
1824 };
1825 result.push(s);
1826 }
1827 Ok(result)
1828 }
1829
1830 pub(crate) async fn locator_check(
1831 &self,
1832 selector: &str,
1833 options: Option<crate::protocol::CheckOptions>,
1834 ) -> Result<()> {
1835 let mut params = serde_json::json!({
1836 "selector": selector,
1837 "strict": true
1838 });
1839
1840 if let Some(opts) = options {
1841 let opts_json = opts.to_json();
1842 if let Some(obj) = params.as_object_mut()
1843 && let Some(opts_obj) = opts_json.as_object()
1844 {
1845 obj.extend(opts_obj.clone());
1846 }
1847 } else {
1848 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1849 }
1850
1851 self.channel().send_no_result("check", params).await
1852 }
1853
1854 pub(crate) async fn locator_uncheck(
1855 &self,
1856 selector: &str,
1857 options: Option<crate::protocol::CheckOptions>,
1858 ) -> Result<()> {
1859 let mut params = serde_json::json!({
1860 "selector": selector,
1861 "strict": true
1862 });
1863
1864 if let Some(opts) = options {
1865 let opts_json = opts.to_json();
1866 if let Some(obj) = params.as_object_mut()
1867 && let Some(opts_obj) = opts_json.as_object()
1868 {
1869 obj.extend(opts_obj.clone());
1870 }
1871 } else {
1872 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1873 }
1874
1875 self.channel().send_no_result("uncheck", params).await
1876 }
1877
1878 pub(crate) async fn locator_hover(
1879 &self,
1880 selector: &str,
1881 options: Option<crate::protocol::HoverOptions>,
1882 ) -> Result<()> {
1883 let mut params = serde_json::json!({
1884 "selector": selector,
1885 "strict": true
1886 });
1887
1888 if let Some(opts) = options {
1889 let opts_json = opts.to_json();
1890 if let Some(obj) = params.as_object_mut()
1891 && let Some(opts_obj) = opts_json.as_object()
1892 {
1893 obj.extend(opts_obj.clone());
1894 }
1895 } else {
1896 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1897 }
1898
1899 self.channel().send_no_result("hover", params).await
1900 }
1901
1902 pub(crate) async fn locator_input_value(&self, selector: &str) -> Result<String> {
1903 #[derive(Deserialize)]
1904 struct InputValueResponse {
1905 value: String,
1906 }
1907
1908 let response: InputValueResponse = self
1909 .channel()
1910 .send(
1911 "inputValue",
1912 serde_json::json!({
1913 "selector": selector,
1914 "strict": true,
1915 "timeout": crate::DEFAULT_TIMEOUT_MS }),
1917 )
1918 .await?;
1919
1920 Ok(response.value)
1921 }
1922
1923 pub(crate) async fn locator_select_option(
1924 &self,
1925 selector: &str,
1926 value: crate::protocol::SelectOption,
1927 options: Option<crate::protocol::SelectOptions>,
1928 ) -> Result<Vec<String>> {
1929 #[derive(Deserialize)]
1930 struct SelectOptionResponse {
1931 values: Vec<String>,
1932 }
1933
1934 let mut params = serde_json::json!({
1935 "selector": selector,
1936 "strict": true,
1937 "options": [value.to_json()]
1938 });
1939
1940 if let Some(opts) = options {
1941 let opts_json = opts.to_json();
1942 if let Some(obj) = params.as_object_mut()
1943 && let Some(opts_obj) = opts_json.as_object()
1944 {
1945 obj.extend(opts_obj.clone());
1946 }
1947 } else {
1948 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1950 }
1951
1952 let response: SelectOptionResponse = self.channel().send("selectOption", params).await?;
1953
1954 Ok(response.values)
1955 }
1956
1957 pub(crate) async fn locator_select_option_multiple(
1958 &self,
1959 selector: &str,
1960 values: Vec<crate::protocol::SelectOption>,
1961 options: Option<crate::protocol::SelectOptions>,
1962 ) -> Result<Vec<String>> {
1963 #[derive(Deserialize)]
1964 struct SelectOptionResponse {
1965 values: Vec<String>,
1966 }
1967
1968 let values_array: Vec<_> = values.iter().map(|v| v.to_json()).collect();
1969
1970 let mut params = serde_json::json!({
1971 "selector": selector,
1972 "strict": true,
1973 "options": values_array
1974 });
1975
1976 if let Some(opts) = options {
1977 let opts_json = opts.to_json();
1978 if let Some(obj) = params.as_object_mut()
1979 && let Some(opts_obj) = opts_json.as_object()
1980 {
1981 obj.extend(opts_obj.clone());
1982 }
1983 } else {
1984 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
1986 }
1987
1988 let response: SelectOptionResponse = self.channel().send("selectOption", params).await?;
1989
1990 Ok(response.values)
1991 }
1992
1993 pub(crate) async fn locator_set_input_files(
1994 &self,
1995 selector: &str,
1996 file: &std::path::PathBuf,
1997 ) -> Result<()> {
1998 use base64::{Engine as _, engine::general_purpose};
1999 use std::io::Read;
2000
2001 let mut file_handle = std::fs::File::open(file)?;
2003 let mut buffer = Vec::new();
2004 file_handle.read_to_end(&mut buffer)?;
2005
2006 let base64_content = general_purpose::STANDARD.encode(&buffer);
2008
2009 let file_name = file
2011 .file_name()
2012 .and_then(|n| n.to_str())
2013 .ok_or_else(|| crate::error::Error::InvalidArgument("Invalid file path".to_string()))?;
2014
2015 self.channel()
2016 .send_no_result(
2017 "setInputFiles",
2018 serde_json::json!({
2019 "selector": selector,
2020 "strict": true,
2021 "timeout": crate::DEFAULT_TIMEOUT_MS, "payloads": [{
2023 "name": file_name,
2024 "buffer": base64_content
2025 }]
2026 }),
2027 )
2028 .await
2029 }
2030
2031 pub(crate) async fn locator_set_input_files_multiple(
2032 &self,
2033 selector: &str,
2034 files: &[&std::path::PathBuf],
2035 ) -> Result<()> {
2036 use base64::{Engine as _, engine::general_purpose};
2037 use std::io::Read;
2038
2039 if files.is_empty() {
2041 return self
2042 .channel()
2043 .send_no_result(
2044 "setInputFiles",
2045 serde_json::json!({
2046 "selector": selector,
2047 "strict": true,
2048 "timeout": crate::DEFAULT_TIMEOUT_MS, "payloads": []
2050 }),
2051 )
2052 .await;
2053 }
2054
2055 let mut file_objects = Vec::new();
2057 for file_path in files {
2058 let mut file_handle = std::fs::File::open(file_path)?;
2059 let mut buffer = Vec::new();
2060 file_handle.read_to_end(&mut buffer)?;
2061
2062 let base64_content = general_purpose::STANDARD.encode(&buffer);
2063 let file_name = file_path
2064 .file_name()
2065 .and_then(|n| n.to_str())
2066 .ok_or_else(|| {
2067 crate::error::Error::InvalidArgument("Invalid file path".to_string())
2068 })?;
2069
2070 file_objects.push(serde_json::json!({
2071 "name": file_name,
2072 "buffer": base64_content
2073 }));
2074 }
2075
2076 self.channel()
2077 .send_no_result(
2078 "setInputFiles",
2079 serde_json::json!({
2080 "selector": selector,
2081 "strict": true,
2082 "timeout": crate::DEFAULT_TIMEOUT_MS, "payloads": file_objects
2084 }),
2085 )
2086 .await
2087 }
2088
2089 pub(crate) async fn locator_set_input_files_payload(
2090 &self,
2091 selector: &str,
2092 file: crate::protocol::FilePayload,
2093 ) -> Result<()> {
2094 use base64::{Engine as _, engine::general_purpose};
2095
2096 let base64_content = general_purpose::STANDARD.encode(&file.buffer);
2098
2099 self.channel()
2100 .send_no_result(
2101 "setInputFiles",
2102 serde_json::json!({
2103 "selector": selector,
2104 "strict": true,
2105 "timeout": crate::DEFAULT_TIMEOUT_MS,
2106 "payloads": [{
2107 "name": file.name,
2108 "mimeType": file.mime_type,
2109 "buffer": base64_content
2110 }]
2111 }),
2112 )
2113 .await
2114 }
2115
2116 pub(crate) async fn locator_set_input_files_payload_multiple(
2117 &self,
2118 selector: &str,
2119 files: &[crate::protocol::FilePayload],
2120 ) -> Result<()> {
2121 use base64::{Engine as _, engine::general_purpose};
2122
2123 if files.is_empty() {
2125 return self
2126 .channel()
2127 .send_no_result(
2128 "setInputFiles",
2129 serde_json::json!({
2130 "selector": selector,
2131 "strict": true,
2132 "timeout": crate::DEFAULT_TIMEOUT_MS,
2133 "payloads": []
2134 }),
2135 )
2136 .await;
2137 }
2138
2139 let file_objects: Vec<_> = files
2141 .iter()
2142 .map(|file| {
2143 let base64_content = general_purpose::STANDARD.encode(&file.buffer);
2144 serde_json::json!({
2145 "name": file.name,
2146 "mimeType": file.mime_type,
2147 "buffer": base64_content
2148 })
2149 })
2150 .collect();
2151
2152 self.channel()
2153 .send_no_result(
2154 "setInputFiles",
2155 serde_json::json!({
2156 "selector": selector,
2157 "strict": true,
2158 "timeout": crate::DEFAULT_TIMEOUT_MS,
2159 "payloads": file_objects
2160 }),
2161 )
2162 .await
2163 }
2164
2165 pub(crate) async fn locator_aria_snapshot(
2172 &self,
2173 selector: &str,
2174 options: Option<&crate::protocol::AriaSnapshotOptions>,
2175 ) -> Result<String> {
2176 let timeout = options
2177 .and_then(|o| o.timeout)
2178 .unwrap_or(crate::DEFAULT_TIMEOUT_MS);
2179 self.aria_snapshot_raw(selector, timeout, options).await
2180 }
2181
2182 pub(crate) async fn aria_snapshot_raw(
2183 &self,
2184 selector: &str,
2185 timeout: f64,
2186 options: Option<&crate::protocol::AriaSnapshotOptions>,
2187 ) -> Result<String> {
2188 #[derive(Deserialize)]
2189 struct AriaSnapshotResponse {
2190 snapshot: String,
2191 }
2192
2193 let mut params = serde_json::json!({
2194 "selector": selector,
2195 "timeout": timeout,
2196 });
2197 if let Some(opts) = options {
2198 if let Some(mode) = opts.mode {
2199 params["mode"] = serde_json::Value::String(mode.as_str().to_string());
2200 }
2201 if let Some(depth) = opts.depth {
2202 params["depth"] = serde_json::Value::from(depth);
2203 }
2204 if let Some(boxes) = opts.boxes {
2205 params["boxes"] = serde_json::Value::Bool(boxes);
2206 }
2207 }
2208
2209 let response: AriaSnapshotResponse = self.channel().send("ariaSnapshot", params).await?;
2210 Ok(response.snapshot)
2211 }
2212
2213 pub(crate) async fn locator_aria_snapshot_json(
2214 &self,
2215 selector: &str,
2216 options: Option<&crate::protocol::AriaSnapshotOptions>,
2217 ) -> Result<serde_json::Value> {
2218 let timeout = options
2219 .and_then(|o| o.timeout)
2220 .unwrap_or(crate::DEFAULT_TIMEOUT_MS);
2221 self.aria_snapshot_json_raw(selector, timeout, options)
2222 .await
2223 }
2224
2225 pub(crate) async fn aria_snapshot_json_raw(
2226 &self,
2227 selector: &str,
2228 timeout: f64,
2229 options: Option<&crate::protocol::AriaSnapshotOptions>,
2230 ) -> Result<serde_json::Value> {
2231 #[derive(Deserialize)]
2232 struct AriaSnapshotJsonResponse {
2233 snapshot: serde_json::Value,
2234 }
2235
2236 let mut params = serde_json::json!({
2237 "selector": selector,
2238 "timeout": timeout,
2239 });
2240 if let Some(opts) = options {
2241 if let Some(mode) = opts.mode {
2242 params["mode"] = serde_json::Value::String(mode.as_str().to_string());
2243 }
2244 if let Some(depth) = opts.depth {
2245 params["depth"] = serde_json::Value::from(depth);
2246 }
2247 if let Some(boxes) = opts.boxes {
2248 params["boxes"] = serde_json::Value::Bool(boxes);
2249 }
2250 }
2251
2252 let response: AriaSnapshotJsonResponse =
2253 self.channel().send("ariaSnapshotJSON", params).await?;
2254 Ok(response.snapshot)
2255 }
2256
2257 pub(crate) async fn frame_resolve_selector(&self, selector: &str) -> Result<String> {
2263 #[derive(Deserialize)]
2264 struct ResolveSelectorResponse {
2265 #[serde(rename = "resolvedSelector")]
2266 resolved_selector: String,
2267 }
2268
2269 let response: ResolveSelectorResponse = self
2270 .channel()
2271 .send(
2272 "resolveSelector",
2273 serde_json::json!({
2274 "selector": selector,
2275 }),
2276 )
2277 .await?;
2278
2279 Ok(response.resolved_selector)
2280 }
2281
2282 pub(crate) async fn locator_highlight(
2289 &self,
2290 selector: &str,
2291 style: Option<&str>,
2292 ) -> Result<()> {
2293 let mut params = serde_json::json!({ "selector": selector });
2294 if let Some(style) = style {
2295 params["style"] = serde_json::Value::String(style.to_string());
2296 }
2297 self.channel().send_no_result("highlight", params).await
2298 }
2299
2300 pub(crate) async fn frame_evaluate_expression(&self, expression: &str) -> Result<()> {
2304 let params = serde_json::json!({
2305 "expression": expression,
2306 "arg": {
2307 "value": {"v": "null"},
2308 "handles": []
2309 }
2310 });
2311
2312 let _: serde_json::Value = self.channel().send("evaluateExpression", params).await?;
2313 Ok(())
2314 }
2315
2316 pub(crate) async fn frame_evaluate_expression_value(&self, expression: &str) -> Result<String> {
2328 let params = serde_json::json!({
2329 "expression": expression,
2330 "arg": {
2331 "value": {"v": "null"},
2332 "handles": []
2333 }
2334 });
2335
2336 #[derive(Deserialize)]
2337 struct EvaluateResult {
2338 value: serde_json::Value,
2339 }
2340
2341 let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
2342
2343 match &result.value {
2350 Value::Object(map) => {
2351 if let Some(s) = map.get("s").and_then(|v| v.as_str()) {
2352 Ok(s.to_string())
2354 } else if let Some(n) = map.get("n") {
2355 Ok(n.to_string())
2357 } else if let Some(b) = map.get("b").and_then(|v| v.as_bool()) {
2358 Ok(b.to_string())
2360 } else if let Some(v) = map.get("v").and_then(|v| v.as_str()) {
2361 Ok(v.to_string())
2363 } else {
2364 Ok(result.value.to_string())
2366 }
2367 }
2368 _ => {
2369 Ok(result.value.to_string())
2371 }
2372 }
2373 }
2374
2375 #[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
2415 pub async fn evaluate<T: serde::Serialize>(
2416 &self,
2417 expression: &str,
2418 arg: Option<&T>,
2419 ) -> Result<Value> {
2420 let serialized_arg = match arg {
2422 Some(a) => serialize_argument(a),
2423 None => serialize_null(),
2424 };
2425
2426 let params = serde_json::json!({
2428 "expression": expression,
2429 "arg": serialized_arg
2430 });
2431
2432 #[derive(Deserialize)]
2434 struct EvaluateResult {
2435 value: serde_json::Value,
2436 }
2437
2438 let result: EvaluateResult = self.channel().send("evaluateExpression", params).await?;
2439
2440 Ok(parse_result(&result.value))
2442 }
2443
2444 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
2484 pub async fn add_style_tag(
2485 &self,
2486 options: crate::protocol::page::AddStyleTagOptions,
2487 ) -> Result<Arc<crate::protocol::ElementHandle>> {
2488 options.validate()?;
2490
2491 let mut params = serde_json::json!({});
2493
2494 if let Some(content) = &options.content {
2495 params["content"] = serde_json::json!(content);
2496 }
2497
2498 if let Some(url) = &options.url {
2499 params["url"] = serde_json::json!(url);
2500 }
2501
2502 if let Some(path) = &options.path {
2503 let css_content = tokio::fs::read_to_string(path).await.map_err(|e| {
2505 Error::InvalidArgument(format!("Failed to read CSS file '{}': {}", path, e))
2506 })?;
2507 params["content"] = serde_json::json!(css_content);
2508 }
2509
2510 #[derive(Deserialize)]
2511 struct AddStyleTagResponse {
2512 element: serde_json::Value,
2513 }
2514
2515 let response: AddStyleTagResponse = self.channel().send("addStyleTag", params).await?;
2516
2517 let guid = response.element["guid"].as_str().ok_or_else(|| {
2518 Error::ProtocolError("Element GUID missing in addStyleTag response".to_string())
2519 })?;
2520
2521 let connection = self.base.connection();
2522 let handle: crate::protocol::ElementHandle = connection
2523 .get_typed::<crate::protocol::ElementHandle>(guid)
2524 .await?;
2525
2526 Ok(Arc::new(handle))
2527 }
2528
2529 pub(crate) async fn locator_dispatch_event(
2537 &self,
2538 selector: &str,
2539 type_: &str,
2540 event_init: Option<serde_json::Value>,
2541 ) -> Result<()> {
2542 let event_init_serialized = match event_init {
2545 Some(v) => serialize_argument(&v),
2546 None => serde_json::json!({"value": {"v": "undefined"}, "handles": []}),
2547 };
2548
2549 let params = serde_json::json!({
2550 "selector": selector,
2551 "type": type_,
2552 "eventInit": event_init_serialized,
2553 "strict": true,
2554 "timeout": crate::DEFAULT_TIMEOUT_MS
2555 });
2556
2557 self.channel().send_no_result("dispatchEvent", params).await
2558 }
2559
2560 pub(crate) async fn locator_bounding_box(
2570 &self,
2571 selector: &str,
2572 ) -> Result<Option<crate::protocol::locator::BoundingBox>> {
2573 let element = self.query_selector(selector).await?;
2574 match element {
2575 Some(handle) => handle.bounding_box().await,
2576 None => Ok(None),
2577 }
2578 }
2579
2580 pub(crate) async fn locator_scroll_into_view_if_needed(&self, selector: &str) -> Result<()> {
2587 let element = self.query_selector(selector).await?;
2588 match element {
2589 Some(handle) => handle.scroll_into_view_if_needed().await,
2590 None => Err(crate::error::Error::ElementNotFound(format!(
2591 "Element not found: {}",
2592 selector
2593 ))),
2594 }
2595 }
2596
2597 pub(crate) async fn frame_expect(
2603 &self,
2604 selector: &str,
2605 expression: &str,
2606 expected_value: serde_json::Value,
2607 is_not: bool,
2608 timeout_ms: f64,
2609 ) -> Result<()> {
2610 let params = serde_json::json!({
2611 "selector": selector,
2612 "expression": expression,
2613 "expectedValue": expected_value,
2614 "isNot": is_not,
2615 "timeout": timeout_ms
2616 });
2617
2618 let result: serde_json::Value = self.channel().send("expect", params).await?;
2626
2627 if crate::server::error_parsing::legacy_expect_verdict(&result, is_not) == Some(false) {
2635 return Err(crate::error::Error::AssertionFailed(format!(
2636 "Assertion failed for selector '{selector}' ({expression}). \
2637 Reported by a pre-1.61 Playwright server, which does not send \
2638 assertion details; connect to a version-matched server for a \
2639 fuller diagnostic."
2640 )));
2641 }
2642 Ok(())
2643 }
2644
2645 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
2655 pub async fn add_script_tag(
2656 &self,
2657 options: crate::protocol::page::AddScriptTagOptions,
2658 ) -> Result<Arc<crate::protocol::ElementHandle>> {
2659 options.validate()?;
2661
2662 let mut params = serde_json::json!({});
2664
2665 if let Some(content) = &options.content {
2666 params["content"] = serde_json::json!(content);
2667 }
2668
2669 if let Some(url) = &options.url {
2670 params["url"] = serde_json::json!(url);
2671 }
2672
2673 if let Some(path) = &options.path {
2674 let js_content = tokio::fs::read_to_string(path).await.map_err(|e| {
2676 Error::InvalidArgument(format!("Failed to read JS file '{}': {}", path, e))
2677 })?;
2678 params["content"] = serde_json::json!(js_content);
2679 }
2680
2681 if let Some(type_) = &options.type_ {
2682 params["type"] = serde_json::json!(type_);
2683 }
2684
2685 #[derive(Deserialize)]
2686 struct AddScriptTagResponse {
2687 element: serde_json::Value,
2688 }
2689
2690 let response: AddScriptTagResponse = self.channel().send("addScriptTag", params).await?;
2691
2692 let guid = response.element["guid"].as_str().ok_or_else(|| {
2693 Error::ProtocolError("Element GUID missing in addScriptTag response".to_string())
2694 })?;
2695
2696 let connection = self.base.connection();
2697 let handle: crate::protocol::ElementHandle = connection
2698 .get_typed::<crate::protocol::ElementHandle>(guid)
2699 .await?;
2700
2701 Ok(Arc::new(handle))
2702 }
2703}
2704
2705impl ChannelOwner for Frame {
2706 fn guid(&self) -> &str {
2707 self.base.guid()
2708 }
2709
2710 fn type_name(&self) -> &str {
2711 self.base.type_name()
2712 }
2713
2714 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
2715 self.base.parent()
2716 }
2717
2718 fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
2719 self.base.connection()
2720 }
2721
2722 fn initializer(&self) -> &Value {
2723 self.base.initializer()
2724 }
2725
2726 fn channel(&self) -> &Channel {
2727 self.base.channel()
2728 }
2729
2730 fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
2731 if let Ok(mut guard) = self.page.lock() {
2735 *guard = None;
2736 }
2737 self.base.dispose(reason)
2738 }
2739
2740 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
2741 self.base.adopt(child)
2742 }
2743
2744 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
2745 self.base.add_child(guid, child)
2746 }
2747
2748 fn remove_child(&self, guid: &str) {
2749 self.base.remove_child(guid)
2750 }
2751
2752 fn on_event(&self, method: &str, params: Value) {
2753 match method {
2754 "navigated" => {
2755 if let Some(url_value) = params.get("url")
2757 && let Some(url_str) = url_value.as_str()
2758 {
2759 if let Ok(mut url) = self.url.write() {
2761 *url = url_str.to_string();
2762 }
2763 }
2764 let self_clone = self.clone();
2766 crate::task::spawn(async move {
2767 if let Some(page) = self_clone.page() {
2768 page.trigger_framenavigated_event(self_clone).await;
2769 }
2770 });
2771 }
2772 "loadstate" => {
2773 if let Some(add) = params.get("add").and_then(|v| v.as_str())
2778 && add == "load"
2779 && self.parent_frame_guid.is_none()
2780 {
2781 let self_clone = self.clone();
2782 crate::task::spawn(async move {
2783 if let Some(page) = self_clone.page() {
2784 page.trigger_load_event().await;
2785 }
2786 });
2787 }
2788 }
2789 "detached" => {
2790 if let Ok(mut flag) = self.is_detached.write() {
2792 *flag = true;
2793 }
2794 }
2795 _ => {
2796 }
2798 }
2799 }
2800
2801 fn was_collected(&self) -> bool {
2802 self.base.was_collected()
2803 }
2804
2805 fn as_any(&self) -> &dyn Any {
2806 self
2807 }
2808}
2809
2810impl std::fmt::Debug for Frame {
2811 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2812 f.debug_struct("Frame").field("guid", &self.guid()).finish()
2813 }
2814}
2815
2816#[cfg(test)]
2817mod content_options_tests {
2818 use super::*;
2819 use crate::protocol_spec::{FRAME, command_parameters, keys};
2820 use serde_json::json;
2821
2822 #[test]
2823 fn content_options_send_only_what_is_set_under_the_driver_spelling() {
2824 assert_eq!(
2825 serde_json::to_value(ContentOptions::default()).unwrap(),
2826 json!({})
2827 );
2828 let sent = serde_json::to_value(ContentOptions::default().include_shadow(true)).unwrap();
2829 assert_eq!(sent, json!({"includeShadow": true}));
2830 assert!(keys(&sent).is_subset(&command_parameters(FRAME, "content")));
2831 }
2832}