use std::{fmt, fs::File, io::Write, path::Path, write};
use base64::decode;
use serde::ser::{Serialize, SerializeMap, Serializer};
use crate::common::command::MAGIC_ELEMENTID;
use crate::error::WebDriverError;
use crate::webdrivercommands::WebDriverCommands;
use crate::WebDriverSession;
use crate::{
common::{
command::Command,
connection_common::convert_json,
keys::TypingData,
types::{ElementId, ElementRect, ElementRef},
},
error::WebDriverResult,
By, ScriptArgs,
};
pub fn convert_element_sync<'a>(
driver: &'a WebDriverSession,
value: &serde_json::Value,
) -> WebDriverResult<WebElement<'a>> {
let elem_id: ElementRef = serde_json::from_value(value.clone())?;
Ok(WebElement::new(driver, ElementId::from(elem_id.id)))
}
pub fn convert_elements_sync<'a>(
driver: &'a WebDriverSession,
value: &serde_json::Value,
) -> WebDriverResult<Vec<WebElement<'a>>> {
let values: Vec<ElementRef> = serde_json::from_value(value.clone())?;
Ok(values.into_iter().map(|x| WebElement::new(driver, ElementId::from(x.id))).collect())
}
#[derive(Debug, Clone)]
pub struct WebElement<'a> {
pub element_id: ElementId,
pub session: &'a WebDriverSession,
}
impl<'a> WebElement<'a> {
pub fn new(session: &'a WebDriverSession, element_id: ElementId) -> Self {
WebElement {
element_id,
session,
}
}
fn cmd(&self, command: Command) -> WebDriverResult<serde_json::Value> {
self.session.cmd(command)
}
pub fn rect(&self) -> WebDriverResult<ElementRect> {
let v = self.cmd(Command::GetElementRect(self.element_id.clone()))?;
let r: ElementRect = serde_json::from_value((&v["value"]).clone())?;
Ok(r)
}
pub fn tag_name(&self) -> WebDriverResult<String> {
let v = self.cmd(Command::GetElementTagName(self.element_id.clone()))?;
convert_json(&v["value"])
}
pub fn class_name(&self) -> WebDriverResult<Option<String>> {
self.get_attribute("class")
}
pub fn id(&self) -> WebDriverResult<Option<String>> {
self.get_attribute("id")
}
pub fn text(&self) -> WebDriverResult<String> {
let v = self.cmd(Command::GetElementText(self.element_id.clone()))?;
convert_json(&v["value"])
}
pub fn value(&self) -> WebDriverResult<Option<String>> {
self.get_attribute("value")
}
pub fn click(&self) -> WebDriverResult<()> {
self.cmd(Command::ElementClick(self.element_id.clone()))?;
Ok(())
}
pub fn clear(&self) -> WebDriverResult<()> {
self.cmd(Command::ElementClear(self.element_id.clone()))?;
Ok(())
}
pub fn get_property(&self, name: &str) -> WebDriverResult<Option<String>> {
let v = self.cmd(Command::GetElementProperty(self.element_id.clone(), name.to_owned()))?;
if v["value"].is_null() {
Ok(None)
} else if !v["value"].is_string() {
Ok(Some(v["value"].to_string()))
} else {
convert_json(&v["value"]).map(Some)
}
}
pub fn get_attribute(&self, name: &str) -> WebDriverResult<Option<String>> {
let v = self.cmd(Command::GetElementAttribute(self.element_id.clone(), name.to_owned()))?;
if !v["value"].is_string() {
Ok(None)
} else {
convert_json(&v["value"])
}
}
pub fn get_css_property(&self, name: &str) -> WebDriverResult<String> {
let v = self.cmd(Command::GetElementCssValue(self.element_id.clone(), name.to_owned()))?;
if !v["value"].is_string() {
Ok(String::new())
} else {
convert_json(&v["value"])
}
}
pub fn is_selected(&self) -> WebDriverResult<bool> {
let v = self.cmd(Command::IsElementSelected(self.element_id.clone()))?;
convert_json(&v["value"])
}
pub fn is_displayed(&self) -> WebDriverResult<bool> {
let v = self.cmd(Command::IsElementDisplayed(self.element_id.clone()))?;
convert_json(&v["value"])
}
pub fn is_enabled(&self) -> WebDriverResult<bool> {
let v = self.cmd(Command::IsElementEnabled(self.element_id.clone()))?;
convert_json(&v["value"])
}
pub fn is_clickable(&self) -> WebDriverResult<bool> {
Ok(self.is_displayed()? && self.is_enabled()?)
}
pub fn is_present(&self) -> WebDriverResult<bool> {
let present = match self.tag_name() {
Ok(_) => true,
Err(WebDriverError::NoSuchElement(_))
| Err(WebDriverError::StaleElementReference(_)) => false,
Err(e) => return Err(e),
};
Ok(present)
}
pub fn find_element(&self, by: By) -> WebDriverResult<WebElement> {
let v = self
.cmd(Command::FindElementFromElement(self.element_id.clone(), by.get_w3c_selector()))?;
convert_element_sync(self.session, &v["value"])
}
pub fn find_elements(&self, by: By) -> WebDriverResult<Vec<WebElement>> {
let v = self.cmd(Command::FindElementsFromElement(
self.element_id.clone(),
by.get_w3c_selector(),
))?;
convert_elements_sync(self.session, &v["value"])
}
pub fn send_keys<S>(&self, keys: S) -> WebDriverResult<()>
where
S: Into<TypingData>,
{
self.cmd(Command::ElementSendKeys(self.element_id.clone(), keys.into()))?;
Ok(())
}
pub fn screenshot_as_base64(&self) -> WebDriverResult<String> {
let v = self.cmd(Command::TakeElementScreenshot(self.element_id.clone()))?;
convert_json(&v["value"])
}
pub fn screenshot_as_png(&self) -> WebDriverResult<Vec<u8>> {
let s = self.screenshot_as_base64()?;
let bytes: Vec<u8> = decode(&s)?;
Ok(bytes)
}
pub fn screenshot(&self, path: &Path) -> WebDriverResult<()> {
let png = self.screenshot_as_png()?;
let mut file = File::create(path)?;
file.write_all(&png)?;
Ok(())
}
pub fn focus(&self) -> WebDriverResult<()> {
let mut args = ScriptArgs::new();
args.push(&self)?;
self.session.execute_script_with_args(r#"arguments[0].focus();"#, &args)?;
Ok(())
}
pub fn scroll_into_view(&self) -> WebDriverResult<()> {
let mut args = ScriptArgs::new();
args.push(&self)?;
self.session.execute_script_with_args(r#"arguments[0].scrollIntoView();"#, &args)?;
Ok(())
}
pub fn inner_html(&self) -> WebDriverResult<String> {
self.get_property("innerHTML").map(|x| x.unwrap_or_default())
}
pub fn outer_html(&self) -> WebDriverResult<String> {
self.get_property("outerHTML").map(|x| x.unwrap_or_default())
}
}
impl<'a> fmt::Display for WebElement<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, r#"(session="{}", element="{}")"#, self.session.session_id(), self.element_id)
}
}
impl<'a> Serialize for WebElement<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry(MAGIC_ELEMENTID, &self.element_id.to_string())?;
map.end()
}
}