use std::future::Future;
use std::time::Duration;
use crate::error::AssertionError;
pub fn js_string_literal(s: &str) -> String {
let escaped = s
.replace('\\', "\\\\")
.replace('\'', "\\'")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("'{escaped}'")
}
pub async fn evaluate_js(
page: &viewpoint_core::Page,
expression: &str,
) -> Result<serde_json::Value, AssertionError> {
use viewpoint_cdp::protocol::runtime::EvaluateParams;
if page.is_closed() {
return Err(AssertionError::new(
"Page is closed",
"page to be open",
"page is closed",
));
}
let params = EvaluateParams {
expression: expression.to_string(),
object_group: None,
include_command_line_api: None,
silent: Some(true),
context_id: None,
return_by_value: Some(true),
await_promise: Some(false),
};
let result: viewpoint_cdp::protocol::runtime::EvaluateResult = page
.connection()
.send_command("Runtime.evaluate", Some(params), Some(page.session_id()))
.await
.map_err(|e| {
AssertionError::new("Failed to evaluate JavaScript", "success", e.to_string())
})?;
if let Some(exception) = result.exception_details {
return Err(AssertionError::new(
"JavaScript error",
"no error",
exception.text,
));
}
result.result.value.ok_or_else(|| {
AssertionError::new("No result from JavaScript", "a value", "null/undefined")
})
}
pub async fn get_input_value(
locator: &viewpoint_core::Locator<'_>,
) -> Result<String, AssertionError> {
let page = locator.page();
let selector = locator.selector();
let js = format!(
r"(function() {{
const elements = {};
if (elements.length === 0) return {{ found: false }};
const el = elements[0];
return {{ found: true, value: el.value || '' }};
}})()",
selector.to_js_expression()
);
let result = evaluate_js(page, &js).await?;
let found = result
.get("found")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if !found {
return Err(AssertionError::new(
"Element not found",
"element to exist",
"element not found",
));
}
Ok(result
.get("value")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string())
}
pub async fn get_selected_values(
locator: &viewpoint_core::Locator<'_>,
) -> Result<Vec<String>, AssertionError> {
let page = locator.page();
let selector = locator.selector();
let js = format!(
r"(function() {{
const elements = {};
if (elements.length === 0) return {{ found: false }};
const el = elements[0];
if (el.tagName.toLowerCase() !== 'select') {{
return {{ found: true, values: [el.value || ''] }};
}}
const values = [];
for (const opt of el.selectedOptions) {{
values.push(opt.value);
}}
return {{ found: true, values: values }};
}})()",
selector.to_js_expression()
);
let result = evaluate_js(page, &js).await?;
let found = result
.get("found")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if !found {
return Err(AssertionError::new(
"Element not found",
"element to exist",
"element not found",
));
}
Ok(result
.get("values")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str())
.map(std::string::ToString::to_string)
.collect()
})
.unwrap_or_default())
}
pub async fn get_attribute(
locator: &viewpoint_core::Locator<'_>,
name: &str,
) -> Result<Option<String>, AssertionError> {
let page = locator.page();
let selector = locator.selector();
let js = format!(
r"(function() {{
const elements = {};
if (elements.length === 0) return {{ found: false }};
const el = elements[0];
const value = el.getAttribute({});
return {{ found: true, value: value }};
}})()",
selector.to_js_expression(),
js_string_literal(name)
);
let result = evaluate_js(page, &js).await?;
let found = result
.get("found")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if !found {
return Ok(None);
}
Ok(result
.get("value")
.and_then(|v| v.as_str())
.map(String::from))
}
pub async fn is_enabled(locator: &viewpoint_core::Locator<'_>) -> Result<bool, AssertionError> {
let page = locator.page();
let selector = locator.selector();
let js = format!(
r"(function() {{
const elements = {};
if (elements.length === 0) return {{ found: false }};
const el = elements[0];
return {{ found: true, enabled: !el.disabled }};
}})()",
selector.to_js_expression()
);
let result = evaluate_js(page, &js).await?;
let found = result
.get("found")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
if !found {
return Err(AssertionError::new(
"Element not found",
"element to exist",
"element not found",
));
}
Ok(result
.get("enabled")
.and_then(serde_json::Value::as_bool)
.unwrap_or(true))
}
pub async fn retry_until<F, Fut, T>(
timeout: Duration,
is_negated: bool,
mut check_fn: F,
error_message: impl Fn(bool) -> String,
expected_value: impl Fn(bool) -> String,
actual_value: impl Fn(&T) -> String,
) -> Result<(), AssertionError>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<(bool, T), AssertionError>>,
{
let start = std::time::Instant::now();
loop {
let (matches, actual) = check_fn().await?;
let expected_match = !is_negated;
if matches == expected_match {
return Ok(());
}
if start.elapsed() >= timeout {
return Err(AssertionError::new(
error_message(is_negated),
expected_value(is_negated),
actual_value(&actual),
));
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}