use http::Method;
use serde_json::{Value, json};
use crate::IntoArcStr;
use crate::RequestData;
use crate::common::{
capabilities::desiredcapabilities::make_w3c_caps,
cookie::Cookie,
keys::TypingData,
print::PrintParameters,
types::{ElementId, OptionRect, SessionId, TimeoutConfiguration, WindowHandle},
};
use std::fmt;
use std::fmt::Debug;
use std::sync::Arc;
pub const MAGIC_ELEMENTID: &str = "element-6066-11e4-a52e-4f735466cecf";
#[derive(Debug)]
pub struct Actions(Value);
impl From<Value> for Actions {
fn from(value: Value) -> Self {
Actions(value)
}
}
#[derive(Debug, Clone)]
pub struct Selector {
pub name: Arc<str>,
pub query: Arc<str>,
}
impl Selector {
pub fn new(name: impl IntoArcStr, query: impl IntoArcStr) -> Self {
Self {
name: name.into(),
query: query.into(),
}
}
}
#[derive(Debug, Clone)]
pub enum BySelector {
Id(Arc<str>),
XPath(Arc<str>),
LinkText(Arc<str>),
PartialLinkText(Arc<str>),
Name(Arc<str>),
Tag(Arc<str>),
ClassName(Arc<str>),
Css(Arc<str>),
Testid(Arc<str>),
}
#[derive(Debug, Clone)]
pub struct By {
selector: BySelector,
}
#[allow(non_snake_case)]
impl By {
pub fn Id(id: impl IntoArcStr) -> Self {
Self {
selector: BySelector::Id(id.into()),
}
}
pub fn LinkText(text: impl IntoArcStr) -> Self {
Self {
selector: BySelector::LinkText(text.into()),
}
}
pub fn PartialLinkText(text: impl IntoArcStr) -> Self {
Self {
selector: BySelector::PartialLinkText(text.into()),
}
}
pub fn Css(css: impl IntoArcStr) -> Self {
Self {
selector: BySelector::Css(css.into()),
}
}
pub fn XPath(x: impl IntoArcStr) -> Self {
Self {
selector: BySelector::XPath(x.into()),
}
}
pub fn Name(name: impl IntoArcStr) -> Self {
Self {
selector: BySelector::Css(format!(r#"[name="{}"]"#, name.into()).into()),
}
}
pub fn Tag(tag: impl IntoArcStr) -> Self {
Self {
selector: BySelector::Css(tag.into()),
}
}
pub fn ClassName(name: impl IntoArcStr) -> Self {
Self {
selector: BySelector::Css(format!(".{}", name.into()).into()),
}
}
pub fn Testid(id: impl IntoArcStr) -> Self {
Self {
selector: BySelector::Css(format!("[data-testid=\"{}\"]", id.into()).into()),
}
}
}
impl fmt::Display for BySelector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BySelector::Id(id) => write!(f, "Id({})", id),
BySelector::XPath(xpath) => write!(f, "XPath({})", xpath),
BySelector::LinkText(text) => write!(f, "Link Text({})", text),
BySelector::PartialLinkText(text) => write!(f, "Partial Link Text({})", text),
BySelector::Name(name) => write!(f, "Name({})", name),
BySelector::Tag(tag) => write!(f, "Tag({})", tag),
BySelector::ClassName(cname) => write!(f, "Class({})", cname),
BySelector::Css(css) => write!(f, "CSS({})", css),
BySelector::Testid(id) => write!(f, "Testid({})", id),
}
}
}
impl fmt::Display for By {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.selector)
}
}
impl From<BySelector> for Selector {
fn from(by: BySelector) -> Self {
match by {
BySelector::Id(x) => Selector::new("css selector", format!("[id=\"{}\"]", x)),
BySelector::XPath(x) => Selector::new("xpath", x),
BySelector::LinkText(x) => Selector::new("link text", x),
BySelector::PartialLinkText(x) => Selector::new("partial link text", x),
BySelector::Name(x) => Selector::new("css selector", format!("[name=\"{}\"]", x)),
BySelector::Tag(x) => Selector::new("css selector", x),
BySelector::ClassName(x) => Selector::new("css selector", format!(".{}", x)),
BySelector::Css(x) => Selector::new("css selector", x),
BySelector::Testid(x) => {
Selector::new("testid selector", format!("[data-testid=\"{}\"]", x))
}
}
}
}
impl From<By> for Selector {
fn from(by: By) -> Self {
by.selector.into()
}
}
pub trait ExtensionCommand: Debug {
fn parameters_json(&self) -> Option<Value>;
fn method(&self) -> Method;
fn endpoint(&self) -> Arc<str>;
}
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Command {
NewSession(Value),
DeleteSession,
Status,
GetTimeouts,
SetTimeouts(TimeoutConfiguration),
NavigateTo(Arc<str>),
GetCurrentUrl,
Back,
Forward,
Refresh,
GetTitle,
GetWindowHandle,
CloseWindow,
SwitchToWindow(WindowHandle),
GetWindowHandles,
NewWindow,
NewTab,
SwitchToFrameDefault,
SwitchToFrameNumber(u16),
SwitchToFrameElement(ElementId),
SwitchToParentFrame,
GetWindowRect,
SetWindowRect(OptionRect),
MaximizeWindow,
MinimizeWindow,
FullscreenWindow,
GetActiveElement,
FindElement(Selector),
FindElements(Selector),
FindElementFromElement(ElementId, Selector),
FindElementsFromElement(ElementId, Selector),
IsElementSelected(ElementId),
IsElementDisplayed(ElementId),
GetElementAttribute(ElementId, Arc<str>),
GetElementProperty(ElementId, Arc<str>),
GetElementCssValue(ElementId, Arc<str>),
GetElementText(ElementId),
GetElementTagName(ElementId),
GetElementRect(ElementId),
IsElementEnabled(ElementId),
ElementClick(ElementId),
ElementClear(ElementId),
ElementSendKeys(ElementId, TypingData),
GetPageSource,
ExecuteScript(Arc<str>, Arc<[Value]>),
ExecuteAsyncScript(Arc<str>, Arc<[Value]>),
GetAllCookies,
GetNamedCookie(Arc<str>),
AddCookie(Cookie),
DeleteCookie(Arc<str>),
DeleteAllCookies,
PerformActions(Actions),
ReleaseActions,
DismissAlert,
AcceptAlert,
GetAlertText,
SendAlertText(TypingData),
PrintPage(PrintParameters),
TakeScreenshot,
TakeElementScreenshot(ElementId),
GetLogTypes,
GetLog(Arc<str>),
ExtensionCommand(Box<dyn ExtensionCommand + Send + Sync>),
}
pub trait FormatRequestData: Debug {
fn format_request(&self, session_id: &SessionId) -> RequestData;
}
impl FormatRequestData for Command {
fn format_request(&self, session_id: &SessionId) -> RequestData {
match self {
Command::NewSession(caps) => {
let w3c_caps = make_w3c_caps(caps);
RequestData::new(Method::POST, "session").add_body(json!({
"capabilities": w3c_caps,
"desiredCapabilities": caps
}))
}
Command::DeleteSession => {
RequestData::new(Method::DELETE, format!("session/{}", session_id))
}
Command::Status => RequestData::new(Method::GET, "/status"),
Command::GetTimeouts => {
RequestData::new(Method::GET, format!("session/{}/timeouts", session_id))
}
Command::SetTimeouts(timeout_configuration) => {
RequestData::new(Method::POST, format!("session/{}/timeouts", session_id))
.add_body(json!(timeout_configuration))
}
Command::NavigateTo(url) => {
RequestData::new(Method::POST, format!("session/{}/url", session_id))
.add_body(json!({ "url": url }))
}
Command::GetCurrentUrl => {
RequestData::new(Method::GET, format!("session/{}/url", session_id))
}
Command::Back => RequestData::new(Method::POST, format!("session/{}/back", session_id))
.add_body(json!({})),
Command::Forward => {
RequestData::new(Method::POST, format!("session/{}/forward", session_id))
.add_body(json!({}))
}
Command::Refresh => {
RequestData::new(Method::POST, format!("session/{}/refresh", session_id))
.add_body(json!({}))
}
Command::GetTitle => {
RequestData::new(Method::GET, format!("session/{}/title", session_id))
}
Command::GetWindowHandle => {
RequestData::new(Method::GET, format!("session/{}/window", session_id))
}
Command::CloseWindow => {
RequestData::new(Method::DELETE, format!("session/{}/window", session_id))
}
Command::SwitchToWindow(window_handle) => {
RequestData::new(Method::POST, format!("session/{}/window", session_id))
.add_body(json!({ "handle": window_handle.to_string() }))
}
Command::GetWindowHandles => {
RequestData::new(Method::GET, format!("session/{}/window/handles", session_id))
}
Command::NewWindow => {
RequestData::new(Method::POST, format!("session/{}/window/new", session_id))
.add_body(json!({"type": "window"}))
}
Command::NewTab => {
RequestData::new(Method::POST, format!("session/{}/window/new", session_id))
.add_body(json!({"type": "tab"}))
}
Command::SwitchToFrameDefault => {
RequestData::new(Method::POST, format!("session/{}/frame", session_id))
.add_body(json!({ "id": serde_json::Value::Null }))
}
Command::SwitchToFrameNumber(frame_number) => {
RequestData::new(Method::POST, format!("session/{}/frame", session_id))
.add_body(json!({ "id": frame_number }))
}
Command::SwitchToFrameElement(element_id) => {
RequestData::new(Method::POST, format!("session/{}/frame", session_id)).add_body(
json!({"id": {
"ELEMENT": element_id.to_string(),
MAGIC_ELEMENTID: element_id.to_string()
}}),
)
}
Command::SwitchToParentFrame => {
RequestData::new(Method::POST, format!("session/{}/frame/parent", session_id))
.add_body(json!({}))
}
Command::GetWindowRect => {
RequestData::new(Method::GET, format!("session/{}/window/rect", session_id))
}
Command::SetWindowRect(option_rect) => {
RequestData::new(Method::POST, format!("session/{}/window/rect", session_id))
.add_body(json!(option_rect))
}
Command::MaximizeWindow => {
RequestData::new(Method::POST, format!("session/{}/window/maximize", session_id))
.add_body(json!({}))
}
Command::MinimizeWindow => {
RequestData::new(Method::POST, format!("session/{}/window/minimize", session_id))
.add_body(json!({}))
}
Command::FullscreenWindow => {
RequestData::new(Method::POST, format!("session/{}/window/fullscreen", session_id))
.add_body(json!({}))
}
Command::GetActiveElement => {
RequestData::new(Method::GET, format!("session/{}/element/active", session_id))
}
Command::FindElement(selector) => {
RequestData::new(Method::POST, format!("session/{}/element", session_id))
.add_body(json!({"using": selector.name, "value": selector.query}))
}
Command::FindElements(selector) => {
RequestData::new(Method::POST, format!("session/{}/elements", session_id))
.add_body(json!({"using": selector.name, "value": selector.query}))
}
Command::FindElementFromElement(element_id, selector) => RequestData::new(
Method::POST,
format!("session/{}/element/{}/element", session_id, element_id),
)
.add_body(json!({"using": selector.name, "value": selector.query})),
Command::FindElementsFromElement(element_id, selector) => RequestData::new(
Method::POST,
format!("session/{}/element/{}/elements", session_id, element_id),
)
.add_body(json!({"using": selector.name, "value": selector.query})),
Command::IsElementSelected(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/selected", session_id, element_id),
),
Command::IsElementDisplayed(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/displayed", session_id, element_id),
),
Command::GetElementAttribute(element_id, attribute_name) => RequestData::new(
Method::GET,
format!(
"session/{}/element/{}/attribute/{}",
session_id, element_id, attribute_name
),
),
Command::GetElementProperty(element_id, property_name) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/property/{}", session_id, element_id, property_name),
),
Command::GetElementCssValue(element_id, property_name) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/css/{}", session_id, element_id, property_name),
),
Command::GetElementText(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/text", session_id, element_id),
),
Command::GetElementTagName(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/name", session_id, element_id),
),
Command::GetElementRect(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/rect", session_id, element_id),
),
Command::IsElementEnabled(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/enabled", session_id, element_id),
),
Command::ElementClick(element_id) => RequestData::new(
Method::POST,
format!("session/{}/element/{}/click", session_id, element_id),
)
.add_body(json!({})),
Command::ElementClear(element_id) => RequestData::new(
Method::POST,
format!("session/{}/element/{}/clear", session_id, element_id),
)
.add_body(json!({})),
Command::ElementSendKeys(element_id, typing_data) => RequestData::new(
Method::POST,
format!("session/{}/element/{}/value", session_id, element_id),
)
.add_body(json!({"text": typing_data.to_string(), "value": typing_data.as_vec() })),
Command::GetPageSource => {
RequestData::new(Method::GET, format!("session/{}/source", session_id))
}
Command::ExecuteScript(script, args) => {
RequestData::new(Method::POST, format!("session/{}/execute/sync", session_id))
.add_body(json!({"script": script, "args": args}))
}
Command::ExecuteAsyncScript(script, args) => {
RequestData::new(Method::POST, format!("session/{}/execute/async", session_id))
.add_body(json!({"script": script, "args": args}))
}
Command::GetAllCookies => {
RequestData::new(Method::GET, format!("session/{}/cookie", session_id))
}
Command::GetNamedCookie(cookie_name) => RequestData::new(
Method::GET,
format!("session/{}/cookie/{}", session_id, cookie_name),
),
Command::AddCookie(cookie) => {
RequestData::new(Method::POST, format!("session/{}/cookie", session_id))
.add_body(json!({ "cookie": cookie }))
}
Command::DeleteCookie(cookie_name) => RequestData::new(
Method::DELETE,
format!("session/{}/cookie/{}", session_id, cookie_name),
),
Command::DeleteAllCookies => {
RequestData::new(Method::DELETE, format!("session/{}/cookie", session_id))
}
Command::PerformActions(actions) => {
RequestData::new(Method::POST, format!("session/{}/actions", session_id))
.add_body(json!({"actions": actions.0}))
}
Command::ReleaseActions => {
RequestData::new(Method::DELETE, format!("session/{}/actions", session_id))
}
Command::DismissAlert => {
RequestData::new(Method::POST, format!("session/{}/alert/dismiss", session_id))
.add_body(json!({}))
}
Command::AcceptAlert => {
RequestData::new(Method::POST, format!("session/{}/alert/accept", session_id))
.add_body(json!({}))
}
Command::GetAlertText => {
RequestData::new(Method::GET, format!("session/{}/alert/text", session_id))
}
Command::SendAlertText(typing_data) => {
RequestData::new(Method::POST, format!("session/{}/alert/text", session_id))
.add_body(json!({
"value": typing_data.as_vec(), "text": typing_data.to_string()
}))
}
Command::PrintPage(params) => {
RequestData::new(Method::POST, format!("/session/{}/print", session_id)).add_body(
serde_json::to_value(params)
.expect("Fail to parse Print Page Parameters to json"),
)
}
Command::TakeScreenshot => {
RequestData::new(Method::GET, format!("session/{}/screenshot", session_id))
}
Command::TakeElementScreenshot(element_id) => RequestData::new(
Method::GET,
format!("session/{}/element/{}/screenshot", session_id, element_id),
),
Command::GetLogTypes => {
RequestData::new(Method::GET, format!("session/{}/log/types", session_id))
}
Command::GetLog(log_type) => {
RequestData::new(Method::POST, format!("session/{}/log", session_id))
.add_body(json!({ "type": log_type }))
}
Command::ExtensionCommand(command) => {
let request_data = RequestData::new(
command.method(),
format!("session/{}{}", session_id, command.endpoint()),
);
match command.parameters_json() {
Some(param) => request_data.add_body(param),
None => request_data,
}
}
}
}
}