From 41c604968ae92e881ae40c55963c5bdbf681a000 Mon Sep 17 00:00:00 2001 From: James Graham Date: Thu, 15 Jan 2015 19:20:55 +0000 Subject: [PATCH] webdriver: Initial commit for seperate webdriver module. Source-Repo: https://github.com/mozilla/webdriver-rust Source-Revision: da53f261e0ee97c68cfd567e3dac1aa4bcc1a151 --HG-- extra : subtree_source : http%3A//tristan.corp.lon2.mozilla.com%3A8000 extra : subtree_revision : d3a8d3cc76bca7cf4f714aba074a55be7a4c91d2 --- testing/webdriver/Cargo.toml | 15 + testing/webdriver/src/command.rs | 805 +++++++++++++++++++++++ testing/webdriver/src/common.rs | 284 ++++++++ testing/webdriver/src/httpserver.rs | 238 +++++++ testing/webdriver/src/lib.rs | 29 + testing/webdriver/src/marionette_bits.rs | 3 + testing/webdriver/src/messagebuilder.rs | 186 ++++++ testing/webdriver/src/response.rs | 149 +++++ 8 files changed, 1709 insertions(+) create mode 100644 testing/webdriver/Cargo.toml create mode 100644 testing/webdriver/src/command.rs create mode 100644 testing/webdriver/src/common.rs create mode 100644 testing/webdriver/src/httpserver.rs create mode 100644 testing/webdriver/src/lib.rs create mode 100644 testing/webdriver/src/marionette_bits.rs create mode 100644 testing/webdriver/src/messagebuilder.rs create mode 100644 testing/webdriver/src/response.rs diff --git a/testing/webdriver/Cargo.toml b/testing/webdriver/Cargo.toml new file mode 100644 index 000000000000..8ff9e6b1dd8e --- /dev/null +++ b/testing/webdriver/Cargo.toml @@ -0,0 +1,15 @@ +[package] + +name = "webdriver" +version = "0.0.1" +authors = ["James Graham "] + +[dependencies] +log = "0.1.9" +rustc-serialize = "0.2.7" + +[dependencies.hyper] +git = "https://github.com/hyperium/hyper.git" + +[dependencies.uuid] +git = "https://github.com/rust-lang/uuid.git" diff --git a/testing/webdriver/src/command.rs b/testing/webdriver/src/command.rs new file mode 100644 index 000000000000..70eead3fd4ae --- /dev/null +++ b/testing/webdriver/src/command.rs @@ -0,0 +1,805 @@ +use std::collections::BTreeMap; +use rustc_serialize::json::{ToJson, Json}; +use regex::Captures; + +use common::{WebDriverResult, WebDriverError, ErrorStatus, Nullable, WebElement, FrameId, LocatorStrategy}; +use response::Date; //TODO: Put all these types in a specific file +use messagebuilder::MatchType; + + +#[derive(PartialEq)] +pub enum WebDriverCommand { + NewSession, + DeleteSession, + Get(GetParameters), + GetCurrentUrl, + GoBack, + GoForward, + Refresh, + GetTitle, + GetWindowHandle, + GetWindowHandles, + Close, + SetWindowSize(WindowSizeParameters), + GetWindowSize, + MaximizeWindow, +// FullscreenWindow // Not supported in marionette + SwitchToWindow(SwitchToWindowParameters), + SwitchToFrame(SwitchToFrameParameters), + SwitchToParentFrame, + FindElement(LocatorParameters), + FindElements(LocatorParameters), + IsDisplayed(WebElement), + IsSelected(WebElement), + GetElementAttribute(WebElement, String), + GetCSSValue(WebElement, String), + GetElementText(WebElement), + GetElementTagName(WebElement), + GetElementRect(WebElement), + IsEnabled(WebElement), + ExecuteScript(JavascriptCommandParameters), + ExecuteAsyncScript(JavascriptCommandParameters), + GetCookie(GetCookieParameters), + AddCookie(AddCookieParameters), + SetTimeouts(TimeoutsParameters), + //Actions(ActionsParameters), + ElementClick(WebElement), + ElementTap(WebElement), + ElementClear(WebElement), + ElementSendKeys(WebElement, SendKeysParameters), + DismissAlert, + AcceptAlert, + GetAlertText, + SendAlertText(SendAlertTextParameters), + TakeScreenshot(TakeScreenshotParameters) +} + +#[derive(PartialEq)] +pub struct WebDriverMessage { + pub session_id: Option, + pub command: WebDriverCommand +} + +impl WebDriverMessage { + pub fn new(session_id: Option, command: WebDriverCommand) -> WebDriverMessage { + WebDriverMessage { + session_id: session_id, + command: command + } + } + + pub fn from_http(match_type: MatchType, params: &Captures, body: &str) -> WebDriverResult { + let session_id = WebDriverMessage::get_session_id(params); + let body_data = if body != "" { + debug!("Got request body {}", body); + match Json::from_str(body) { + Ok(x) => x, + Err(_) => return Err(WebDriverError::new(ErrorStatus::UnknownError, + format!("Failed to decode request body as json: {}", body).as_slice())) + } + } else { + Json::Null + }; + let command = match match_type { + MatchType::NewSession => WebDriverCommand::NewSession, + MatchType::DeleteSession => WebDriverCommand::DeleteSession, + MatchType::Get => { + let parameters: GetParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::Get(parameters) + }, + MatchType::GetCurrentUrl => WebDriverCommand::GetCurrentUrl, + MatchType::GoBack => WebDriverCommand::GoBack, + MatchType::GoForward => WebDriverCommand::GoForward, + MatchType::Refresh => WebDriverCommand::Refresh, + MatchType::GetTitle => WebDriverCommand::GetTitle, + MatchType::GetWindowHandle => WebDriverCommand::GetWindowHandle, + MatchType::GetWindowHandles => WebDriverCommand::GetWindowHandles, + MatchType::Close => WebDriverCommand::Close, + MatchType::SetTimeouts => { + let parameters: TimeoutsParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::SetTimeouts(parameters) + }, + MatchType::SetWindowSize => { + let parameters: WindowSizeParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::SetWindowSize(parameters) + }, + MatchType::GetWindowSize => WebDriverCommand::GetWindowSize, + MatchType::MaximizeWindow => WebDriverCommand::MaximizeWindow, + MatchType::SwitchToWindow => { + let parameters: SwitchToWindowParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::SwitchToWindow(parameters) + } + MatchType::SwitchToFrame => { + let parameters: SwitchToFrameParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::SwitchToFrame(parameters) + }, + MatchType::SwitchToParentFrame => WebDriverCommand::SwitchToParentFrame, + MatchType::FindElement => { + let parameters: LocatorParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::FindElement(parameters) + }, + MatchType::FindElements => { + let parameters: LocatorParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::FindElements(parameters) + }, + MatchType::IsDisplayed => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::IsDisplayed(element) + }, + MatchType::IsSelected => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::IsSelected(element) + }, + MatchType::GetElementAttribute => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + let attr = try_opt!(params.name("name"), + ErrorStatus::InvalidArgument, + "Missing name parameter").to_string(); + WebDriverCommand::GetElementAttribute(element, attr) + }, + MatchType::GetCSSValue => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + let property = try_opt!(params.name("propertyName"), + ErrorStatus::InvalidArgument, + "Missing propertyName parameter").to_string(); + WebDriverCommand::GetCSSValue(element, property) + }, + MatchType::GetElementText => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::GetElementText(element) + }, + MatchType::GetElementTagName => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::GetElementTagName(element) + }, + MatchType::GetElementRect => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::GetElementRect(element) + }, + MatchType::IsEnabled => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::IsEnabled(element) + }, + MatchType::ElementClick => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::ElementClick(element) + }, + MatchType::ElementTap => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::ElementTap(element) + }, + MatchType::ElementClear => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + WebDriverCommand::ElementClear(element) + }, + MatchType::ElementSendKeys => { + let element_id = try_opt!(params.name("elementId"), + ErrorStatus::InvalidArgument, + "Missing elementId parameter"); + let element = WebElement::new(element_id.to_string()); + let parameters: SendKeysParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::ElementSendKeys(element, parameters) + }, + MatchType::ExecuteScript => { + let parameters: JavascriptCommandParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::ExecuteScript(parameters) + }, + MatchType::ExecuteAsyncScript => { + let parameters: JavascriptCommandParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::ExecuteAsyncScript(parameters) + }, + MatchType::GetCookie => { + let parameters: GetCookieParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::GetCookie(parameters) + }, + MatchType::AddCookie => { + let parameters: AddCookieParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::AddCookie(parameters) + }, + MatchType::DismissAlert => { + WebDriverCommand::DismissAlert + }, + MatchType::AcceptAlert => { + WebDriverCommand::AcceptAlert + }, + MatchType::GetAlertText => { + WebDriverCommand::GetAlertText + }, + MatchType::SendAlertText => { + let parameters: SendAlertTextParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::SendAlertText(parameters) + } + MatchType::TakeScreenshot => { + let parameters: TakeScreenshotParameters = try!(Parameters::from_json(&body_data)); + WebDriverCommand::TakeScreenshot(parameters) + } + }; + Ok(WebDriverMessage::new(session_id, command)) + } + + fn get_session_id(params: &Captures) -> Option { + params.name("sessionId").map(|x| x.to_string()) + } +} + +impl ToJson for WebDriverMessage { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + let parameters = match self.command { + WebDriverCommand::NewSession | + WebDriverCommand::DeleteSession | WebDriverCommand::GetCurrentUrl | + WebDriverCommand::GoBack | WebDriverCommand::GoForward | WebDriverCommand::Refresh | + WebDriverCommand::GetTitle | WebDriverCommand::GetWindowHandle | + WebDriverCommand::GetWindowHandles | WebDriverCommand::Close | + WebDriverCommand::GetWindowSize | WebDriverCommand::MaximizeWindow | + WebDriverCommand::SwitchToParentFrame | WebDriverCommand::IsDisplayed(_) | + WebDriverCommand::IsSelected(_) | WebDriverCommand::GetElementAttribute(_, _) | + WebDriverCommand::GetCSSValue(_, _) | WebDriverCommand::GetElementText(_) | + WebDriverCommand::GetElementTagName(_) | WebDriverCommand::GetElementRect(_) | + WebDriverCommand::IsEnabled(_) | WebDriverCommand::AddCookie(_) | + WebDriverCommand::DismissAlert | WebDriverCommand::AcceptAlert | + WebDriverCommand::GetAlertText | WebDriverCommand::ElementClick(_) | + WebDriverCommand::ElementTap(_) | WebDriverCommand::ElementClear(_) => { + None + }, + WebDriverCommand::Get(ref x) => Some(x.to_json()), + WebDriverCommand::SetTimeouts(ref x) => Some(x.to_json()), + WebDriverCommand::SetWindowSize(ref x) => Some(x.to_json()), + WebDriverCommand::SwitchToWindow(ref x) => Some(x.to_json()), + WebDriverCommand::SwitchToFrame(ref x) => Some(x.to_json()), + WebDriverCommand::FindElement(ref x) => Some(x.to_json()), + WebDriverCommand::FindElements(ref x) => Some(x.to_json()), + WebDriverCommand::ElementSendKeys(_, ref x) => Some(x.to_json()), + WebDriverCommand::ExecuteScript(ref x) | + WebDriverCommand::ExecuteAsyncScript(ref x) => Some(x.to_json()), + WebDriverCommand::GetCookie(ref x) => Some(x.to_json()), + WebDriverCommand::SendAlertText(ref x) => Some(x.to_json()), + WebDriverCommand::TakeScreenshot(ref x) => Some(x.to_json()) + }; + if parameters.is_some() { + data.insert("parameters".to_string(), parameters.unwrap()); + } + Json::Object(data) + } +} + +trait Parameters { + fn from_json(body: &Json) -> WebDriverResult; +} + +#[derive(PartialEq)] +pub struct GetParameters { + url: String +} + +impl Parameters for GetParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "Message body was not an object"); + let url = try_opt!( + try_opt!(data.get("url"), + ErrorStatus::InvalidArgument, + "Missing 'url' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'url' not a string"); + return Ok(GetParameters { + url: url.to_string() + }) + } +} + +impl ToJson for GetParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("url".to_string(), self.url.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct TimeoutsParameters { + type_: String, + ms: u64 +} + +impl Parameters for TimeoutsParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "Message body was not an object"); + let type_ = try_opt!( + try_opt!(data.get("type"), + ErrorStatus::InvalidArgument, + "Missing 'type' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'type' not a string"); + + let ms = try_opt!( + try_opt!(data.get("ms"), + ErrorStatus::InvalidArgument, + "Missing 'ms' parameter").as_u64(), + ErrorStatus::InvalidArgument, + "'ms' not an integer"); + return Ok(TimeoutsParameters { + type_: type_.to_string(), + ms: ms + }) + } +} + +impl ToJson for TimeoutsParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("type".to_string(), self.type_.to_json()); + data.insert("ms".to_string(), self.ms.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct WindowSizeParameters { + width: u64, + height: u64 +} + +impl Parameters for WindowSizeParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "Message body was not an object"); + let height = try_opt!( + try_opt!(data.get("height"), + ErrorStatus::InvalidArgument, + "Missing 'height' parameter").as_u64(), + ErrorStatus::InvalidArgument, + "'height' is not a positive integer"); + let width = try_opt!( + try_opt!(data.get("width"), + ErrorStatus::InvalidArgument, + "Missing width parameter").as_u64(), + ErrorStatus::InvalidArgument, + "'width' is not a positive integer"); + return Ok(WindowSizeParameters { + height: height, + width: width + }) + } +} + +impl ToJson for WindowSizeParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("width".to_string(), self.width.to_json()); + data.insert("height".to_string(), self.height.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct SwitchToWindowParameters { + handle: String +} + +impl Parameters for SwitchToWindowParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "Message body was not an object"); + let handle = try_opt!( + try_opt!(data.get("handle"), + ErrorStatus::InvalidArgument, + "Missing 'handle' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'handle' not a string"); + return Ok(SwitchToWindowParameters { + handle: handle.to_string() + }) + } +} + +impl ToJson for SwitchToWindowParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("handle".to_string(), self.handle.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct LocatorParameters { + using: LocatorStrategy, + value: String +} + +impl Parameters for LocatorParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "Message body was not an object"); + + let using = try!(LocatorStrategy::from_json( + try_opt!(data.get("using"), + ErrorStatus::InvalidArgument, + "Missing 'using' parameter"))); + + let value = try_opt!( + try_opt!(data.get("value"), + ErrorStatus::InvalidArgument, + "Missing 'using' parameter").as_string(), + ErrorStatus::InvalidArgument, + "Could not convert using to string").to_string(); + + return Ok(LocatorParameters { + using: using, + value: value + }) + } +} + +impl ToJson for LocatorParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("using".to_string(), self.using.to_json()); + data.insert("value".to_string(), self.value.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct SwitchToFrameParameters { + pub id: FrameId +} + +impl Parameters for SwitchToFrameParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), + ErrorStatus::UnknownError, + "Message body was not an object"); + let id = try!(FrameId::from_json(try_opt!(data.get("id"), + ErrorStatus::UnknownError, + "Missing 'id' parameter"))); + + Ok(SwitchToFrameParameters { + id: id + }) + } +} + +impl ToJson for SwitchToFrameParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("id".to_string(), self.id.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct SendKeysParameters { + pub value: String +} + +impl Parameters for SendKeysParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), + ErrorStatus::InvalidArgument, + "Message body was not an object"); + let value = try_opt!(try_opt!(data.get("value"), + ErrorStatus::InvalidArgument, + "Missing 'value' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'value' not a string").to_string(); + + Ok(SendKeysParameters { + value: value + }) + } +} + +impl ToJson for SendKeysParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("value".to_string(), self.value.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct JavascriptCommandParameters { + script: String, + args: Nullable> +} + +impl Parameters for JavascriptCommandParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), + ErrorStatus::InvalidArgument, + "Message body was not an object"); + + let args_json = try_opt!(data.get("args"), + ErrorStatus::InvalidArgument, + "Missing args parameter"); + + let args = try!(Nullable::from_json( + args_json, + |x| { + Ok((try_opt!(x.as_array(), + ErrorStatus::InvalidArgument, + "Failed to convert args to Array")).clone()) + })); + + //TODO: Look for WebElements in args? + let script = try_opt!( + try_opt!(data.get("script"), + ErrorStatus::InvalidArgument, + "Missing script parameter").as_string(), + ErrorStatus::InvalidArgument, + "Failed to convert script to String"); + Ok(JavascriptCommandParameters { + script: script.to_string(), + args: args.clone() + }) + } +} + +impl ToJson for JavascriptCommandParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + //TODO: Wrap script so that it becomes marionette-compatible + data.insert("script".to_string(), self.script.to_json()); + data.insert("args".to_string(), self.args.to_json()); + data.insert("newSandbox".to_string(), false.to_json()); + data.insert("specialPowers".to_string(), false.to_json()); + data.insert("scriptTimeout".to_string(), Json::Null); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct GetCookieParameters { + name: Nullable +} + +impl Parameters for GetCookieParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::InvalidArgument, + "Message body was not an object"); + let name_json = try_opt!(data.get("name"), + ErrorStatus::InvalidArgument, + "Missing 'name' parameter"); + let name = try!(Nullable::from_json( + name_json, + |x| { + Ok(try_opt!(x.as_string(), + ErrorStatus::InvalidArgument, + "Failed to convert name to String").to_string()) + })); + return Ok(GetCookieParameters { + name: name + }) + } +} + +impl ToJson for GetCookieParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("name".to_string(), self.name.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct AddCookieParameters { + pub name: String, + pub value: String, + pub path: Nullable, + pub domain: Nullable, + pub expiry: Nullable, + pub maxAge: Nullable, + pub secure: bool, + pub httpOnly: bool +} + +impl Parameters for AddCookieParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), + ErrorStatus::InvalidArgument, + "Message body was not an object"); + let name = try_opt!( + try_opt!(data.get("name"), + ErrorStatus::InvalidArgument, + "Missing 'name' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'name' is not a string").to_string(); + + let value = try_opt!( + try_opt!(data.get("value"), + ErrorStatus::InvalidArgument, + "Missing 'value' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'value' is not a string").to_string(); + + let path = match data.get("path") { + Some(path_json) => { + try!(Nullable::from_json( + path_json, + |x| { + Ok(try_opt!(x.as_string(), + ErrorStatus::InvalidArgument, + "Failed to convert path to String").to_string()) + })) + }, + None => Nullable::Null + }; + + let domain = match data.get("domain") { + Some(domain_json) => { + try!(Nullable::from_json( + domain_json, + |x| { + Ok(try_opt!(x.as_string(), + ErrorStatus::InvalidArgument, + "Failed to convert domain to String").to_string()) + })) + }, + None => Nullable::Null + }; + + //TODO: This is supposed to support some text format + let expiry = match data.get("expiry") { + Some(expiry_json) => { + try!(Nullable::from_json( + expiry_json, + |x| { + Ok(Date::new(try_opt!(x.as_u64(), + ErrorStatus::InvalidArgument, + "Failed to convert expiry to Date"))) + })) + }, + None => Nullable::Null + }; + + let max_age = match data.get("maxAge") { + Some(max_age_json) => { + try!(Nullable::from_json( + max_age_json, + |x| { + Ok(Date::new(try_opt!(x.as_u64(), + ErrorStatus::InvalidArgument, + "Failed to convert expiry to Date"))) + })) + }, + None => Nullable::Null + }; + + let secure = match data.get("secure") { + Some(x) => try_opt!(x.as_boolean(), + ErrorStatus::InvalidArgument, + "Failed to convert secure to boolean"), + None => false + }; + + let http_only = match data.get("httpOnly") { + Some(x) => try_opt!(x.as_boolean(), + ErrorStatus::InvalidArgument, + "Failed to convert httpOnly to boolean"), + None => false + }; + + return Ok(AddCookieParameters { + name: name, + value: value, + path: path, + domain: domain, + expiry: expiry, + maxAge: max_age, + secure: secure, + httpOnly: http_only + }) + } +} + +impl ToJson for AddCookieParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("name".to_string(), self.name.to_json()); + data.insert("value".to_string(), self.value.to_json()); + data.insert("path".to_string(), self.path.to_json()); + data.insert("domain".to_string(), self.domain.to_json()); + data.insert("expiry".to_string(), self.expiry.to_json()); + data.insert("maxAge".to_string(), self.maxAge.to_json()); + data.insert("secure".to_string(), self.secure.to_json()); + data.insert("httpOnly".to_string(), self.httpOnly.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct SendAlertTextParameters { + keysToSend: String +} + +impl Parameters for SendAlertTextParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::InvalidArgument, + "Message body was not an object"); + let keys = try_opt!( + try_opt!(data.get("keysToSend"), + ErrorStatus::InvalidArgument, + "Missing 'handle' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'keysToSend' not a string").to_string(); + return Ok(SendAlertTextParameters { + keysToSend: keys + }) + } +} + +impl ToJson for SendAlertTextParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("keysToSend".to_string(), self.keysToSend.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub struct TakeScreenshotParameters { + pub element: Nullable +} + +impl Parameters for TakeScreenshotParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), + ErrorStatus::InvalidArgument, + "Message body was not an object"); + let element = match data.get("element") { + Some(element_json) => try!(Nullable::from_json( + element_json, + |x| { + Ok(try!(WebElement::from_json(x))) + })), + None => Nullable::Null + }; + + return Ok(TakeScreenshotParameters { + element: element + }) + } +} + +impl ToJson for TakeScreenshotParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("element".to_string(), self.element.to_json()); + Json::Object(data) + } +} diff --git a/testing/webdriver/src/common.rs b/testing/webdriver/src/common.rs new file mode 100644 index 000000000000..da092af7b028 --- /dev/null +++ b/testing/webdriver/src/common.rs @@ -0,0 +1,284 @@ +use core::num::ToPrimitive; +use rustc_serialize::json::{Json, ToJson, ParserError}; +use rustc_serialize::{Encodable, Encoder}; +use std::collections::BTreeMap; +use std::error::{Error, FromError}; + +static ELEMENT_KEY: &'static str = "element-6066-11e4-a52e-4f735466cecf"; + +#[derive(PartialEq, Show)] +pub enum ErrorStatus { + ElementNotSelectable, + ElementNotVisible, + InvalidArgument, + InvalidCookieDomain, + InvalidElementCoordinates, + InvalidElementState, + InvalidSelector, + InvalidSessionId, + JavascriptError, + MoveTargetOutOfBounds, + NoSuchAlert, + NoSuchElement, + NoSuchFrame, + NoSuchWindow, + ScriptTimeout, + SessionNotCreated, + StaleElementReference, + Timeout, + UnableToSetCookie, + UnexpectedAlertOpen, + UnknownError, + UnknownPath, + UnknownMethod, + UnsupportedOperation, +} + +pub type WebDriverResult = Result; + +#[derive(Show)] +pub struct WebDriverError { + pub status: ErrorStatus, + pub message: String +} + +impl WebDriverError { + pub fn new(status: ErrorStatus, message: &str) -> WebDriverError { + WebDriverError { + status: status, + message: message.to_string() + } + } + + pub fn status_code(&self) -> &'static str { + match self.status { + ErrorStatus::ElementNotSelectable => "element not selectable", + ErrorStatus::ElementNotVisible => "element not visible", + ErrorStatus::InvalidArgument => "invalid argument", + ErrorStatus::InvalidCookieDomain => "invalid cookie domain", + ErrorStatus::InvalidElementCoordinates => "invalid element coordinates", + ErrorStatus::InvalidElementState => "invalid element state", + ErrorStatus::InvalidSelector => "invalid selector", + ErrorStatus::InvalidSessionId => "invalid session id", + ErrorStatus::JavascriptError => "javascript error", + ErrorStatus::MoveTargetOutOfBounds => "move target out of bounds", + ErrorStatus::NoSuchAlert => "no such alert", + ErrorStatus::NoSuchElement => "no such element", + ErrorStatus::NoSuchFrame => "no such frame", + ErrorStatus::NoSuchWindow => "no such window", + ErrorStatus::ScriptTimeout => "script timeout", + ErrorStatus::SessionNotCreated => "session not created", + ErrorStatus::StaleElementReference => "stale element reference", + ErrorStatus::Timeout => "timeout", + ErrorStatus::UnableToSetCookie => "unable to set cookie", + ErrorStatus::UnexpectedAlertOpen => "unexpected alert open", + ErrorStatus::UnknownError => "unknown error", + ErrorStatus::UnknownPath => "unknown command", + ErrorStatus::UnknownMethod => "unknown command", + ErrorStatus::UnsupportedOperation => "unsupported operation", + } + } + + pub fn http_status(&self) -> u32 { + match self.status { + ErrorStatus::UnknownPath => 404u32, + ErrorStatus::UnknownMethod => 405u32, + _ => 500u32 + } + } + + pub fn to_json_string(&self) -> String { + self.to_json().to_string() + } +} + +impl ToJson for WebDriverError { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("status".to_string(), self.status_code().to_json()); + data.insert("error".to_string(), self.message.to_json()); + Json::Object(data) + } +} + +impl Error for WebDriverError { + fn description(&self) -> &str { + self.status_code() + } + + fn detail(&self) -> Option { + Some(self.message.clone()) + } + + fn cause(&self) -> Option<&Error> { + None + } +} + +impl FromError for WebDriverError { + fn from_error(err: ParserError) -> WebDriverError { + let msg = format!("{:?}", err); + WebDriverError::new(ErrorStatus::UnknownError, msg.as_slice()) + } +} + +#[derive(PartialEq, Clone, Show)] +pub enum Nullable { + Value(T), + Null +} + +impl Nullable { + pub fn is_null(&self) -> bool { + match *self { + Nullable::Value(_) => false, + Nullable::Null => true + } + } + + pub fn is_value(&self) -> bool { + match *self { + Nullable::Value(_) => true, + Nullable::Null => false + } + } +} + +impl Nullable { + //This is not very pretty + pub fn from_json WebDriverResult>(value: &Json, f: F) -> WebDriverResult> { + if value.is_null() { + Ok(Nullable::Null) + } else { + Ok(Nullable::Value(try!(f(value)))) + } + } +} + +impl ToJson for Nullable { + fn to_json(&self) -> Json { + match *self { + Nullable::Value(ref x) => x.to_json(), + Nullable::Null => Json::Null + } + } +} + +impl Encodable for Nullable { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + match *self { + Nullable::Value(ref x) => x.to_json().encode(s), + Nullable::Null => s.emit_option_none() + } + } +} + +#[derive(PartialEq)] +pub struct WebElement { + pub id: String +} + +impl WebElement { + pub fn new(id: String) -> WebElement { + WebElement { + id: id + } + } + + pub fn from_json(data: &Json) -> WebDriverResult { + let object = try_opt!(data.as_object(), + ErrorStatus::InvalidArgument, + "Could not convert webelement to object"); + let id_value = try_opt!(object.get(ELEMENT_KEY), + ErrorStatus::InvalidArgument, + "Could not find webelement key"); + + let id = try_opt!(id_value.as_string(), + ErrorStatus::InvalidArgument, + "Could not convert web element to string").to_string(); + + Ok(WebElement::new(id)) + } +} + +impl ToJson for WebElement { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert(ELEMENT_KEY.to_string(), self.id.to_json()); + Json::Object(data) + } +} + +#[derive(PartialEq)] +pub enum FrameId { + Short(u16), + Element(WebElement), + Null +} + +impl FrameId { + pub fn from_json(data: &Json) -> WebDriverResult { + match data { + &Json::U64(x) => { + let id = try_opt!(x.to_u16(), + ErrorStatus::NoSuchFrame, + "frame id out of range"); + Ok(FrameId::Short(id)) + }, + &Json::Null => Ok(FrameId::Null), + &Json::String(ref x) => Ok(FrameId::Element(WebElement::new(x.clone()))), + _ => Err(WebDriverError::new(ErrorStatus::NoSuchFrame, + "frame id has unexpected type")) + } + } +} + +impl ToJson for FrameId { + fn to_json(&self) -> Json { + match *self { + FrameId::Short(x) => { + Json::U64(x as u64) + }, + FrameId::Element(ref x) => { + Json::String(x.id.clone()) + }, + FrameId::Null => { + Json::Null + } + } + } +} + +#[derive(PartialEq)] +pub enum LocatorStrategy { + CSSSelector, + LinkText, + PartialLinkText, + XPath +} + +impl LocatorStrategy { + pub fn from_json(body: &Json) -> WebDriverResult { + match try_opt!(body.as_string(), + ErrorStatus::InvalidArgument, + "Cound not convert strategy to string") { + "css selector" => Ok(LocatorStrategy::CSSSelector), + "link text" => Ok(LocatorStrategy::LinkText), + "partial link text" => Ok(LocatorStrategy::PartialLinkText), + "xpath" => Ok(LocatorStrategy::XPath), + _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument, + "Unknown locator strategy")) + } + } +} + +impl ToJson for LocatorStrategy { + fn to_json(&self) -> Json { + Json::String(match *self { + LocatorStrategy::CSSSelector => "css selector", + LocatorStrategy::LinkText => "link text", + LocatorStrategy::PartialLinkText => "partial link text", + LocatorStrategy::XPath => "xpath" + }.to_string()) + } +} diff --git a/testing/webdriver/src/httpserver.rs b/testing/webdriver/src/httpserver.rs new file mode 100644 index 000000000000..0b82b92cbf27 --- /dev/null +++ b/testing/webdriver/src/httpserver.rs @@ -0,0 +1,238 @@ +use std::io::net::ip::IpAddr; +use std::num::FromPrimitive; +use std::sync::Mutex; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread::Thread; + +use hyper::header::common::ContentLength; +use hyper::method::Method; +use hyper::server::{Server, Handler, Request, Response}; +use hyper::uri::RequestUri::AbsolutePath; + +use command::{WebDriverMessage, WebDriverCommand}; +use common::{WebDriverResult, ErrorStatus, WebDriverError}; +use messagebuilder::{get_builder, MessageBuilder}; +use response::WebDriverResponse; + +enum DispatchMessage { + HandleWebDriver(WebDriverMessage, Sender>), + Quit +} + +#[derive(PartialEq, Clone)] +pub struct Session { + id: String +} + +impl Session { + fn new(id: String) -> Session { + Session { + id: id + } + } +} + +pub trait WebDriverHandler : Send { + fn handle_command(&mut self, session: &Option, msg: &WebDriverMessage) -> WebDriverResult; + fn delete_session(&mut self, session: &Option); +} + +struct Dispatcher { + handler: T, + session: Option +} + +impl Dispatcher { + fn new(handler: T) -> Dispatcher { + Dispatcher { + handler: handler, + session: None + } + } + + fn run(&mut self, msg_chan: Receiver) { + loop { + match msg_chan.recv() { + Ok(DispatchMessage::HandleWebDriver(msg, resp_chan)) => { + let resp = match self.check_session(&msg) { + Ok(_) => self.handler.handle_command(&self.session, &msg), + Err(e) => Err(e) + }; + + match resp { + Ok(WebDriverResponse::NewSession(ref new_session)) => { + self.session = Some(Session::new(new_session.sessionId.clone())); + }, + Ok(WebDriverResponse::DeleteSession) => { + debug!("Deleting session"); + self.handler.delete_session(&self.session); + self.session = None; + }, + _ => {} + } + + if resp_chan.send(resp).is_err() { + error!("Sending response to the main thread failed"); + }; + }, + Ok(DispatchMessage::Quit) => { + break; + }, + Err(_) => panic!("Error receiving message in handler") + } + } + } + + fn check_session(&self, msg: &WebDriverMessage) -> WebDriverResult<()> { + match msg.session_id { + Some(ref msg_session_id) => { + match self.session { + Some(ref existing_session) => { + if existing_session.id != *msg_session_id { + Err(WebDriverError::new( + ErrorStatus::InvalidSessionId, + format!("Got unexpected session id {} expected {}", + msg_session_id, + existing_session.id).as_slice())) + } else { + Ok(()) + } + }, + None => Ok(()) + } + }, + None => { + match self.session { + Some(_) => { + match msg.command { + WebDriverCommand::NewSession => { + Err(WebDriverError::new( + ErrorStatus::UnknownError, + "Session is already started")) + }, + _ => { + //This should be impossible + error!("Got a message with no session id"); + Err(WebDriverError::new( + ErrorStatus::UnknownError, + "Got a command with no session?!")) + } + } + }, + None => { + match msg.command { + WebDriverCommand::NewSession => Ok(()), + + _ => Err(WebDriverError::new( + ErrorStatus::SessionNotCreated, + "Tried to run a command before creating a session")) + } + } + } + } + } + } +} + +struct HttpHandler { + chan: Mutex>, + builder: Mutex +} + +impl HttpHandler { + fn new(builder: MessageBuilder, chan: Sender) -> HttpHandler { + HttpHandler { + chan: Mutex::new(chan), + builder: Mutex::new(builder) + } + } +} + +impl Handler for HttpHandler { + fn handle(&self, req: Request, res: Response) { + let mut req = req; + let mut res = res; + + let body = match req.method { + Method::Post => req.read_to_string().unwrap(), + _ => "".to_string() + }; + debug!("Got request {} {:?}", req.method, req.uri); + match req.uri { + AbsolutePath(path) => { + let msg_result = { + // The fact that this locks for basically the whole request doesn't + // matter as long as we are only handling one request at a time. + match self.builder.lock() { + Ok(ref builder) => { + builder.from_http(req.method, path.as_slice(), body.as_slice()) + }, + Err(_) => return + } + }; + let (status, resp_body) = match msg_result { + Ok(message) => { + let (send_res, recv_res) = channel(); + match self.chan.lock() { + Ok(ref c) => { + let res = c.send(DispatchMessage::HandleWebDriver(message, + send_res)); + match res { + Ok(x) => x, + Err(_) => { + error!("Something terrible happened"); + return + } + } + }, + Err(_) => { + error!("Something terrible happened"); + return + } + } + match recv_res.recv() { + Ok(data) => match data { + Ok(response) => (200, response.to_json_string()), + Err(err) => (err.http_status(), err.to_json_string()), + }, + Err(_) => panic!("Error reading response") + } + }, + Err(err) => { + (err.http_status(), err.to_json_string()) + } + }; + if status != 200 { + error!("Returning status code {}", status); + error!("Returning body {}", resp_body); + } else { + debug!("Returning status code {}", status); + debug!("Returning body {}", resp_body); + } + { + let status_code = res.status_mut(); + *status_code = FromPrimitive::from_u32(status).unwrap(); + } + res.headers_mut().set(ContentLength(resp_body.len() as u64)); + let mut stream = res.start(); + stream.write_str(resp_body.as_slice()).unwrap(); + stream.unwrap().end().unwrap(); + }, + _ => {} + } + } +} + +pub fn start(ip_address: IpAddr, port: u16, handler: T) { + let server = Server::http(ip_address, port); + + let (msg_send, msg_recv) = channel(); + + Thread::spawn(move || { + let mut dispatcher = Dispatcher::new(handler); + dispatcher.run(msg_recv) + }); + let builder = get_builder(); + let http_handler = HttpHandler::new(builder, msg_send.clone()); + server.listen(http_handler).unwrap(); +} diff --git a/testing/webdriver/src/lib.rs b/testing/webdriver/src/lib.rs new file mode 100644 index 000000000000..1c2e5b009f24 --- /dev/null +++ b/testing/webdriver/src/lib.rs @@ -0,0 +1,29 @@ +//Until it's clear what the unstable things are replaced by +#![allow(unstable)] +#![allow(non_snake_case)] + +#[macro_use] extern crate log; +extern crate "rustc-serialize" as rustc_serialize; +extern crate core; +extern crate hyper; +extern crate regex; + +macro_rules! try_opt { + ($expr:expr, $err_type:expr, $err_msg:expr) => ({ + match $expr { + Some(x) => x, + None => return Err(WebDriverError::new($err_type, $err_msg)) + } + }) +} + +pub mod command; +pub mod common; +pub mod httpserver; +pub mod response; +mod messagebuilder; + + +#[test] +fn it_works() { +} diff --git a/testing/webdriver/src/marionette_bits.rs b/testing/webdriver/src/marionette_bits.rs new file mode 100644 index 000000000000..08a02905bbe3 --- /dev/null +++ b/testing/webdriver/src/marionette_bits.rs @@ -0,0 +1,3 @@ +use std::sync::Mutex; + + diff --git a/testing/webdriver/src/messagebuilder.rs b/testing/webdriver/src/messagebuilder.rs new file mode 100644 index 000000000000..5a057ad0e3eb --- /dev/null +++ b/testing/webdriver/src/messagebuilder.rs @@ -0,0 +1,186 @@ +use regex::{Regex, Captures}; + +use hyper::method::Method; +use hyper::method::Method::{Get, Post, Delete}; + +use command::{WebDriverMessage}; +use common::{WebDriverResult, WebDriverError, ErrorStatus}; + +#[derive(Clone, Copy)] +pub enum MatchType { + NewSession, + DeleteSession, + Get, + GetCurrentUrl, + GoBack, + GoForward, + Refresh, + GetTitle, + GetWindowHandle, + GetWindowHandles, + Close, + SetWindowSize, + GetWindowSize, + MaximizeWindow, + SwitchToWindow, + SwitchToFrame, + SwitchToParentFrame, + FindElement, + FindElements, + IsDisplayed, + IsSelected, + GetElementAttribute, + GetCSSValue, + GetElementText, + GetElementTagName, + GetElementRect, + IsEnabled, + ExecuteScript, + ExecuteAsyncScript, + GetCookie, + AddCookie, + SetTimeouts, + //Actions XXX - once I understand the spec, perhaps + ElementClick, + ElementTap, + ElementClear, + ElementSendKeys, + DismissAlert, + AcceptAlert, + GetAlertText, + SendAlertText, + TakeScreenshot +} + +#[derive(Clone)] +pub struct RequestMatcher { + method: Method, + path_regexp: Regex, + match_type: MatchType +} + +impl RequestMatcher { + pub fn new(method: Method, path: &str, match_type: MatchType) -> RequestMatcher { + let path_regexp = RequestMatcher::compile_path(path); + RequestMatcher { + method: method, + path_regexp: path_regexp, + match_type: match_type + } + } + + pub fn get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option) { + let captures = self.path_regexp.captures(path); + (method == self.method, captures) + } + + fn compile_path(path: &str) -> Regex { + let mut rv = String::new(); + rv.push_str("^"); + let mut components = path.split('/'); + for component in components { + if component.starts_with("{") { + if !component.ends_with("}") { + panic!("Invalid url pattern") + } + rv.push_str(format!("(?P<{}>[^/]+)/", &component[1..component.len()-1]).as_slice()); + } else { + rv.push_str(format!("{}/", component).as_slice()); + } + } + //Remove the trailing / + rv.pop(); + rv.push_str("$"); + //This will fail at runtime if the regexp is invalid + Regex::new(rv.as_slice()).unwrap() + } +} + +pub struct MessageBuilder { + http_matchers: Vec<(Method, RequestMatcher)> +} + +impl MessageBuilder { + pub fn new() -> MessageBuilder { + MessageBuilder { + http_matchers: vec![] + } + } + + pub fn from_http(&self, method: Method, path: &str, body: &str) -> WebDriverResult { + let mut error = ErrorStatus::UnknownPath; + for &(ref match_method, ref matcher) in self.http_matchers.iter() { + if method == *match_method { + let (method_match, captures) = matcher.get_match(method.clone(), path); + if captures.is_some() { + if method_match { + return WebDriverMessage::from_http(matcher.match_type, + &captures.unwrap(), + body) + } else { + error = ErrorStatus::UnknownMethod; + } + } + } + } + Err(WebDriverError::new(error, + format!("{} {} did not match a known command", method, path).as_slice())) + } + + pub fn add(&mut self, method: Method, path: &str, match_type: MatchType) { + let http_matcher = RequestMatcher::new(method.clone(), path, match_type); + self.http_matchers.push((method, http_matcher)); + } +} + +pub fn get_builder() -> MessageBuilder { + let mut builder = MessageBuilder::new(); + let matchers = vec![(Post, "/session", MatchType::NewSession), + (Delete, "/session/{sessionId}", MatchType::DeleteSession), + (Post, "/session/{sessionId}/url", MatchType::Get), + (Get, "/session/{sessionId}/url", MatchType::GetCurrentUrl), + (Post, "/session/{sessionId}/back", MatchType::GoBack), + (Post, "/session/{sessionId}/forward", MatchType::GoForward), + (Post, "/session/{sessionId}/refresh", MatchType::Refresh), + (Get, "/session/{sessionId}/title", MatchType::GetTitle), + (Get, "/session/{sessionId}/window_handle", MatchType::GetWindowHandle), + (Get, "/session/{sessionId}/window_handles", MatchType::GetWindowHandles), + (Delete, "/session/{sessionId}/window_handle", MatchType::Close), + (Post, "/session/{sessionId}/window/size", MatchType::SetWindowSize), + (Get, "/session/{sessionId}/window/size", MatchType::GetWindowSize), + (Post, "/session/{sessionId}/window/maximize", MatchType::MaximizeWindow), + (Post, "/session/{sessionId}/window", MatchType::SwitchToWindow), + (Post, "/session/{sessionId}/frame", MatchType::SwitchToFrame), + (Post, "/session/{sessionId}/frame/parent", MatchType::SwitchToParentFrame), + (Post, "/session/{sessionId}/element", MatchType::FindElement), + (Post, "/session/{sessionId}/elements", MatchType::FindElements), + (Get, "/session/{sessionId}/element/{elementId}/displayed", MatchType::IsDisplayed), + (Get, "/session/{sessionId}/element/{elementId}/selected", MatchType::IsSelected), + (Get, "/session/{sessionId}/element/{elementId}/attribute/{name}", MatchType::GetElementAttribute), + (Get, "/session/{sessionId}/element/{elementId}/css/{propertyName}", MatchType::GetCSSValue), + (Get, "/session/{sessionId}/element/{elementId}/text", MatchType::GetElementText), + (Get, "/session/{sessionId}/element/{elementId}/name", MatchType::GetElementTagName), + (Get, "/session/{sessionId}/element/{elementId}/rect", MatchType::GetElementRect), + (Get, "/session/{sessionId}/element/{elementId}/enabled", MatchType::IsEnabled), + (Post, "/session/{sessionId}/execute", MatchType::ExecuteScript), + (Post, "/session/{sessionId}/execute_async", MatchType::ExecuteAsyncScript), + (Get, "/session/{sessionId}/cookie", MatchType::GetCookie), + (Post, "/session/{sessionId}/cookie", MatchType::AddCookie), + (Post, "/session/{sessionId}/timeouts", MatchType::SetTimeouts), + //(Post, "/session/{sessionId}/actions", MatchType::Actions), + (Post, "/session/{sessionId}/element/{elementId}/click", MatchType::ElementClick), + (Post, "/session/{sessionId}/element/{elementId}/tap", MatchType::ElementTap), + (Post, "/session/{sessionId}/element/{elementId}/clear", MatchType::ElementClear), + (Post, "/session/{sessionId}/element/{elementId}/sendKeys", MatchType::ElementSendKeys), + (Post, "/session/{sessionId}/dismiss_alert", MatchType::DismissAlert), + (Post, "/session/{sessionId}/accept_alert", MatchType::AcceptAlert), + (Get, "/session/{sessionId}/alert_text", MatchType::GetAlertText), + (Post, "/session/{sessionId}/alert_text", MatchType::SendAlertText), + (Get, "/session/{sessionId}/screenshot", MatchType::TakeScreenshot) + ]; + debug!("Creating routes"); + for &(ref method, ref url, ref match_type) in matchers.iter() { + builder.add(method.clone(), *url, *match_type); + } + builder +} diff --git a/testing/webdriver/src/response.rs b/testing/webdriver/src/response.rs new file mode 100644 index 000000000000..421f28cc0a0a --- /dev/null +++ b/testing/webdriver/src/response.rs @@ -0,0 +1,149 @@ +use rustc_serialize::json; +use rustc_serialize::json::ToJson; + +use common::Nullable; + +#[derive(Show)] +pub enum WebDriverResponse { + NewSession(NewSessionResponse), + DeleteSession, + WindowSize(WindowSizeResponse), + ElementRect(ElementRectResponse), + Cookie(CookieResponse), + Generic(ValueResponse), + Void +} + +impl WebDriverResponse { + pub fn to_json_string(self) -> String { + match self { + WebDriverResponse::NewSession(x) => json::encode(&x), + WebDriverResponse::DeleteSession => "".to_string(), + WebDriverResponse::WindowSize(x) => json::encode(&x), + WebDriverResponse::ElementRect(x) => json::encode(&x), + WebDriverResponse::Cookie(x) => json::encode(&x), + WebDriverResponse::Generic(x) => json::encode(&x), + WebDriverResponse::Void => "".to_string() + } + } +} + +#[derive(RustcEncodable, Show)] +pub struct NewSessionResponse { + pub sessionId: String, + pub value: json::Json +} + +impl NewSessionResponse { + pub fn new(session_id: String, value: json::Json) -> NewSessionResponse { + NewSessionResponse { + value: value, + sessionId: session_id + } + } +} + +#[derive(RustcEncodable, Show)] +pub struct ValueResponse { + value: json::Json +} + +impl ValueResponse { + pub fn new(value: json::Json) -> ValueResponse { + ValueResponse { + value: value + } + } +} + +#[derive(RustcEncodable, Show)] +pub struct WindowSizeResponse { + width: u64, + height: u64 +} + +impl WindowSizeResponse { + pub fn new(width: u64, height: u64) -> WindowSizeResponse { + WindowSizeResponse { + width: width, + height: height + } + } +} + +#[derive(RustcEncodable, Show)] +pub struct ElementRectResponse { + x: u64, + y: u64, + width: u64, + height: u64 +} + +impl ElementRectResponse { + pub fn new(x: u64, y: u64, width: u64, height: u64) -> ElementRectResponse { + ElementRectResponse { + x: x, + y: y, + width: width, + height: height + } + } +} + +#[derive(RustcEncodable, PartialEq, Show)] +pub struct Date(u64); + +impl Date { + pub fn new(timestamp: u64) -> Date { + Date(timestamp) + } +} + +impl ToJson for Date { + fn to_json(&self) -> json::Json { + let &Date(x) = self; + x.to_json() + } +} + +//TODO: some of these fields are probably supposed to be optional +#[derive(RustcEncodable, PartialEq, Show)] +pub struct Cookie { + name: String, + value: String, + path: Nullable, + domain: Nullable, + expiry: Nullable, + maxAge: Date, + secure: bool, + httpOnly: bool +} + +impl Cookie { + pub fn new(name: String, value: String, path: Nullable, domain: Nullable, + expiry: Nullable, max_age: Date, secure: bool, http_only: bool) -> Cookie { + Cookie { + name: name, + value: value, + path: path, + domain: domain, + expiry: expiry, + maxAge: max_age, + secure: secure, + httpOnly: http_only + } + } +} + +#[derive(RustcEncodable, Show)] +pub struct CookieResponse { + value: Vec +} + +impl CookieResponse { + pub fn new(value: Vec) -> CookieResponse { + CookieResponse { + value: value + } + } +}