pebbles 0.0.103

The Worst Web Automation Framework Ever. (╯°□°)╯︵ ┻━┻
Documentation
use crate::service::Service;
use crate::{raise, error};
use crate::rect::Rect;
use crate::result::{PebblesError,PebblesErrorDetails};
use std::rc::Rc;
use serde_json::{Value, json};

use serde::{ser::SerializeStruct, Serializer};

/// Represents a web element.
///
/// The `WebElement` struct is a representation of a web element. It contains a reference to the `Service` 
/// which allows it to perform operations like fetching the element's attributes.
pub struct WebElement {
    service: Rc<Service>,
    id:String,
}

impl WebElement {
    /// Construct a new `WebElement`.
    ///
    /// This method takes a reference-counted `Service` and an elements id string, and returns a new `WebElement`.
    pub fn new(service: Rc<Service>, id:&str) -> Self {
        WebElement {
            service,
            id:id.to_string(),
        }
    }

    // region: Properties
    pub fn selected(&self) -> Result<bool, PebblesError> {
        let json = self.service.get(&format!("/element/{}/selected", self.id))?
        .json::<Value>()?;

        let value = match json.get("value") {
            Some(value) => value,
            None => raise!(PebblesError::PhrasingError, "Invalid value for \"selected\" from webdriver."),
        };

        match value.as_bool() {
            Some(value) => Ok(value),
            None => raise!(PebblesError::PhrasingError, "Invalid value for \"selected\" from webdriver."),
        }
    }

    pub fn displayed(&self) -> Result<bool, PebblesError> {
        let java_script: &'static str = include_str!("displayed.js");
        Ok(self
            .service
            .post(
                "/execute/sync",
                &json!({
                    "script": format!("/* isDisplayed */return ({}).apply(null, arguments);", java_script).as_str(), 
                    "args": [self]}),
            )?
            .json::<Value>()?["value"]
            .as_bool().unwrap_or(false))
    }
    // endregion: Properties

    /// Fetch the rectangle of the `WebElement`.
    ///
    /// This method sends a request to the `Service` to fetch the rectangle of the `WebElement`. It returns 
    /// a `Result` that contains a `Rect` on success, or a `PebblesError` on failure.    
    pub fn 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 Rect values from webdriver.", json_value.clone()))?
                .as_f64()
                .ok_or_else(|| error!(PebblesError::PhrasingError, "Invalid Rect values from webdriver.", json_value.clone()))? as i64
            )
        }
        let json_rect: Value = self.service
            .get(&format!("/element/{}/rect", self.id))?
            .json::<Value>()?
            .get("value")
            .ok_or_else(|| error!(PebblesError::PhrasingError, "Invalid Rect values from webdriver."))?
            .clone();
        Ok(Rect {
            x: get_value_as_i64(&json_rect, "x")?,
            y: get_value_as_i64(&json_rect, "y")?,
            width: get_value_as_i64(&json_rect, "width")?,
            height: get_value_as_i64(&json_rect, "height")?,
        })
    }

    /// Fetch an attribute of the `WebElement`.
    ///
    /// This method takes the name of an attribute and sends a request to the `Service` to fetch the value 
    /// of that attribute. It returns a `Result` that contains the attribute value on success, or a 
    /// `PebblesError` on failure.
    pub fn attribute(&self, name:&str) -> Result<Value, PebblesError> {
        let java_script: &'static str = include_str!("attribute.js");
        Ok(self
            .service
            .post(
                "/execute/sync",
                &json!({"script": format!("/* getAttribute */return ({}).apply(null, arguments);", java_script).as_str(), "args": [self, name]}),
            )?
            .json::<Value>()?["value"]
            .clone())
    }

    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(format!("/element/{}/elements", self.id).as_str(), &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)
    }
}

use std::cmp::{ Ord, PartialOrd };

impl Ord for WebElement {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        match self.rect() {
            Ok(self_rect) => match other.rect() {
                Ok(other_rect) => self_rect.y.cmp(&other_rect.y),
                Err(_) => std::cmp::Ordering::Less,
            },
            Err(_) => std::cmp::Ordering::Greater,
        }
    }
}

impl PartialEq for WebElement {
    fn eq(&self, other: &Self) -> bool {
        match self.rect() {
            Ok(self_rect) => match other.rect() {
                Ok(other_rect) => self_rect.y == other_rect.y,
                Err(_) => false,
            },
            Err(_) => false,
        }
    }
}

impl Eq for WebElement {}

impl PartialOrd for WebElement {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl std::fmt::Display for WebElement {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.id)
    }
}

impl serde::Serialize for WebElement {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("WebElement", 1)?;
        state.serialize_field("element-6066-11e4-a52e-4f735466cecf", &self.id)?;
        state.end()
    }
}

impl Clone for WebElement {
    fn clone(&self) -> Self {
        WebElement {
            service: Rc::clone(&self.service),
            id: self.id.clone(),
        }
    }
}