use crate::core::engine::{EngineAdapter, EngineError};
pub async fn text_content(
adapter: &dyn EngineAdapter,
selector: &str,
) -> Result<Option<String>, EngineError> {
adapter.text_content(selector).await
}
pub async fn input_value(
adapter: &dyn EngineAdapter,
selector: &str,
) -> Result<Option<String>, EngineError> {
adapter.input_value(selector).await
}
pub async fn get_attribute(
adapter: &dyn EngineAdapter,
selector: &str,
attribute: &str,
) -> Result<Option<String>, EngineError> {
adapter.get_attribute(selector, attribute).await
}
pub async fn is_element_empty(
adapter: &dyn EngineAdapter,
selector: &str,
) -> Result<bool, EngineError> {
let value = adapter.input_value(selector).await?;
Ok(value.is_none_or(|v| v.trim().is_empty()))
}
#[derive(Debug, Clone)]
pub struct ElementLogInfo {
pub tag_name: String,
pub text_preview: String,
pub attributes: Vec<(String, String)>,
}
impl std::fmt::Display for ElementLogInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<{}", self.tag_name)?;
for (name, value) in &self.attributes {
if !value.is_empty() {
write!(f, " {}=\"{}\"", name, value)?;
}
}
write!(f, ">")?;
if !self.text_preview.is_empty() {
write!(f, "{}", self.text_preview)?;
}
write!(f, "</{}>", self.tag_name)
}
}
pub fn truncate_for_preview(s: &str, max_len: usize) -> String {
let trimmed = s.trim();
if trimmed.len() <= max_len {
trimmed.to_string()
} else {
format!("{}...", &trimmed[..max_len])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_for_preview_short_string() {
assert_eq!(truncate_for_preview("hello", 10), "hello");
}
#[test]
fn truncate_for_preview_long_string() {
assert_eq!(truncate_for_preview("hello world", 5), "hello...");
}
#[test]
fn truncate_for_preview_trims_whitespace() {
assert_eq!(truncate_for_preview(" hello ", 10), "hello");
}
#[test]
fn truncate_for_preview_exact_length() {
assert_eq!(truncate_for_preview("hello", 5), "hello");
}
#[test]
fn element_log_info_display() {
let info = ElementLogInfo {
tag_name: "button".to_string(),
text_preview: "Click me".to_string(),
attributes: vec![
("id".to_string(), "submit-btn".to_string()),
("class".to_string(), "primary".to_string()),
],
};
let display = format!("{}", info);
assert!(display.contains("button"));
assert!(display.contains("submit-btn"));
assert!(display.contains("Click me"));
}
#[test]
fn element_log_info_display_no_text() {
let info = ElementLogInfo {
tag_name: "input".to_string(),
text_preview: String::new(),
attributes: vec![("type".to_string(), "text".to_string())],
};
let display = format!("{}", info);
assert!(display.contains("input"));
assert!(display.contains("type"));
assert!(display.contains("text"));
}
#[test]
fn element_log_info_display_empty_attribute() {
let info = ElementLogInfo {
tag_name: "div".to_string(),
text_preview: "content".to_string(),
attributes: vec![
("id".to_string(), "test".to_string()),
("class".to_string(), String::new()),
],
};
let display = format!("{}", info);
assert!(display.contains("id=\"test\""));
assert!(!display.contains("class="));
}
}