viewpoint_core/page/locator/evaluation/
evaluate.rs1use serde::Deserialize;
4use tracing::{debug, instrument};
5use viewpoint_cdp::protocol::dom::{BackendNodeId, ResolveNodeParams, ResolveNodeResult};
6use viewpoint_js::js;
7
8use super::super::Locator;
9use super::super::Selector;
10use crate::error::LocatorError;
11
12impl Locator<'_> {
13 #[instrument(level = "debug", skip(self), fields(selector = ?self.selector))]
57 pub async fn evaluate<T: serde::de::DeserializeOwned>(
58 &self,
59 expression: &str,
60 ) -> Result<T, LocatorError> {
61 self.wait_for_actionable().await?;
62
63 debug!(expression, "Evaluating expression on element");
64
65 if let Selector::Ref(ref_str) = &self.selector {
67 let backend_node_id = self.page.get_backend_node_id_for_ref(ref_str)?;
68 return self
69 .evaluate_by_backend_id(backend_node_id, expression)
70 .await;
71 }
72
73 if let Selector::BackendNodeId(backend_node_id) = &self.selector {
75 return self
76 .evaluate_by_backend_id(*backend_node_id, expression)
77 .await;
78 }
79
80 let selector_expr = self.selector.to_js_expression();
81 let js = js! {
82 (function() {
83 const elements = @{selector_expr};
84 if (elements.length === 0) return { __viewpoint_error: "Element not found" };
85
86 const element = elements[0];
87 try {
88 const result = (function(element) { return @{expression}; })(element);
89 return { __viewpoint_result: result };
90 } catch (e) {
91 return { __viewpoint_error: e.toString() };
92 }
93 })()
94 };
95
96 let result = self.evaluate_js(&js).await?;
97
98 if let Some(error) = result.get("__viewpoint_error").and_then(|v| v.as_str()) {
99 return Err(LocatorError::EvaluationError(error.to_string()));
100 }
101
102 let value = result
103 .get("__viewpoint_result")
104 .cloned()
105 .unwrap_or(serde_json::Value::Null);
106 serde_json::from_value(value).map_err(|e| {
107 LocatorError::EvaluationError(format!("Failed to deserialize result: {e}"))
108 })
109 }
110
111 pub(super) async fn evaluate_by_backend_id<T: serde::de::DeserializeOwned>(
113 &self,
114 backend_node_id: BackendNodeId,
115 expression: &str,
116 ) -> Result<T, LocatorError> {
117 let result: ResolveNodeResult = self
119 .page
120 .connection()
121 .send_command(
122 "DOM.resolveNode",
123 Some(ResolveNodeParams {
124 node_id: None,
125 backend_node_id: Some(backend_node_id),
126 object_group: Some("viewpoint-evaluate".to_string()),
127 execution_context_id: None,
128 }),
129 Some(self.page.session_id()),
130 )
131 .await
132 .map_err(|_| {
133 LocatorError::NotFound(format!(
134 "Could not resolve backend node ID {backend_node_id}: element may no longer exist"
135 ))
136 })?;
137
138 let object_id = result.object.object_id.ok_or_else(|| {
139 LocatorError::NotFound(format!(
140 "No object ID for backend node ID {backend_node_id}"
141 ))
142 })?;
143
144 #[derive(Debug, Deserialize)]
146 struct CallResult {
147 result: viewpoint_cdp::protocol::runtime::RemoteObject,
148 #[serde(rename = "exceptionDetails")]
149 exception_details: Option<viewpoint_cdp::protocol::runtime::ExceptionDetails>,
150 }
151
152 let js_fn = js! {
155 (function() {
156 const element = this;
157 try {
158 const result = (function(element) { return @{expression}; })(element);
159 return { __viewpoint_result: result };
160 } catch (e) {
161 return { __viewpoint_error: e.toString() };
162 }
163 })
164 };
165 let js_fn = js_fn.trim_start_matches('(').trim_end_matches(')');
167
168 let call_result: CallResult = self
169 .page
170 .connection()
171 .send_command(
172 "Runtime.callFunctionOn",
173 Some(serde_json::json!({
174 "objectId": object_id,
175 "functionDeclaration": js_fn,
176 "returnByValue": true
177 })),
178 Some(self.page.session_id()),
179 )
180 .await?;
181
182 let _ = self
184 .page
185 .connection()
186 .send_command::<_, serde_json::Value>(
187 "Runtime.releaseObject",
188 Some(serde_json::json!({ "objectId": object_id })),
189 Some(self.page.session_id()),
190 )
191 .await;
192
193 if let Some(exception) = call_result.exception_details {
194 return Err(LocatorError::EvaluationError(exception.text));
195 }
196
197 let value = call_result
198 .result
199 .value
200 .ok_or_else(|| LocatorError::EvaluationError("No result from evaluate".to_string()))?;
201
202 if let Some(error) = value.get("__viewpoint_error").and_then(|v| v.as_str()) {
203 return Err(LocatorError::EvaluationError(error.to_string()));
204 }
205
206 let result_value = value
207 .get("__viewpoint_result")
208 .cloned()
209 .unwrap_or(serde_json::Value::Null);
210 serde_json::from_value(result_value).map_err(|e| {
211 LocatorError::EvaluationError(format!("Failed to deserialize result: {e}"))
212 })
213 }
214}