//! UX Test Helper //! //! High-level interface for UX testing operations use crate::environment::*; use std::time::Duration; use tokio::time::timeout; /// Main UX test helper providing high-level testing operations pub struct UXTestHelper<'a> { pub browser: &'a BrowserManager, pub api_client: &'a APITestClient, pub data_manager: &'a TestDataManager, current_user: Option, } impl<'a> UXTestHelper<'a> { /// Create a new UX test helper pub fn new(environment: &'a UXTestEnvironment) -> Self { Self { browser: &environment.browser, api_client: &environment.api_client, data_manager: &environment.data_manager, current_user: None, } } // ===== AUTHENTICATION HELPERS ===== /// Register a new user pub async fn register_user(&mut self, persona: &TestPersona) -> Result<(), Box> { log::info!("Registering user: {}", persona.email); // Navigate to registration page self.browser.navigate_to("/register").await?; self.assert_page_loaded("Register").await?; // Fill registration form self.browser.type_text("input[name='email']", &persona.email).await?; self.browser.type_text("input[name='password']", &persona.password).await?; self.browser.type_text("input[name='name']", &persona.name).await?; // Submit form self.browser.click("button[type='submit']").await?; // Wait for success or error self.browser.wait_for_element(".alert, .notification, .success").await?; // Take screenshot self.browser.take_screenshot(&format!("register_{}", self.safe_filename(&persona.email))).await?; Ok(()) } /// Login as a test persona pub async fn login_as(&mut self, persona: &TestPersona) -> Result<(), Box> { log::info!("Logging in as: {}", persona.email); // Navigate to login page self.browser.navigate_to("/login").await?; self.assert_page_loaded("Login").await?; // Fill login form self.browser.type_text("input[name='email']", &persona.email).await?; self.browser.type_text("input[name='password']", &persona.password).await?; // Submit form self.browser.click("button[type='submit']").await?; // Wait for dashboard redirect or success self.browser.wait_for_element(".dashboard, .user-menu, .navbar-user").await?; // Verify authentication self.assert_user_is_authenticated().await?; // Store current user self.current_user = Some(persona.clone()); // Take screenshot self.browser.take_screenshot(&format!("login_{}", self.safe_filename(&persona.email))).await?; Ok(()) } /// Logout current user pub async fn logout(&mut self) -> Result<(), Box> { log::info!("Logging out current user"); // Look for logout link/button if self.browser.element_exists(".user-menu").await { self.browser.click(".user-menu").await?; self.browser.wait_for_element("a[href='/logout']").await?; self.browser.click("a[href='/logout']").await?; } else { // Direct navigation to logout self.browser.navigate_to("/logout").await?; } // Wait for redirect to home page self.browser.wait_for_element(".home, .login-prompt").await?; self.current_user = None; // Take screenshot self.browser.take_screenshot("logout").await?; Ok(()) } // ===== SHOPPING HELPERS ===== /// Add product to cart pub async fn add_product_to_cart(&mut self, product_id: &str) -> Result<(), Box> { log::info!("Adding product to cart: {}", product_id); // Navigate to product or find it in marketplace self.browser.navigate_to("/marketplace").await?; // Find the product let product_selector = &format!("[data-product-id='{}']", product_id); self.browser.wait_for_element(product_selector).await?; // Click add to cart button let add_to_cart_selector = &format!("{} .add-to-cart, {} button[data-action='add-to-cart']", product_selector, product_selector); self.browser.click(add_to_cart_selector).await?; // Wait for cart update notification self.browser.wait_for_element(".cart-updated, .notification, .alert-success").await?; // Take screenshot self.browser.take_screenshot(&format!("add_to_cart_{}", product_id)).await?; Ok(()) } /// Buy product directly (Buy Now) pub async fn buy_now(&mut self, product_id: &str) -> Result> { log::info!("Buying product directly: {}", product_id); // Navigate to product self.browser.navigate_to("/marketplace").await?; // Find the product let product_selector = &format!("[data-product-id='{}']", product_id); self.browser.wait_for_element(product_selector).await?; // Click buy now button let buy_now_selector = &format!("{} .buy-now, {} button[data-action='buy-now']", product_selector, product_selector); self.browser.click(buy_now_selector).await?; // Complete checkout flow self.complete_checkout_flow().await } /// Complete checkout flow pub async fn complete_checkout_flow(&mut self) -> Result> { log::info!("Completing checkout flow"); // Wait for checkout page or modal self.browser.wait_for_element(".checkout, .checkout-modal, .purchase-confirmation").await?; // If there's a confirm purchase button, click it if self.browser.element_exists("button[data-action='confirm-purchase']").await { self.browser.click("button[data-action='confirm-purchase']").await?; } // Wait for order confirmation self.browser.wait_for_element(".order-confirmation, .purchase-success").await?; // Extract order ID if available let order_id = if let Ok(order_element) = self.browser.find_element(".order-id, [data-order-id]").await { order_element.text().await.unwrap_or_else(|_| "unknown".to_string()) } else { "test-order-id".to_string() }; // Take screenshot self.browser.take_screenshot(&format!("order_confirmation_{}", order_id)).await?; Ok(order_id) } /// Navigate to cart pub async fn navigate_to_cart(&mut self) -> Result<(), Box> { let cart_url = if self.current_user.is_some() { "/dashboard/cart" } else { "/cart" }; self.browser.navigate_to(cart_url).await?; self.assert_page_loaded("Cart").await?; Ok(()) } // ===== DASHBOARD HELPERS ===== /// Navigate to dashboard section pub async fn navigate_to_dashboard_section(&mut self, section: &str) -> Result<(), Box> { let url = format!("/dashboard/{}", section); self.browser.navigate_to(&url).await?; // Wait for dashboard content self.browser.wait_for_element(".dashboard-content, .dashboard-section").await?; // Take screenshot self.browser.take_screenshot(&format!("dashboard_{}", section)).await?; Ok(()) } /// Navigate to wallet pub async fn navigate_to_wallet(&mut self) -> Result<(), Box> { self.browser.navigate_to("/dashboard/wallet").await?; self.browser.wait_for_element(".wallet-dashboard, .wallet-content").await?; // Take screenshot self.browser.take_screenshot("wallet_dashboard").await?; Ok(()) } /// Navigate to settings pub async fn navigate_to_settings(&mut self) -> Result<(), Box> { self.browser.navigate_to("/dashboard/settings").await?; self.browser.wait_for_element(".settings-form, .settings-content").await?; // Take screenshot self.browser.take_screenshot("settings_page").await?; Ok(()) } // ===== SETTINGS HELPERS ===== /// Update profile information pub async fn update_profile(&mut self, name: &str, country: &str, timezone: &str) -> Result<(), Box> { self.navigate_to_settings().await?; // Fill profile form self.browser.type_text("input[name='name']", name).await?; self.browser.type_text("select[name='country']", country).await?; self.browser.type_text("select[name='timezone']", timezone).await?; // Submit form self.browser.click("button[data-action='update-profile']").await?; // Wait for success notification self.browser.wait_for_element(".alert-success, .notification-success").await?; self.browser.take_screenshot("profile_updated").await?; Ok(()) } /// Add SSH key pub async fn add_ssh_key(&mut self, key_name: &str, public_key: &str) -> Result<(), Box> { self.navigate_to_settings().await?; // Navigate to SSH keys section if self.browser.element_exists("a[href='#ssh-keys']").await { self.browser.click("a[href='#ssh-keys']").await?; } // Fill SSH key form self.browser.type_text("input[name='ssh_key_name']", key_name).await?; self.browser.type_text("textarea[name='public_key']", public_key).await?; // Submit form self.browser.click("button[data-action='add-ssh-key']").await?; // Wait for success notification self.browser.wait_for_element(".alert-success, .notification-success").await?; self.browser.take_screenshot(&format!("ssh_key_added_{}", key_name)).await?; Ok(()) } // ===== ASSERTION HELPERS ===== /// Assert that page has loaded with expected title pub async fn assert_page_loaded(&self, expected_title_part: &str) -> Result<(), Box> { let title = self.browser.get_title().await?; if !title.to_lowercase().contains(&expected_title_part.to_lowercase()) { return Err(format!("Expected page title to contain '{}', got '{}'", expected_title_part, title).into()); } Ok(()) } /// Assert user is authenticated pub async fn assert_user_is_authenticated(&self) -> Result<(), Box> { // Check for authentication indicators in UI if !self.browser.element_exists(".user-menu, .navbar-user, .dashboard-link").await { return Err("User does not appear to be authenticated (no user menu found)".into()); } // Validate with API let auth_status = self.api_client.validate_auth_status().await?; if !auth_status.authenticated { return Err("API reports user is not authenticated".into()); } Ok(()) } /// Assert cart item count pub async fn assert_cart_item_count(&self, expected_count: usize) -> Result<(), Box> { // Check UI cart badge if let Ok(cart_badge) = self.browser.find_element(".cart-badge, .cart-count").await { let displayed_count = cart_badge.text().await?.parse::().unwrap_or(0); if displayed_count != expected_count { return Err(format!("Expected cart count {}, but UI shows {}", expected_count, displayed_count).into()); } } // Validate with API let cart_state = self.api_client.validate_cart_state().await?; if cart_state.item_count != expected_count { return Err(format!("Expected cart count {}, but API shows {}", expected_count, cart_state.item_count).into()); } Ok(()) } /// Assert element contains text pub async fn assert_element_contains_text(&self, selector: &str, expected_text: &str) -> Result<(), Box> { let element_text = self.browser.get_text(selector).await?; if !element_text.contains(expected_text) { return Err(format!("Expected element '{}' to contain '{}', but got '{}'", selector, expected_text, element_text).into()); } Ok(()) } // ===== UTILITY HELPERS ===== /// Create safe filename from email fn safe_filename(&self, email: &str) -> String { email.replace("@", "_at_").replace(".", "_dot_") } /// Take screenshot with timestamp pub async fn take_screenshot(&self, name: &str) -> Result<(), Box> { self.browser.take_screenshot(name).await?; Ok(()) } /// Wait for specific duration pub async fn wait(&self, duration: Duration) { tokio::time::sleep(duration).await; } /// Get current user pub fn current_user(&self) -> Option<&TestPersona> { self.current_user.as_ref() } }