swarm / companies/lantern/seed/src/mock.rs

raw file 2638 bytes
//! 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(_))
        ));
    }
}