use crate::core::engine::{EngineAdapter, EngineError};
use crate::core::navigation::is_navigation_error;
use std::time::Duration;
pub async fn wait_for_url_condition<F>(
adapter: &dyn EngineAdapter,
target_url: &str,
_description: Option<&str>,
polling_interval: Duration,
custom_check: Option<F>,
) -> Result<bool, EngineError>
where
F: Fn(&str) -> bool,
{
loop {
if let Some(ref check) = custom_check {
let current_url = adapter.url().await?;
if check(¤t_url) {
return Ok(true);
}
}
let current_url = match adapter.url().await {
Ok(url) => url,
Err(e) if is_navigation_error(&e.to_string()) => {
tokio::time::sleep(polling_interval).await;
continue;
}
Err(e) => return Err(e),
};
if current_url.starts_with(target_url) {
return Ok(true);
}
tokio::time::sleep(polling_interval).await;
}
}
pub async fn install_click_listener(
adapter: &dyn EngineAdapter,
button_text: &str,
storage_key: &str,
) -> Result<bool, EngineError> {
let script = format!(
r#"
(function() {{
document.addEventListener('click', (event) => {{
let element = event.target;
while (element && element !== document.body) {{
const elementText = element.textContent?.trim() || '';
if (elementText === '{}' ||
((element.tagName === 'A' || element.tagName === 'BUTTON') &&
elementText.includes('{}'))) {{
console.log('[Click Listener] Detected click on {} button!');
window.sessionStorage.setItem('{}', 'true');
break;
}}
element = element.parentElement;
}}
}}, true);
}})()
"#,
button_text.replace('\'', "\\'"),
button_text.replace('\'', "\\'"),
button_text.replace('\'', "\\'"),
storage_key.replace('\'', "\\'")
);
match adapter.evaluate(&script).await {
Ok(_) => Ok(true),
Err(e) if is_navigation_error(&e.to_string()) => Ok(false),
Err(e) => Err(e),
}
}
pub async fn check_and_clear_flag(
adapter: &dyn EngineAdapter,
storage_key: &str,
) -> Result<bool, EngineError> {
let script = format!(
r#"
(function() {{
const flag = window.sessionStorage.getItem('{}');
if (flag === 'true') {{
window.sessionStorage.removeItem('{}');
return true;
}}
return false;
}})()
"#,
storage_key.replace('\'', "\\'"),
storage_key.replace('\'', "\\'")
);
match adapter.evaluate(&script).await {
Ok(value) => Ok(value.as_bool().unwrap_or(false)),
Err(e) if is_navigation_error(&e.to_string()) => Ok(false),
Err(e) => Err(e),
}
}
pub async fn find_toggle_button(
adapter: &dyn EngineAdapter,
data_qa_selectors: &[&str],
text_to_find: Option<&str>,
element_types: &[&str],
) -> Result<Option<String>, EngineError> {
for selector in data_qa_selectors {
let count = adapter.count(selector).await?;
if count > 0 {
return Ok(Some(selector.to_string()));
}
}
if let Some(text) = text_to_find {
for element_type in element_types {
let xpath = format!(
"//{}[contains(text(), '{}')]",
element_type,
text.replace('\'', "\\'")
);
let script = format!(
r#"
(function() {{
const result = document.evaluate('{}', document, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
return result.snapshotLength;
}})()
"#,
xpath.replace('\'', "\\'")
);
match adapter.evaluate(&script).await {
Ok(value) => {
if let Some(count) = value.as_u64() {
if count > 0 {
return Ok(Some(xpath));
}
}
}
Err(_) => continue,
}
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn install_click_listener_script_escapes_quotes() {
let _button_text = "Click me";
let _storage_key = "button_clicked";
}
#[test]
fn check_and_clear_flag_script_escapes_quotes() {
let _storage_key = "test_key";
}
}