use chromiumoxide::cdp::browser_protocol::input::{DispatchKeyEventParams, DispatchKeyEventType};
use chromiumoxide::keys::get_key_definition;
use chromiumoxide::Page;
use crate::error::{AuditError, Result};
pub async fn press(page: &Page, key: &str) -> Result<()> {
press_with_modifiers(page, key, 0).await
}
pub async fn press_with_modifiers(page: &Page, key: &str, modifiers: i64) -> Result<()> {
let def = get_key_definition(key).ok_or_else(|| AuditError::InteractionFailed {
reason: format!("Unknown key: {key}"),
})?;
let mut cmd = DispatchKeyEventParams::builder();
let key_down_event_type = if let Some(txt) = def.text {
cmd = cmd.text(txt);
DispatchKeyEventType::KeyDown
} else if def.key.len() == 1 {
cmd = cmd.text(def.key);
DispatchKeyEventType::KeyDown
} else {
DispatchKeyEventType::RawKeyDown
};
cmd = cmd
.key(def.key)
.code(def.code)
.windows_virtual_key_code(def.key_code)
.native_virtual_key_code(def.key_code)
.modifiers(modifiers);
let key_down = cmd
.clone()
.r#type(key_down_event_type)
.build()
.map_err(|e| AuditError::InteractionFailed {
reason: format!("KeyDown build failed: {e}"),
})?;
page.execute(key_down)
.await
.map_err(|e| AuditError::InteractionFailed {
reason: format!("KeyDown dispatch failed: {e}"),
})?;
let key_up = cmd
.r#type(DispatchKeyEventType::KeyUp)
.build()
.map_err(|e| AuditError::InteractionFailed {
reason: format!("KeyUp build failed: {e}"),
})?;
page.execute(key_up)
.await
.map_err(|e| AuditError::InteractionFailed {
reason: format!("KeyUp dispatch failed: {e}"),
})?;
Ok(())
}
pub async fn press_tab(page: &Page) -> Result<()> {
press(page, "Tab").await
}
pub async fn press_shift_tab(page: &Page) -> Result<()> {
press_with_modifiers(page, "Tab", 8).await
}
pub async fn press_enter(page: &Page) -> Result<()> {
press(page, "Enter").await
}
pub async fn press_escape(page: &Page) -> Result<()> {
press(page, "Escape").await
}
pub async fn press_arrow(page: &Page, direction: &str) -> Result<()> {
let key = arrow_key(direction).ok_or_else(|| AuditError::InteractionFailed {
reason: format!("Invalid arrow direction: {direction}"),
})?;
press(page, key).await
}
fn arrow_key(direction: &str) -> Option<&'static str> {
match direction {
"Up" => Some("ArrowUp"),
"Down" => Some("ArrowDown"),
"Left" => Some("ArrowLeft"),
"Right" => Some("ArrowRight"),
_ => None,
}
}
pub async fn type_text(page: &Page, text: &str) -> Result<()> {
for ch in text.chars() {
let s = ch.to_string();
press(page, &s).await?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::arrow_key;
#[test]
fn arrow_key_maps_supported_directions_to_w3c_key_names() {
assert_eq!(arrow_key("Up"), Some("ArrowUp"));
assert_eq!(arrow_key("Down"), Some("ArrowDown"));
assert_eq!(arrow_key("Left"), Some("ArrowLeft"));
assert_eq!(arrow_key("Right"), Some("ArrowRight"));
}
#[test]
fn arrow_key_rejects_unknown_directions() {
for direction in ["", "up", "Forward", "ArrowUp"] {
assert_eq!(arrow_key(direction), None);
}
}
}