use tracing::{debug, instrument};
use viewpoint_cdp::protocol::dom::{BackendNodeId, DescribeNodeParams, DescribeNodeResult, ResolveNodeParams, ResolveNodeResult};
use super::locator::ElementHandle;
use super::Page;
use crate::error::{LocatorError, PageError};
pub fn parse_ref(ref_str: &str) -> Result<BackendNodeId, LocatorError> {
if !ref_str.starts_with('e') {
return Err(LocatorError::EvaluationError(format!(
"Invalid ref format: expected 'e{{backendNodeId}}', got '{ref_str}'"
)));
}
ref_str[1..]
.parse::<BackendNodeId>()
.map_err(|e| LocatorError::EvaluationError(format!("Invalid backend node ID in ref: {e}")))
}
pub fn format_ref(backend_node_id: BackendNodeId) -> String {
format!("e{backend_node_id}")
}
impl Page {
#[instrument(level = "debug", skip(self), fields(target_id = %self.target_id, ref_str = %ref_str))]
pub async fn element_from_ref(&self, ref_str: &str) -> Result<ElementHandle<'_>, LocatorError> {
if self.is_closed() {
return Err(LocatorError::PageClosed);
}
let backend_node_id = parse_ref(ref_str)?;
debug!(backend_node_id = backend_node_id, "Resolving ref to element");
let result: ResolveNodeResult = self
.connection()
.send_command(
"DOM.resolveNode",
Some(ResolveNodeParams {
node_id: None,
backend_node_id: Some(backend_node_id),
object_group: Some("viewpoint-ref".to_string()),
execution_context_id: None,
}),
Some(self.session_id()),
)
.await
.map_err(|e| {
LocatorError::NotFound(format!(
"Failed to resolve ref '{ref_str}': element may no longer exist. Error: {e}"
))
})?;
let object_id = result.object.object_id.ok_or_else(|| {
LocatorError::NotFound(format!(
"Failed to get object ID for ref '{ref_str}': element may be detached"
))
})?;
debug!(object_id = %object_id, "Resolved ref to element handle");
Ok(ElementHandle {
object_id,
page: self,
})
}
pub fn locator_from_ref(&self, ref_str: &str) -> super::Locator<'_> {
use super::locator::{Locator, Selector};
let backend_node_id = parse_ref(ref_str)
.expect("Invalid ref format. Refs must be in format 'e{backendNodeId}'");
Locator::new(self, Selector::BackendNodeId(backend_node_id))
}
pub(crate) async fn get_backend_node_id(
&self,
object_id: &str,
) -> Result<BackendNodeId, PageError> {
let result: DescribeNodeResult = self
.connection()
.send_command(
"DOM.describeNode",
Some(DescribeNodeParams {
node_id: None,
backend_node_id: None,
object_id: Some(object_id.to_string()),
depth: Some(0),
pierce: None,
}),
Some(self.session_id()),
)
.await?;
Ok(result.node.backend_node_id)
}
}