use async_trait::async_trait;
use serde_json::Value;
#[async_trait]
pub trait BrowserBackend: Send + Sync {
async fn navigate(&self, url: &str) -> Result<(), String>;
async fn get_url(&self) -> Result<String, String>;
async fn get_title(&self) -> Result<String, String>;
async fn get_content(&self) -> Result<String, String>;
async fn evaluate(&self, script: &str) -> Result<Value, String>;
async fn screenshot(&self) -> Result<String, String>;
async fn click(&self, selector: &str) -> Result<(), String>;
async fn fill(&self, selector: &str, value: &str) -> Result<(), String>;
async fn close(&mut self) -> Result<(), String>;
async fn back(&self) -> Result<(), String>;
async fn forward(&self) -> Result<(), String>;
async fn reload(&self) -> Result<(), String>;
async fn get_cookies(&self) -> Result<Value, String>;
fn backend_type(&self) -> &str;
fn supports(&self, feature: &str) -> bool {
match feature {
"navigate" | "evaluate" | "screenshot" | "click" | "fill" => true,
"screencast" | "tracing" | "network_intercept" | "cdp" => self.backend_type() == "cdp",
_ => false,
}
}
fn unsupported_error(&self, action: &str) -> String {
format!(
"Action '{}' is not supported on the {} backend",
action,
self.backend_type()
)
}
}
pub struct WebDriverBackend {
client: super::client::WebDriverClient,
}
impl WebDriverBackend {
pub fn new(client: super::client::WebDriverClient) -> Self {
Self { client }
}
}
#[async_trait]
impl BrowserBackend for WebDriverBackend {
async fn navigate(&self, url: &str) -> Result<(), String> {
self.client.navigate(url).await
}
async fn get_url(&self) -> Result<String, String> {
self.client.get_url().await
}
async fn get_title(&self) -> Result<String, String> {
self.client.get_title().await
}
async fn get_content(&self) -> Result<String, String> {
self.client.get_page_source().await
}
async fn evaluate(&self, script: &str) -> Result<Value, String> {
self.client.execute_script(script, vec![]).await
}
async fn screenshot(&self) -> Result<String, String> {
self.client.screenshot().await
}
async fn click(&self, selector: &str) -> Result<(), String> {
let element_id = self.client.find_element("css selector", selector).await?;
self.client.click_element(&element_id).await
}
async fn fill(&self, selector: &str, value: &str) -> Result<(), String> {
let element_id = self.client.find_element("css selector", selector).await?;
self.client.clear_element(&element_id).await?;
self.client.send_keys(&element_id, value).await
}
async fn close(&mut self) -> Result<(), String> {
self.client.delete_session().await
}
async fn back(&self) -> Result<(), String> {
self.client.back().await
}
async fn forward(&self) -> Result<(), String> {
self.client.forward().await
}
async fn reload(&self) -> Result<(), String> {
self.client.refresh().await
}
async fn get_cookies(&self) -> Result<Value, String> {
self.client.get_cookies().await
}
fn backend_type(&self) -> &str {
"webdriver"
}
}
pub const WEBDRIVER_UNSUPPORTED_ACTIONS: &[&str] = &[
"screencast_start",
"screencast_stop",
"trace_start",
"trace_stop",
"profiler_start",
"profiler_stop",
"route",
"unroute",
"expose",
"addscript",
"addinitscript",
"network",
"har_start",
"har_stop",
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unsupported_actions() {
assert!(WEBDRIVER_UNSUPPORTED_ACTIONS.contains(&"screencast_start"));
assert!(WEBDRIVER_UNSUPPORTED_ACTIONS.contains(&"trace_start"));
assert!(!WEBDRIVER_UNSUPPORTED_ACTIONS.contains(&"navigate"));
}
}