//! Scripted mock browser: deterministic fake pages (the test contract). //! QA writes fixtures here; a real Chromium/CDP adapter is a future //! implementor of the same trait. use crate::browser::{Browser, BrowserError, Page}; /// Deterministic fixture tree: whatever you goto, exists; click/typing /// extend the current URL. Nothing here hits the network — tests are hermetic. #[derive(Default)] pub struct MockBrowser { page: Option, pub navigations: Vec, } impl MockBrowser { pub fn new() -> Self { Self::default() } fn page_for(&self, url: &str) -> Page { Page { url: url.to_string(), title: format!("Fixture page: {url}"), links: vec![format!("{url}/probe")], screenshot: None, } } } impl Browser for MockBrowser { fn goto(&mut self, url: &str) -> Result { let page = self.page_for(url); self.navigations.push(url.to_string()); self.page = Some(page.clone()); Ok(page) } fn click(&mut self, selector: &str) -> Result { let Some(cur) = &self.page else { return Err(BrowserError::NoSuchElement(selector.to_string())); }; let next = format!("{}/clicked/{selector}", cur.url); let page = self.page_for(&next); self.page = Some(page.clone()); Ok(page) } fn typing(&mut self, selector: &str, text: &str) -> Result { let Some(cur) = &mut self.page else { return Err(BrowserError::NoSuchElement(selector.to_string())); }; let entry = format!("{selector}={text}"); cur.links.push(entry); Ok(cur.clone()) } fn screenshot(&mut self) -> Result, BrowserError> { let Some(p) = &self.page else { return Err(BrowserError::Budget); }; Ok(format!("mock-png:{}", p.title).into_bytes()) } fn text(&self) -> String { self.page .as_ref() .map(|p| format!("{}", p.title)) .unwrap_or_default() } } #[cfg(test)] mod tests { use super::*; #[test] fn goto_click_navigate() { let mut b = MockBrowser::new(); b.goto("https://acme.test/start").unwrap(); let p = b.click("button-a").unwrap(); assert!(p.url.contains("/clicked/button-a")); } #[test] fn typing_needs_a_page() { let mut b = MockBrowser::new(); assert!(matches!( b.typing("name-box", "hi"), Err(BrowserError::NoSuchElement(_)) )); } }