pebbles 0.0.101

A unfinished webautomation framework for working with Firefox and the geckodriver.
Documentation
use crate::service::Service;
use crate::result::{PebblesError,PebblesErrorDetails};
use std::rc::Rc;
use serde_json::{Value, json};

use serde::{ser::SerializeStruct, Serializer};
use rand_distr::{Triangular, Distribution};

/// A representation of a rectangular area.
///
/// The `Rect` struct represents a rectangle by storing the x and y coordinates of the top-left corner, 
/// along with the rectangle's width and height.
#[derive(Debug)]
pub struct Rect {
    pub x:i64,
    pub y:i64,
    pub width:i64,
    pub height:i64,
}

/// 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(),
        }
    }

    /// 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> {
        let json_rect: Value = self.service
            .get(&format!("/element/{}/rect", self.id))?
            .json::<Value>()?
            ["value"].clone();
        Ok(Rect {
            x: match json_rect["x"].as_f64() {
                Some(value) => value as i64,
                None => {
                    return Err(PebblesError::PhrasingError(PebblesErrorDetails::new(
                        "Invalid Rect values from webdriver.",
                    )))
                }
            },
            y: match json_rect["y"].as_f64() {
                Some(value) => value as i64,
                None => {
                    return Err(PebblesError::PhrasingError(PebblesErrorDetails::new(
                        "Invalid Rect values from webdriver.",
                    )))
                }
            },
            width: match json_rect["width"].as_f64() {
                Some(value) => value as i64,
                None => {
                    return Err(PebblesError::PhrasingError(PebblesErrorDetails::new(
                        "Invalid Rect values from webdriver.",
                    )))
                }
            },
            height: match json_rect["height"].as_f64() {
                Some(value) => value as i64,
                None => {
                    return Err(PebblesError::PhrasingError(PebblesErrorDetails::new(
                        "Invalid Rect values from webdriver.",
                    )))
                }
            },
        })
    }

    /// Returns a random point within the rectangle of the web element.
    ///
    /// The `point` method generates a random point that lies within the rectangle of the `WebElement`. 
    /// It uses a triangular distribution for generating the random coordinates, with the peak of the 
    /// distribution at the center of the rectangle. This means that points near the center of the rectangle 
    /// are more likely to be generated.
    ///
    /// # Errors
    ///
    /// This method will return an error if it fails to fetch the rectangle of the `WebElement` or if the 
    /// rectangle's dimensions are invalid for generating the triangular distribution.
    pub fn point(&self) -> Result<(i64, i64), PebblesError> {
        let mut rng = rand::thread_rng();
        let rect = self.rect()?;
        let triangular_x = Triangular::new(0.0, rect.width as f64, rect.width as f64 / 2.0)
            .map_err(|_| PebblesError::PhrasingError(
                PebblesErrorDetails::new("Invalid Rect values from webdriver.")
            ))?;
        let triangular_y = Triangular::new(0.0, rect.height as f64, rect.height as f64 / 2.0)
            .map_err(|_| PebblesError::PhrasingError(
                PebblesErrorDetails::new("Invalid Rect values from webdriver.")
            ))?;
        Ok((triangular_x.sample(&mut rng) as i64, triangular_y.sample(&mut rng) as i64))
    }

    /// 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())
    }
}

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()
    }
}