pebbles 0.0.102

A unfinished webautomation framework for working with Firefox and the geckodriver.
Documentation
use crate::{
    keyboard::Keyboard,
    rect::Rect,
    mouse::Mouse,
    result::{PebblesError, PebblesErrorDetails},
    service::Service,
    webelement::WebElement,
    navigator::Navigator,
    // macros
    raise,
    error,
};

use serde_json::{json, Value};
use std::rc::Rc;

pub struct Pebbles {
    pub service: Rc<Service>,
    pub mouse: Mouse,
    pub keyboard: Keyboard,
    pub navigator: Navigator,
}

impl Pebbles {
    pub fn new(geckodriver_cmd: &str, capabilities: Option<&Value>) -> Result<Self, PebblesError> {
        let pebbles_capabilities = match capabilities {
            Some(caps) => caps.clone(),
            None => json!({
                "capabilities": {
                    "firstMatch": [{}],
                    "alwaysMatch": {
                        "browserName": "firefox",
                        "acceptInsecureCerts": true,
                        "moz:debuggerAddress": true,
                        "pageLoadStrategy": "normal",
                        "moz:firefoxOptions": {
                            "prefs": {},
                            "args": [],
                        }
                    }
                }
            }),
        };
        let service = match Service::new(&pebbles_capabilities, geckodriver_cmd) {
            Ok(response) => Rc::new(response),
            Err(error) => return Err(error),
        };
        let mouse: Mouse = Mouse::new(service.clone());
        let navigator: Navigator = Navigator::new(service.clone());
        let keyboard: Keyboard = Keyboard::new(service.clone());
        Ok(Pebbles {
            service,
            mouse,
            keyboard,
            navigator,
        })
    }

    // region: Elements
    pub fn query(&self, identifier: &str) -> Result<Vec<WebElement>, PebblesError> {
        let body = if identifier.starts_with("$") {
            json!({"using": "xpath", "value": &identifier[1..]})
        } else {
            json!({"using": "css selector", "value": identifier})
        };

        let all_elements = self.service.post("/elements", &body)?.json::<Value>()?["value"]
            .as_array()
            .ok_or_else(|| {
                PebblesError::PhrasingError(PebblesErrorDetails::new(
                    "Error Fetching Elements from json.",
                ))
            })?
            .clone();
        

        if all_elements.len() == 0 {
            return Err(error!(PebblesError::NoElementsError,
                "No elements found."
            ));
        }
        
        let elements_vec: Vec<WebElement> = all_elements
            .iter()
            .filter_map(|element| element["element-6066-11e4-a52e-4f735466cecf"].as_str())
            .map(|value| WebElement::new(self.service.clone(), value))
            .collect();

        Ok(elements_vec)
    }
    // endregion: Elements

    pub fn actions(&self, actions: &Value) -> Result<(), PebblesError> {
        self.service.post("/actions", actions)?;
        Ok(())
    }

    pub fn java_script(&self, script: &str, arguments: &[Value]) -> Result<Value, PebblesError> {
        match self
            .service
            .post(
                "/execute/sync",
                &json!({"script": script, "args": arguments}),
            )?
            .json::<Value>()?
            .get("value") {
                Some(value) => Ok(value.clone()),
                None => raise!(PebblesError::PhrasingError, "Could not phrase javascript response."),
            }
    }

    pub fn viewport_rect(&self) -> Result<Rect, PebblesError> {
        fn get_value_as_i64(json_value: &Value, key: &str) -> Result<i64, PebblesError> {
            Ok(json_value.get(key)
                .ok_or_else(|| error!(PebblesError::PhrasingError, "Invalid Javascript Response could not get Window Size from WebDriver.", json_value.clone()))?
                .as_f64()
                .ok_or_else(|| error!(PebblesError::PhrasingError, "Invalid Javascript Response could not get Window Size from WebDriver.", json_value.clone()))? as i64
            )
        }
        let response = self
            .service
            .post(
                "/execute/sync", 
                &json!({"script": "return {\"x\": window.pageXOffset, \"y\": window.pageYOffset, \"width\": window.innerWidth, \"height\": window.innerHeight};", "args": []})
            )?.json::<Value>()?
            .get("value")
            .ok_or_else(|| error!(PebblesError::PhrasingError, "Could not get window size from javascript response."))?.clone();

        let window_x: i64 = get_value_as_i64(&response, "x")?;
        let window_y: i64 = get_value_as_i64(&response, "y")?;
        let window_width: i64 = get_value_as_i64(&response, "width")?;
        let window_height: i64 = get_value_as_i64(&response, "height")?;
        Ok(
            Rect{
                x:window_x,
                y:window_y,
                width:window_width,
                height:window_height,
            }
        )
    }
}