//! 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<Page>,
pub navigations: Vec<String>,
}
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<Page, BrowserError> {
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<Page, BrowserError> {
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<Page, BrowserError> {
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<Vec<u8>, 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(_))
));
}
}