use base64::DecodeError;
use serde::Deserialize;
use std::fmt::{Display, Formatter, Write};
use std::ops::{Deref, DerefMut};
pub type WebDriverResult<T> = Result<T, WebDriverError>;
fn indent_lines(message: &str, indent: usize) -> String {
struct IdentLines<'a>(&'a str, usize);
impl Display for IdentLines<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for (i, s) in self.0.split('\n').enumerate() {
if i != 0 {
f.write_char('\n')?
}
write!(f, "{:i$}{s}", " ", i = self.1)?;
}
Ok(())
}
}
IdentLines(message, indent).to_string()
}
#[derive(Debug, Deserialize, Clone)]
pub struct WebDriverErrorValue {
pub message: String,
pub error: Option<String>,
pub stacktrace: Option<String>,
pub data: Option<serde_json::Value>,
}
impl WebDriverErrorValue {
pub fn new(message: String) -> Self {
Self {
message,
error: None,
stacktrace: None,
data: None,
}
}
}
impl Display for WebDriverErrorValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let stacktrace = self
.stacktrace
.as_ref()
.map(|x| format!("Stacktrace:\n{}", indent_lines(x, 4)))
.unwrap_or_default();
let error = self.error.as_ref().map(|x| format!("Error: {}", x)).unwrap_or_default();
let data = self
.data
.as_ref()
.map(|x| format!("Data:\n{}", indent_lines(&format!("{:#?}", x), 4)))
.unwrap_or_default();
let lines: Vec<String> = vec![self.message.clone(), error, data, stacktrace]
.into_iter()
.filter(|l| !l.is_empty())
.collect();
write!(f, "{}", lines.join("\n"))
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct WebDriverErrorInfo {
#[serde(skip)]
pub status: u16,
#[serde(default, rename(deserialize = "state"))]
pub error: String,
pub value: WebDriverErrorValue,
}
impl WebDriverErrorInfo {
pub fn new(message: String) -> Self {
Self {
status: 0,
error: message.clone(),
value: WebDriverErrorValue::new(message),
}
}
}
impl Display for WebDriverErrorInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut msg = String::new();
if self.status != 0 {
msg.push_str(&format!("\nStatus: {}\n", self.status));
}
if !self.error.is_empty() {
msg.push_str(&format!("State: {}\n", self.error));
}
let additional_info =
format!("Additional info:\n{}", indent_lines(&self.value.to_string(), 4),);
msg.push_str(&additional_info);
write!(f, "{}", indent_lines(&msg, 4))
}
}
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct WebDriverError(Box<WebDriverErrorInner>);
macro_rules! make_enum_variant_func {
($enum_name: ident $variant_name: ident()) => {
#[allow(non_snake_case)]
#[allow(missing_docs)]
pub fn $variant_name() -> Self {
Self::from_inner($enum_name::$variant_name())
}
};
($enum_name: ident $variant_name: ident($_1: ty)) => {
#[allow(non_snake_case)]
#[allow(missing_docs)]
pub fn $variant_name(a: $_1) -> Self {
Self::from_inner($enum_name::$variant_name(a))
}
};
($enum_name: ident $variant_name: ident($_1: ty, $_2: ty)) => {
#[allow(non_snake_case)]
#[allow(missing_docs)]
pub fn $variant_name(a: $_1, b: $_2) -> Self {
Self::from_inner($enum_name::$variant_name(a, b))
}
};
($($other:tt)*) => {
compile_error!(concat!("Unknown variant: ", (stringify!($($other)*))))
};
}
macro_rules! impl_from_for_variant {
(#[from] $ty:ty) => {
impl From<$ty> for WebDriverError {
fn from(val: $ty) -> Self {
Self::from_inner((val).into())
}
}
};
($($ty:ty),+) => {
};
($($other:tt)*) => {
compile_error!(concat!("Unknown variant: ", stringify!($($other)*)));
};
}
macro_rules! webdriver_err {
(
$(#[$($outer_attr:tt)*])*
pub enum $enum_name: ident {
$(
$(#[$($variant_attr:tt)*])*
$variant_name: ident($($(#[$($ty_attr:tt)*])* $variant_tys:ty),*)
),+
$(,)?
}
) => {
$(#[$($outer_attr)*])*
pub enum $enum_name {
$(
$(#[$($variant_attr)*])*
$variant_name($($(#[$($ty_attr)*])* $variant_tys),*)
),+
}
impl WebDriverError {
$(
make_enum_variant_func! {
$enum_name $variant_name($($variant_tys),*)
}
)+
}
$(
impl_from_for_variant! {
$($(#[$($ty_attr)*])* $variant_tys),*
}
)+
};
}
webdriver_err! {
#[allow(missing_docs)]
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum WebDriverErrorInner {
#[error("The WebDriver server returned an unrecognised response: {0} :: {1}")]
UnknownResponse(u16, String),
#[error("Failed to send request to webdriver: {0}")]
RequestFailed(String),
#[error("The requested item '{0}' was not found: {1}")]
NotFound(String, String),
#[error("parse error: {0}")]
ParseError(String),
#[error("operation timed out: {0}")]
Timeout(String),
#[error("Unable to parse JSON: {0}")]
Json(String),
#[error("Unable to decode base64: {0}")]
DecodeError(#[from] DecodeError),
#[error("IO Error: {0}")]
IoError(#[from] std::io::Error),
#[error("The WebDriver request returned an error: {0}")]
HttpError(String),
#[error("The WebDriver response does not conform to the W3C WebDriver spec: {0}")]
NotInSpec(WebDriverErrorInfo),
#[error("The click event was intercepted by another element: {0}")]
ElementClickIntercepted(WebDriverErrorInfo),
#[error("The element is not interactable: {0}")]
ElementNotInteractable(WebDriverErrorInfo),
#[error("The certificate is insecure: {0}")]
InsecureCertificate(WebDriverErrorInfo),
#[error("An argument passed to the WebDriver server was invalid: {0}")]
InvalidArgument(WebDriverErrorInfo),
#[error("An argument passed to the WebDriver server was invalid: {0}")]
InvalidUrl(url::ParseError),
#[error("Invalid cookie domain: {0}")]
InvalidCookieDomain(WebDriverErrorInfo),
#[error("The element is in an invalid state: {0}")]
InvalidElementState(WebDriverErrorInfo),
#[error("The specified element selector is invalid: {0}")]
InvalidSelector(WebDriverErrorInfo),
#[error("The WebDriver session id is invalid: {0}")]
InvalidSessionId(WebDriverErrorInfo),
#[error("The Javascript code returned an error: {0}")]
JavascriptError(WebDriverErrorInfo),
#[error("Unable to scroll the element into the viewport: {0}")]
MoveTargetOutOfBounds(WebDriverErrorInfo),
#[error("Alert not found: {0}")]
NoSuchAlert(WebDriverErrorInfo),
#[error("Cookie not found: {0}")]
NoSuchCookie(WebDriverErrorInfo),
#[error("Element not found: {0}")]
NoSuchElement(WebDriverErrorInfo),
#[error("Frame not found: {0}")]
NoSuchFrame(WebDriverErrorInfo),
#[error("Window not found: {0}")]
NoSuchWindow(WebDriverErrorInfo),
#[error("The Javascript code did not complete within the script timeout (see WebDriver::set_script_timeout()): {0}")]
ScriptTimeout(WebDriverErrorInfo),
#[error("Unable to create WebDriver session: {0}")]
SessionNotCreated(WebDriverErrorInfo),
#[error("Element is stale: {0}")]
StaleElementReference(WebDriverErrorInfo),
#[error("Operation timed out: {0}")]
WebDriverTimeout(WebDriverErrorInfo),
#[error("Unable to set cookie: {0}")]
UnableToSetCookie(WebDriverErrorInfo),
#[error("Unable to capture screenshot: {0}")]
UnableToCaptureScreen(WebDriverErrorInfo),
#[error("An unexpected alert is currently open: {0}")]
UnexpectedAlertOpen(WebDriverErrorInfo),
#[error("Unknown command: {0}")]
UnknownCommand(WebDriverErrorInfo),
#[error("Unknown error: {0}")]
UnknownError(WebDriverErrorInfo),
#[error("Unknown method: {0}")]
UnknownMethod(WebDriverErrorInfo),
#[error("Unsupport operation: {0}")]
UnsupportedOperation(WebDriverErrorInfo),
#[error("Session terminated: {0}")]
FatalError(String),
#[error("Failed to receive command: {0}")]
CommandRecvError(String),
#[error("The command could not be sent to the session: {0}")]
CommandSendError(String),
#[error("Could not create session: {0}")]
SessionCreateError(String),
}
}
#[cfg(feature = "manager")]
impl From<crate::manager::ManagerError> for WebDriverError {
fn from(e: crate::manager::ManagerError) -> Self {
WebDriverError::SessionCreateError(format!("driver manager: {e}"))
}
}
impl WebDriverError {
pub fn parse(status: u16, body: String) -> Self {
let body_json = match serde_json::from_str(&body) {
Ok(x) => x,
Err(_) => {
return Self::from_inner(WebDriverErrorInner::UnknownResponse(status, body));
}
};
let mut payload: WebDriverErrorInfo = match serde_json::from_value(body_json) {
Ok(x) => x,
Err(_) => return Self::from_inner(WebDriverErrorInner::UnknownResponse(status, body)),
};
payload.status = status;
let mut error = payload.error.clone();
if error.is_empty() {
error = payload.value.error.clone().unwrap_or_default();
if error.is_empty() {
return Self::from_inner(WebDriverErrorInner::NotInSpec(payload));
}
}
Self::from_inner(match error.as_str() {
"element click intercepted" => WebDriverErrorInner::ElementClickIntercepted(payload),
"element not interactable" => WebDriverErrorInner::ElementNotInteractable(payload),
"insecure certificate" => WebDriverErrorInner::InsecureCertificate(payload),
"invalid argument" => WebDriverErrorInner::InvalidArgument(payload),
"invalid cookie domain" => WebDriverErrorInner::InvalidCookieDomain(payload),
"invalid element state" => WebDriverErrorInner::InvalidElementState(payload),
"invalid selector" => WebDriverErrorInner::InvalidSelector(payload),
"invalid session id" => WebDriverErrorInner::InvalidSessionId(payload),
"javascript error" => WebDriverErrorInner::JavascriptError(payload),
"move target out of bounds" => WebDriverErrorInner::MoveTargetOutOfBounds(payload),
"no such alert" => WebDriverErrorInner::NoSuchAlert(payload),
"no such cookie" => WebDriverErrorInner::NoSuchCookie(payload),
"no such element" => WebDriverErrorInner::NoSuchElement(payload),
"no such frame" => WebDriverErrorInner::NoSuchFrame(payload),
"no such window" => WebDriverErrorInner::NoSuchWindow(payload),
"script timeout" => WebDriverErrorInner::ScriptTimeout(payload),
"session not created" => WebDriverErrorInner::SessionNotCreated(payload),
"stale element reference" => WebDriverErrorInner::StaleElementReference(payload),
"timeout" => WebDriverErrorInner::WebDriverTimeout(payload),
"unable to set cookie" => WebDriverErrorInner::UnableToSetCookie(payload),
"unable to capture screen" => WebDriverErrorInner::UnableToCaptureScreen(payload),
"unexpected alert open" => WebDriverErrorInner::UnexpectedAlertOpen(payload),
"unknown command" => WebDriverErrorInner::UnknownCommand(payload),
"unknown error" => WebDriverErrorInner::UnknownError(payload),
"unknown method" => WebDriverErrorInner::UnknownMethod(payload),
"unsupported operation" => WebDriverErrorInner::UnsupportedOperation(payload),
_ => WebDriverErrorInner::NotInSpec(payload),
})
}
pub fn as_inner(&self) -> &WebDriverErrorInner {
self
}
pub fn from_inner(err: WebDriverErrorInner) -> Self {
Self(Box::new(err))
}
pub fn into_inner(self) -> WebDriverErrorInner {
*self.0
}
}
impl From<WebDriverErrorInner> for WebDriverError {
fn from(value: WebDriverErrorInner) -> Self {
Self::from_inner(value)
}
}
impl From<WebDriverError> for WebDriverErrorInner {
fn from(value: WebDriverError) -> Self {
value.into_inner()
}
}
impl Deref for WebDriverError {
type Target = WebDriverErrorInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for WebDriverError {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub fn no_such_element(message: String) -> WebDriverError {
WebDriverError::from_inner(WebDriverErrorInner::NoSuchElement(WebDriverErrorInfo {
status: 400,
error: message.clone(),
value: WebDriverErrorValue {
message,
error: None,
stacktrace: None,
data: None,
},
}))
}
#[cfg(feature = "reqwest")]
impl From<reqwest::Error> for WebDriverError {
fn from(err: reqwest::Error) -> Self {
WebDriverError::HttpError(err.to_string())
}
}
impl From<serde_json::Error> for WebDriverError {
fn from(err: serde_json::Error) -> Self {
WebDriverError::Json(err.to_string())
}
}
impl From<http::header::InvalidHeaderValue> for WebDriverError {
fn from(err: http::header::InvalidHeaderValue) -> Self {
WebDriverError::ParseError(format!("invalid header value: {err}"))
}
}