init projectmycelium

This commit is contained in:
mik-tf
2025-09-01 21:37:01 -04:00
commit b41efb0e99
319 changed files with 128160 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
//! Browser Automation Manager
//!
//! Handles browser lifecycle, navigation, and interaction for UX testing
use thirtyfour::prelude::*;
use std::path::PathBuf;
use std::time::Duration;
use tokio::time::timeout;
/// Supported browser types for testing
#[derive(Debug, Clone)]
pub enum BrowserType {
Chrome,
Firefox,
Safari,
Edge,
}
/// Browser manager for UX testing
pub struct BrowserManager {
driver: WebDriver,
base_url: String,
screenshot_dir: PathBuf,
config: super::UXTestConfig,
}
impl Clone for BrowserManager {
fn clone(&self) -> Self {
// Note: WebDriver cannot be cloned, so this creates a reference to the same driver
// In practice, we should avoid cloning BrowserManager and use references instead
panic!("BrowserManager cannot be cloned due to WebDriver limitations. Use references instead.");
}
}
impl BrowserManager {
/// Create a new browser manager
pub async fn new(config: &super::UXTestConfig) -> Result<Self, Box<dyn std::error::Error>> {
let capabilities = match config.browser_type {
BrowserType::Chrome => {
let mut caps = DesiredCapabilities::chrome();
if config.headless {
caps.add_chrome_arg("--headless")?;
}
caps.add_chrome_arg("--no-sandbox")?;
caps.add_chrome_arg("--disable-dev-shm-usage")?;
caps.add_chrome_arg("--disable-gpu")?;
caps.add_chrome_arg("--window-size=1920,1080")?;
caps
}
BrowserType::Firefox => {
let mut caps = DesiredCapabilities::firefox();
if config.headless {
caps.add_firefox_arg("--headless")?;
}
caps
}
_ => {
return Err("Browser type not yet implemented".into());
}
};
// Try to connect to existing WebDriver or start one
let driver = match WebDriver::new("http://localhost:4444", capabilities.clone()).await {
Ok(driver) => driver,
Err(_) => {
// If selenium server is not running, try local driver
WebDriver::new("http://localhost:9515", capabilities).await?
}
};
// Configure browser
driver.set_window_size(1920, 1080).await?;
driver.implicitly_wait(Duration::from_secs(10)).await?;
Ok(Self {
driver,
base_url: format!("http://localhost:{}", config.test_port),
screenshot_dir: config.screenshot_dir.clone(),
config: config.clone(),
})
}
/// Navigate to a path on the test server
pub async fn navigate_to(&self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let url = if path.starts_with("http") {
path.to_string()
} else {
format!("{}{}", self.base_url, path)
};
log::info!("Navigating to: {}", url);
timeout(
Duration::from_secs(self.config.timeout_seconds),
self.driver.goto(&url)
).await??;
// Wait for page to load
self.wait_for_page_load().await?;
Ok(())
}
/// Wait for page to be fully loaded
pub async fn wait_for_page_load(&self) -> Result<(), Box<dyn std::error::Error>> {
// Wait for document ready state
self.driver.execute("return document.readyState", vec![]).await?;
// Additional wait for any dynamic content
tokio::time::sleep(Duration::from_millis(500)).await;
Ok(())
}
/// Take a screenshot with the given name
pub async fn take_screenshot(&self, name: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
let screenshot = self.driver.screenshot_as_png().await?;
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let filename = format!("{}_{}.png", name, timestamp);
let path = self.screenshot_dir.join(filename);
std::fs::create_dir_all(&self.screenshot_dir)?;
std::fs::write(&path, screenshot)?;
log::info!("Screenshot saved: {:?}", path);
Ok(path)
}
/// Find element by CSS selector
pub async fn find_element(&self, selector: &str) -> Result<WebElement, Box<dyn std::error::Error>> {
Ok(self.driver.find(By::Css(selector)).await?)
}
/// Find elements by CSS selector
pub async fn find_elements(&self, selector: &str) -> Result<Vec<WebElement>, Box<dyn std::error::Error>> {
Ok(self.driver.find_all(By::Css(selector)).await?)
}
/// Click element by CSS selector
pub async fn click(&self, selector: &str) -> Result<(), Box<dyn std::error::Error>> {
let element = self.find_element(selector).await?;
element.scroll_into_view().await?;
element.click().await?;
Ok(())
}
/// Type text into element
pub async fn type_text(&self, selector: &str, text: &str) -> Result<(), Box<dyn std::error::Error>> {
let element = self.find_element(selector).await?;
element.clear().await?;
element.send_keys(text).await?;
Ok(())
}
/// Get text from element
pub async fn get_text(&self, selector: &str) -> Result<String, Box<dyn std::error::Error>> {
let element = self.find_element(selector).await?;
Ok(element.text().await?)
}
/// Check if element exists
pub async fn element_exists(&self, selector: &str) -> bool {
self.driver.find(By::Css(selector)).await.is_ok()
}
/// Wait for element to be visible
pub async fn wait_for_element(&self, selector: &str) -> Result<WebElement, Box<dyn std::error::Error>> {
let timeout_duration = Duration::from_secs(self.config.timeout_seconds);
timeout(timeout_duration, async {
loop {
if let Ok(element) = self.driver.find(By::Css(selector)).await {
if element.is_displayed().await.unwrap_or(false) {
return Ok(element);
}
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}).await?
}
/// Get current page title
pub async fn get_title(&self) -> Result<String, Box<dyn std::error::Error>> {
Ok(self.driver.title().await?)
}
/// Get current URL
pub async fn get_current_url(&self) -> Result<String, Box<dyn std::error::Error>> {
Ok(self.driver.current_url().await?)
}
/// Execute JavaScript
pub async fn execute_script(&self, script: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
Ok(self.driver.execute(script, vec![]).await?)
}
/// Quit the browser
pub async fn quit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.driver.quit().await?;
Ok(())
}
/// Get page source for debugging
pub async fn get_page_source(&self) -> Result<String, Box<dyn std::error::Error>> {
Ok(self.driver.source().await?)
}
/// Scroll to element
pub async fn scroll_to_element(&self, selector: &str) -> Result<(), Box<dyn std::error::Error>> {
let element = self.find_element(selector).await?;
element.scroll_into_view().await?;
Ok(())
}
/// Wait for text to appear in element
pub async fn wait_for_text(&self, selector: &str, expected_text: &str) -> Result<(), Box<dyn std::error::Error>> {
let timeout_duration = Duration::from_secs(self.config.timeout_seconds);
timeout(timeout_duration, async {
loop {
if let Ok(element) = self.driver.find(By::Css(selector)).await {
if let Ok(text) = element.text().await {
if text.contains(expected_text) {
return Ok(());
}
}
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}).await?
}
}