browsing 0.1.7

Browser automation: navigate, click, extract, screenshot. Standalone browser control via CDP.
Documentation
//! Tests for the fallback element resolver

#[cfg(test)]
mod tests {
    use crate::dom::views::{DOMInteractedElement, SemanticRole};
    use crate::tools::resolver::{ElementResolver, ResolutionStrategy, ResolverConfig};
    use std::collections::HashMap;

    fn make_selector_map() -> HashMap<u32, DOMInteractedElement> {
        let mut map = HashMap::new();
        map.insert(
            0,
            DOMInteractedElement {
                index: 0,
                backend_node_id: Some(100),
                tag: "button".to_string(),
                text: Some("Submit".to_string()),
                attributes: {
                    let mut m = HashMap::new();
                    m.insert("id".to_string(), "submit-btn".to_string());
                    m.insert("class".to_string(), "btn primary".to_string());
                    m
                },
                selector: Some("//*[@id='submit-btn']".to_string()),
                semantic_role: Some(SemanticRole::SubmitButton),
                semantic_affordance: Some("Submit the form".to_string()),
                semantic_confidence: 0.9,
            },
        );
        map.insert(
            1,
            DOMInteractedElement {
                index: 1,
                backend_node_id: Some(101),
                tag: "input".to_string(),
                text: Some("Search".to_string()),
                attributes: {
                    let mut m = HashMap::new();
                    m.insert("type".to_string(), "text".to_string());
                    m.insert("placeholder".to_string(), "Search...".to_string());
                    m
                },
                selector: Some("//input[@placeholder='Search...']".to_string()),
                semantic_role: Some(SemanticRole::TextInput),
                semantic_affordance: Some("Enter search query".to_string()),
                semantic_confidence: 0.85,
            },
        );
        map.insert(
            2,
            DOMInteractedElement {
                index: 2,
                backend_node_id: None, // Stale element with no backend_node_id
                tag: "a".to_string(),
                text: Some("Next".to_string()),
                attributes: HashMap::new(),
                selector: None,
                semantic_role: Some(SemanticRole::Pagination),
                semantic_affordance: Some("Go to next page".to_string()),
                semantic_confidence: 0.7,
            },
        );
        map
    }

    #[test]
    fn test_resolution_strategy_display() {
        assert_eq!(
            format!("{:?}", ResolutionStrategy::Index),
            "Index"
        );
        assert_eq!(
            format!("{:?}", ResolutionStrategy::Selector),
            "Selector"
        );
        assert_eq!(
            format!("{:?}", ResolutionStrategy::TextMatch),
            "TextMatch"
        );
        assert_eq!(
            format!("{:?}", ResolutionStrategy::SemanticRole),
            "SemanticRole"
        );
        assert_eq!(
            format!("{:?}", ResolutionStrategy::JavaScript),
            "JavaScript"
        );
    }

    #[test]
    fn test_resolver_config_default() {
        let config = ResolverConfig::default();
        assert!(config.enable_selector);
        assert!(config.enable_text_match);
        assert!(config.enable_semantic_role);
        assert!(config.enable_javascript);
        assert!(config.text_match_threshold >= 0.0 && config.text_match_threshold <= 1.0);
    }

    #[test]
    fn test_resolver_config_custom() {
        let config = ResolverConfig {
            enable_selector: false,
            enable_text_match: false,
            enable_semantic_role: true,
            enable_javascript: false,
            text_match_threshold: 0.5,
        };
        assert!(!config.enable_selector);
        assert!(!config.enable_text_match);
        assert!(config.enable_semantic_role);
        assert!(!config.enable_javascript);
    }

    #[test]
    fn test_element_resolver_new() {
        let resolver = ElementResolver::new();
        // Just verify it can be created
        let _ = resolver;
    }

    #[test]
    fn test_element_resolver_with_config() {
        let config = ResolverConfig {
            enable_selector: false,
            ..Default::default()
        };
        let resolver = ElementResolver::with_config(config);
        let _ = resolver;
    }

    #[test]
    fn test_resolution_result_fields() {
        let result = crate::tools::resolver::ResolutionResult {
            backend_node_id: 42,
            strategy: ResolutionStrategy::Index,
            confidence: 1.0,
        };
        assert_eq!(result.backend_node_id, 42);
        assert_eq!(result.strategy, ResolutionStrategy::Index);
        assert_eq!(result.confidence, 1.0);
    }

    #[test]
    fn test_selector_map_structure() {
        let map = make_selector_map();
        assert_eq!(map.len(), 3);
        assert!(map.contains_key(&0));
        assert!(map.contains_key(&1));
        assert!(map.contains_key(&2));

        let elem0 = map.get(&0).unwrap();
        assert_eq!(elem0.backend_node_id, Some(100));
        assert_eq!(elem0.tag, "button");
        assert_eq!(elem0.semantic_role, Some(SemanticRole::SubmitButton));

        let elem2 = map.get(&2).unwrap();
        assert_eq!(elem2.backend_node_id, None);
        assert_eq!(elem2.semantic_role, Some(SemanticRole::Pagination));
    }

    #[test]
    fn test_semantic_role_to_selector_mapping() {
        // Verify that each semantic role we care about maps to a non-empty selector
        let roles = vec![
            SemanticRole::SearchForm,
            SemanticRole::LoginForm,
            SemanticRole::Navigation,
            SemanticRole::Pagination,
            SemanticRole::ProductCard,
            SemanticRole::Article,
            SemanticRole::FilterPanel,
            SemanticRole::PrimaryAction,
            SemanticRole::SubmitButton,
            SemanticRole::TextInput,
            SemanticRole::Dropdown,
            SemanticRole::ToggleGroup,
            SemanticRole::DatePicker,
            SemanticRole::FileUpload,
            SemanticRole::Captcha,
            SemanticRole::CookieConsent,
            SemanticRole::Advertisement,
            SemanticRole::Header,
            SemanticRole::Footer,
            SemanticRole::Sidebar,
            SemanticRole::MainContent,
        ];

        for role in roles {
            // The resolver's _find_by_semantic_role should not return "Unknown"
            // for these roles. We can't call the async method directly in a sync test,
            // but we can verify the role itself is well-formed.
            let _ = format!("{:?}", role);
        }
    }
}