From ba54211b54296725b1a689db66bf552dbaa4c4a6 Mon Sep 17 00:00:00 2001 From: James Graham Date: Tue, 6 Jan 2015 11:14:55 +0000 Subject: [PATCH] geckodriver: Broken implementation of most of what's in the spec (and some things that aren't) except Actions Source-Repo: https://github.com/mozilla/geckodriver Source-Revision: 14215bccc7dce187ee96ab111bc545deda2c7e8b --HG-- extra : rebase_source : 452b949d8be2903c6d5522b9cf91230c0a4b8e82 --- testing/geckodriver/src/command.rs | 425 ++++++++++++++++------ testing/geckodriver/src/common.rs | 149 +++++++- testing/geckodriver/src/marionette.rs | 208 +++++++---- testing/geckodriver/src/messagebuilder.rs | 24 +- testing/geckodriver/src/response.rs | 63 ++++ 5 files changed, 697 insertions(+), 172 deletions(-) diff --git a/testing/geckodriver/src/command.rs b/testing/geckodriver/src/command.rs index dfeb49d321a7..22fbe977d0da 100644 --- a/testing/geckodriver/src/command.rs +++ b/testing/geckodriver/src/command.rs @@ -1,16 +1,15 @@ -use core::u16; use std::collections::TreeMap; use serialize::json; use serialize::json::{ToJson, Json}; use regex::Captures; -use common::{WebDriverResult, WebDriverError, ErrorStatus}; +use common::{WebDriverResult, WebDriverError, ErrorStatus, Nullable, WebElement, FrameId, LocatorStrategy}; +use response::Date; //TODO: Put all these types in a specific file use messagebuilder::MatchType; #[deriving(PartialEq)] pub enum WebDriverCommand { - GetMarionetteId, //TODO: move this NewSession, DeleteSession, Get(GetParameters), @@ -22,7 +21,6 @@ pub enum WebDriverCommand { GetWindowHandle, GetWindowHandles, Close, - Timeouts(TimeoutsParameters), SetWindowSize(WindowSizeParameters), GetWindowSize, MaximizeWindow, @@ -30,6 +28,8 @@ pub enum WebDriverCommand { SwitchToWindow(SwitchToWindowParameters), SwitchToFrame(SwitchToFrameParameters), SwitchToParentFrame, + FindElement(LocatorParameters), + FindElements(LocatorParameters), IsDisplayed(WebElement), IsSelected(WebElement), GetElementAttribute(WebElement, String), @@ -39,7 +39,16 @@ pub enum WebDriverCommand { GetElementRect(WebElement), IsEnabled(WebElement), ExecuteScript(JavascriptCommandParameters), - ExecuteAsyncScript(JavascriptCommandParameters) + ExecuteAsyncScript(JavascriptCommandParameters), + GetCookie(GetCookieParameters), + AddCookie(AddCookieParameters), + SetTimeouts(TimeoutsParameters), + //Actions(ActionsParameters) + DismissAlert, + AcceptAlert, + GetAlertText, + SendAlertText(SendAlertTextParameters), + TakeScreenshot(TakeScreenshotParameters) } #[deriving(PartialEq)] @@ -83,9 +92,9 @@ impl WebDriverMessage { MatchType::GetWindowHandle => WebDriverCommand::GetWindowHandle, MatchType::GetWindowHandles => WebDriverCommand::GetWindowHandles, MatchType::Close => WebDriverCommand::Close, - MatchType::Timeouts => { + MatchType::SetTimeouts => { let parameters: TimeoutsParameters = try!(Parameters::from_json(&body_data)); - WebDriverCommand::Timeouts(parameters) + WebDriverCommand::SetTimeouts(parameters) }, MatchType::SetWindowSize => { let parameters: WindowSizeParameters = try!(Parameters::from_json(&body_data)); @@ -102,6 +111,14 @@ impl WebDriverMessage { 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 = WebElement::new(params.name("elementId").to_string()); WebDriverCommand::IsDisplayed(element) @@ -130,7 +147,7 @@ impl WebDriverMessage { }, MatchType::GetElementRect => { let element = WebElement::new(params.name("elementId").to_string()); - WebDriverCommand::GetElementText(element) + WebDriverCommand::GetElementRect(element) }, MatchType::IsEnabled => { let element = WebElement::new(params.name("elementId").to_string()); @@ -139,10 +156,35 @@ impl WebDriverMessage { 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)) @@ -160,7 +202,7 @@ impl ToJson for WebDriverMessage { fn to_json(&self) -> json::Json { let mut data = TreeMap::new(); let parameters = match self.command { - WebDriverCommand::GetMarionetteId | WebDriverCommand::NewSession | + WebDriverCommand::NewSession | WebDriverCommand::DeleteSession | WebDriverCommand::GetCurrentUrl | WebDriverCommand::GoBack | WebDriverCommand::GoForward | WebDriverCommand::Refresh | WebDriverCommand::GetTitle | WebDriverCommand::GetWindowHandle | @@ -170,16 +212,23 @@ impl ToJson for WebDriverMessage { WebDriverCommand::IsSelected(_) | WebDriverCommand::GetElementAttribute(_, _) | WebDriverCommand::GetCSSValue(_, _) | WebDriverCommand::GetElementText(_) | WebDriverCommand::GetElementTagName(_) | WebDriverCommand::GetElementRect(_) | - WebDriverCommand::IsEnabled(_) => { + WebDriverCommand::IsEnabled(_) | WebDriverCommand::AddCookie(_) | + WebDriverCommand::DismissAlert | WebDriverCommand::AcceptAlert | + WebDriverCommand::GetAlertText => { None }, WebDriverCommand::Get(ref x) => Some(x.to_json()), - WebDriverCommand::Timeouts(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::ExecuteScript(ref x) | - WebDriverCommand::ExecuteAsyncScript(ref x) => Some(x.to_json()) + 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()); @@ -188,76 +237,6 @@ impl ToJson for WebDriverMessage { } } -#[deriving(PartialEq)] -struct WebElement { - id: String -} - -impl WebElement { - fn new(id: String) -> WebElement { - WebElement { - id: id - } - } -} - -impl ToJson for WebElement { - fn to_json(&self) -> json::Json { - let mut data = TreeMap::new(); - data.insert("element-6066-11e4-a52e-4f735466cecf".to_string(), self.id.to_json()); - json::Object(data) - } -} - -#[deriving(PartialEq)] -enum FrameId { - Short(u16), - Element(WebElement), - Null -} - -impl ToJson for FrameId { - fn to_json(&self) -> json::Json { - match *self { - FrameId::Short(x) => { - json::Json::U64(x as u64) - }, - FrameId::Element(ref x) => { - json::Json::String(x.id.clone()) - }, - FrameId::Null => { - json::Json::Null - } - } - } -} - -#[deriving(PartialEq, Clone)] -enum Nullable { - Value(T), - Null -} - -impl Nullable { - //This is not very pretty - fn from_json WebDriverResult>(value: &json::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::Json { - match *self { - Nullable::Value(ref x) => x.to_json(), - Nullable::Null => json::Json::Null - } - } -} - trait Parameters { fn from_json(body: &json::Json) -> WebDriverResult; } @@ -397,6 +376,45 @@ impl ToJson for SwitchToWindowParameters { } } +#[deriving(PartialEq)] +struct LocatorParameters { + using: LocatorStrategy, + value: String +} + +impl Parameters for LocatorParameters { + fn from_json(body: &json::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").into_string(); + + return Ok(LocatorParameters { + using: using, + value: value + }) + } +} + +impl ToJson for LocatorParameters { + fn to_json(&self) -> json::Json { + let mut data = TreeMap::new(); + data.insert("using".to_string(), self.using.to_json()); + data.insert("value".to_string(), self.value.to_json()); + json::Object(data) + } +} + #[deriving(PartialEq)] struct SwitchToFrameParameters { id: FrameId @@ -404,28 +422,13 @@ struct SwitchToFrameParameters { impl Parameters for SwitchToFrameParameters { fn from_json(body: &json::Json) -> WebDriverResult { - let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + let data = try_opt!(body.as_object(), + ErrorStatus::UnknownError, "Message body was not an object"); - let id_json = try_opt!(data.get("id"), - ErrorStatus::UnknownError, - "Missing 'id' parameter"); - let id = if id_json.is_u64() { - let value = id_json.as_u64().unwrap(); - if value <= u16::MAX as u64 { - FrameId::Short(value as u16) - } else { - return Err(WebDriverError::new(ErrorStatus::NoSuchFrame, - "frame id out of range")) - } - } else if id_json.is_null() { - FrameId::Null - } else if id_json.is_string() { - let value = id_json.as_string().unwrap(); - FrameId::Element(WebElement::new(value.to_string())) - } else { - return Err(WebDriverError::new(ErrorStatus::NoSuchFrame, - "frame id has unexpected type")) - }; + let id = try!(FrameId::from_json(try_opt!(data.get("id"), + ErrorStatus::UnknownError, + "Missing 'id' parameter"))); + Ok(SwitchToFrameParameters { id: id }) @@ -490,3 +493,217 @@ impl ToJson for JavascriptCommandParameters { json::Object(data) } } + +#[deriving(PartialEq)] +struct GetCookieParameters { + name: Nullable +} + +impl Parameters for GetCookieParameters { + fn from_json(body: &json::Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "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::UnknownError, + "Failed to convert name to String").into_string()) + })); + return Ok(GetCookieParameters { + name: name + }) + } +} + +impl ToJson for GetCookieParameters { + fn to_json(&self) -> json::Json { + let mut data = TreeMap::new(); + data.insert("name".to_string(), self.name.to_json()); + json::Object(data) + } +} + +#[deriving(PartialEq)] +struct AddCookieParameters { + name: String, + value: String, + path: Nullable, + domain: Nullable, + expiry: Nullable, + maxAge: Date, + secure: bool, + httpOnly: bool +} + +impl Parameters for AddCookieParameters { + fn from_json(body: &json::Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), + ErrorStatus::UnknownError, + "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").into_string(); + + let value = try_opt!( + try_opt!(data.get("value"), + ErrorStatus::InvalidArgument, + "Missing 'value' parameter").as_string(), + ErrorStatus::InvalidArgument, + "'value' is not a string").into_string(); + + let path = match data.get("path") { + Some(path_json) => { + try!(Nullable::from_json( + path_json, + |x| { + Ok(try_opt!(x.as_string(), + ErrorStatus::UnknownError, + "Failed to convert path to String").into_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::UnknownError, + "Failed to convert domain to String").into_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::UnknownError, + "Failed to convert expiry to Date"))) + })) + }, + None => Nullable::Null + }; + + let max_age = Date::new(try_opt!( + try_opt!(data.get("maxAge"), + ErrorStatus::InvalidArgument, + "Missing 'maxAge' parameter").as_u64(), + ErrorStatus::InvalidArgument, + "'value' is not a string")); + + let secure = match data.get("secure") { + Some(x) => try_opt!(x.as_boolean(), + ErrorStatus::UnknownError, + "Failed to convert secure to boolean"), + None => false + }; + + let http_only = match data.get("httpOnly") { + Some(x) => try_opt!(x.as_boolean(), + ErrorStatus::UnknownError, + "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::Json { + let mut data = TreeMap::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) + } +} + +#[deriving(PartialEq)] +struct SendAlertTextParameters { + keysToSend: String +} + +impl Parameters for SendAlertTextParameters { + fn from_json(body: &json::Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "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").into_string(); + return Ok(SendAlertTextParameters { + keysToSend: keys + }) + } +} + +impl ToJson for SendAlertTextParameters { + fn to_json(&self) -> json::Json { + let mut data = TreeMap::new(); + data.insert("keysToSend".to_string(), self.keysToSend.to_json()); + json::Object(data) + } +} + +#[deriving(PartialEq)] +struct TakeScreenshotParameters { + element: Nullable +} + +impl Parameters for TakeScreenshotParameters { + fn from_json(body: &json::Json) -> WebDriverResult { + let data = try_opt!(body.as_object(), ErrorStatus::UnknownError, + "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::Json { + let mut data = TreeMap::new(); + data.insert("element".to_string(), self.element.to_json()); + json::Object(data) + } +} diff --git a/testing/geckodriver/src/common.rs b/testing/geckodriver/src/common.rs index 27643420b722..3eb714d6100e 100644 --- a/testing/geckodriver/src/common.rs +++ b/testing/geckodriver/src/common.rs @@ -1,4 +1,5 @@ -use serialize::json; +use core::u16; +use serialize::{json, Encodable, Encoder}; use serialize::json::{ToJson, ParserError}; use std::collections::TreeMap; use std::error::{Error, FromError}; @@ -118,3 +119,149 @@ impl FromError for WebDriverError { WebDriverError::new(ErrorStatus::UnknownError, msg.as_slice()) } } + +#[deriving(PartialEq, Clone, Show)] +pub enum Nullable { + Value(T), + Null +} + +impl Nullable { + //This is not very pretty + pub fn from_json WebDriverResult>(value: &json::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::Json { + match *self { + Nullable::Value(ref x) => x.to_json(), + Nullable::Null => json::Json::Null + } + } +} + +impl, E, T: ToJson> Encodable for Nullable { + fn encode(&self, s: &mut S) -> Result<(), E> { + match *self { + Nullable::Value(ref x) => x.to_json().encode(s), + Nullable::Null => s.emit_nil() + } + } +} + +#[deriving(PartialEq)] +pub struct WebElement { + id: String +} + +impl WebElement { + pub fn new(id: String) -> WebElement { + WebElement { + id: id + } + } + + pub fn from_json(data: &json::Json) -> WebDriverResult { + Ok(WebElement::new( + try_opt!( + try_opt!( + try_opt!(data.as_object(), + ErrorStatus::InvalidArgument, + "Could not convert webelement to object").get( + "element-6066-11e4-a52e-4f735466cecf"), + ErrorStatus::InvalidArgument, + "Could not find webelement key").as_string(), + ErrorStatus::InvalidArgument, + "Could not convert web element to string").into_string())) + } +} + +impl ToJson for WebElement { + fn to_json(&self) -> json::Json { + let mut data = TreeMap::new(); + data.insert("element-6066-11e4-a52e-4f735466cecf".to_string(), self.id.to_json()); + json::Object(data) + } +} + +#[deriving(PartialEq)] +pub enum FrameId { + Short(u16), + Element(WebElement), + Null +} + +impl FrameId { + pub fn from_json(data: &json::Json) -> WebDriverResult { + match data { + &json::Json::U64(x) => { + if x <= u16::MAX as u64 { + Ok(FrameId::Short(x as u16)) + } else { + Err(WebDriverError::new(ErrorStatus::NoSuchFrame, + "frame id out of range")) + } + }, + &json::Json::Null => Ok(FrameId::Null), + &json::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::Json { + match *self { + FrameId::Short(x) => { + json::Json::U64(x as u64) + }, + FrameId::Element(ref x) => { + json::Json::String(x.id.clone()) + }, + FrameId::Null => { + json::Json::Null + } + } + } +} + +#[deriving(PartialEq)] +pub enum LocatorStrategy { + CSSSelector, + LinkText, + PartialLinkText, + XPath +} + +impl LocatorStrategy { + pub fn from_json(body: &json::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 { + json::Json::String(match *self { + LocatorStrategy::CSSSelector => "css selector", + LocatorStrategy::LinkText => "link text", + LocatorStrategy::PartialLinkText => "partial link text", + LocatorStrategy::XPath => "xpath" + }.into_string()) + } +} diff --git a/testing/geckodriver/src/marionette.rs b/testing/geckodriver/src/marionette.rs index 484f8e718c57..a3c734455649 100644 --- a/testing/geckodriver/src/marionette.rs +++ b/testing/geckodriver/src/marionette.rs @@ -4,16 +4,19 @@ use std::collections::TreeMap; use std::io::{IoResult, TcpStream, IoError}; use command::{WebDriverMessage}; -use command::WebDriverCommand::{GetMarionetteId, NewSession, DeleteSession, Get, GetCurrentUrl, +use command::WebDriverCommand::{NewSession, DeleteSession, Get, GetCurrentUrl, GoBack, GoForward, Refresh, GetTitle, GetWindowHandle, - GetWindowHandles, Close, Timeouts, SetWindowSize, + GetWindowHandles, Close, SetWindowSize, GetWindowSize, MaximizeWindow, SwitchToWindow, SwitchToFrame, - SwitchToParentFrame, IsDisplayed, IsSelected, - GetElementAttribute, GetCSSValue, GetElementText, + SwitchToParentFrame, FindElement, FindElements, IsDisplayed, + IsSelected, GetElementAttribute, GetCSSValue, GetElementText, GetElementTagName, GetElementRect, IsEnabled, ExecuteScript, - ExecuteAsyncScript}; -use response::{WebDriverResponse, NewSessionResponse, ValueResponse, WindowSizeResponse, ElementRectResponse}; -use common::{WebDriverResult, WebDriverError, ErrorStatus}; + ExecuteAsyncScript, GetCookie, AddCookie, SetTimeouts, + DismissAlert, AcceptAlert, GetAlertText, SendAlertText, + TakeScreenshot}; +use response::{WebDriverResponse, NewSessionResponse, ValueResponse, WindowSizeResponse, + ElementRectResponse, CookieResponse, Date, Cookie}; +use common::{WebDriverResult, WebDriverError, ErrorStatus, Nullable}; pub struct MarionetteSession { pub session_id: String, @@ -37,16 +40,6 @@ impl MarionetteSession { pub fn update(&mut self, msg: &WebDriverMessage, resp: &TreeMap) -> WebDriverResult<()> { match msg.command { - GetMarionetteId => { - let to = try_opt!( - try_opt!(resp.get("to"), - ErrorStatus::UnknownError, - "Unable to get to value").as_string(), - ErrorStatus::UnknownError, - "Unable to convert 'to' to a string"); - - self.to = to.to_string(); - }, NewSession => { let session_id = try_opt!( try_opt!(resp.get("sessionId"), @@ -61,49 +54,63 @@ impl MarionetteSession { Ok(()) } - fn command_name(msg:&WebDriverMessage) -> String { + fn command_name(msg:&WebDriverMessage) -> Option { match msg.command { - GetMarionetteId => "getMarionetteID", - NewSession => "newSession", - DeleteSession => "deleteSession", - Get(_) => "get", - GetCurrentUrl => "getCurrentUrl", - GoBack => "goBack", - GoForward => "goForward", - Refresh => "refresh", - GetTitle => "getTitle", - GetWindowHandle => "getWindowHandle", - GetWindowHandles => "getWindowHandles", - Close => "close", - Timeouts(_) => "timeouts", - SetWindowSize(_) => "setWindowSize", - GetWindowSize => "getWindowSize", - MaximizeWindow => "maximizeWindow", - SwitchToWindow(_) => "switchToWindow", - SwitchToFrame(_) => "switchToFrame", - SwitchToParentFrame => "switchToParentFrame", - IsDisplayed(_) => "isElementDisplayed", - IsSelected(_) => "isElementSelected", - GetElementAttribute(_, _) => "getElementAttribute", - GetCSSValue(_, _) => "getElementValueOfCssProperty", - GetElementText(_) => "getElementText", - GetElementTagName(_) => "getElementTagName", - GetElementRect(_) => "getElementRect", - IsEnabled(_) => "isElementEnabled", - ExecuteScript(_) => "executeScript", - ExecuteAsyncScript(_) => "executeAsyncScript" - }.to_string() + NewSession => Some("newSession"), + DeleteSession => Some("deleteSession"), + Get(_) => Some("get"), + GetCurrentUrl => Some("getCurrentUrl"), + GoBack => Some("goBack"), + GoForward => Some("goForward"), + Refresh => Some("refresh"), + GetTitle => Some("getTitle"), + GetWindowHandle => Some("getWindowHandle"), + GetWindowHandles => Some("getWindowHandles"), + Close => Some("close"), + SetTimeouts(_) => Some("timeouts"), + SetWindowSize(_) => Some("setWindowSize"), + GetWindowSize => Some("getWindowSize"), + MaximizeWindow => Some("maximizeWindow"), + SwitchToWindow(_) => Some("switchToWindow"), + SwitchToFrame(_) => Some("switchToFrame"), + SwitchToParentFrame => Some("switchToParentFrame"), + FindElement(_) => Some("findElement"), + FindElements(_) => Some("findElements"), + IsDisplayed(_) => Some("isElementDisplayed"), + IsSelected(_) => Some("isElementSelected"), + GetElementAttribute(_, _) => Some("getElementAttribute"), + GetCSSValue(_, _) => Some("getElementValueOfCssProperty"), + GetElementText(_) => Some("getElementText"), + GetElementTagName(_) => Some("getElementTagName"), + GetElementRect(_) => Some("getElementRect"), + IsEnabled(_) => Some("isElementEnabled"), + ExecuteScript(_) => Some("executeScript"), + ExecuteAsyncScript(_) => Some("executeAsyncScript"), + GetCookie(_) => Some("getCookies"), + AddCookie(_) => Some("addCookie"), + DismissAlert => None, //Unsupported + AcceptAlert => None, //Unsupported + GetAlertText => None, //Unsupported + SendAlertText(_) => None, //Unsupported + TakeScreenshot(_) => Some("takeScreenshot") + }.map(|x| x.into_string()) } - pub fn msg_to_marionette(&self, msg: &WebDriverMessage) -> json::Json { - let mut data = msg.to_json().as_object().unwrap().clone(); + pub fn msg_to_marionette(&self, msg: &WebDriverMessage) -> WebDriverResult { + let command_name = try_opt!(MarionetteSession::command_name(msg), + ErrorStatus::UnsupportedOperation, + "Operation not supported"); + let mut data = try_opt!(msg.to_json().as_object(), + ErrorStatus::UnknownError, + "Failed to convert message to Object" + ).clone(); match msg.session_id { Some(ref x) => data.insert("sessionId".to_string(), x.to_json()), None => None }; data.insert("to".to_string(), self.to.to_json()); - data.insert("name".to_string(), MarionetteSession::command_name(msg).to_json()); - json::Object(data) + data.insert("name".to_string(), command_name.to_json()); + Ok(json::Object(data)) } pub fn response_from_json(&mut self, message: &WebDriverMessage, @@ -129,23 +136,26 @@ impl MarionetteSession { return Err(WebDriverError::new(status, err_msg)); } - self.update(message, &json_data); + try!(self.update(message, &json_data)); match message.command { //Everything that doesn't have a response value - GetMarionetteId => Ok(None), - Get(_) | GoBack | GoForward | Refresh | Close | Timeouts(_) | + Get(_) | GoBack | GoForward | Refresh | Close | SetTimeouts(_) | SetWindowSize(_) | MaximizeWindow | SwitchToWindow(_) | SwitchToFrame(_) | - SwitchToParentFrame => { + SwitchToParentFrame | AddCookie(_) | DismissAlert | AcceptAlert | + SendAlertText(_) => { Ok(Some(WebDriverResponse::Void)) }, //Things that simply return the contents of the marionette "value" property - GetCurrentUrl | GetTitle | GetWindowHandle | GetWindowHandles | IsDisplayed(_) | - IsSelected(_) | GetElementAttribute(_, _) | GetCSSValue(_, _) | GetElementText(_) | - GetElementTagName(_) | IsEnabled(_) | ExecuteScript(_) | ExecuteAsyncScript(_) => { + GetCurrentUrl | GetTitle | GetWindowHandle | GetWindowHandles | + FindElement(_) | FindElements(_) | IsDisplayed(_) | IsSelected(_) | + GetElementAttribute(_, _) | GetCSSValue(_, _) | GetElementText(_) | + GetElementTagName(_) | IsEnabled(_) | ExecuteScript(_) | ExecuteAsyncScript(_) | + GetAlertText | TakeScreenshot(_) => { let value = try_opt!(json_data.get("value"), ErrorStatus::UnknownError, "Failed to find value field"); + //TODO: Convert webelement keys Ok(Some(WebDriverResponse::Generic(ValueResponse::new(value.clone())))) }, GetWindowSize => { @@ -209,14 +219,84 @@ impl MarionetteSession { "Failed to interpret width as integer"); Ok(Some(WebDriverResponse::ElementRect(ElementRectResponse::new(x, y, width, height)))) - } + }, + GetCookie(_) => { + let value = try_opt!( + try_opt!(json_data.get("value"), + ErrorStatus::UnknownError, + "Failed to find value field").as_array(), + ErrorStatus::UnknownError, + "Failed to interpret value as array"); + let cookies = try!(value.iter().map(|x| { + let name = try_opt!( + try_opt!(x.find("name"), + ErrorStatus::UnknownError, + "Failed to find name field").as_string(), + ErrorStatus::UnknownError, + "Failed to interpret name as string").into_string(); + let value = try_opt!( + try_opt!(x.find("value"), + ErrorStatus::UnknownError, + "Failed to find value field").as_string(), + ErrorStatus::UnknownError, + "Failed to interpret value as string").into_string(); + let path = try!( + Nullable::from_json(try_opt!(x.find("path"), + ErrorStatus::UnknownError, + "Failed to find path field"), + |x| { + Ok((try_opt!(x.as_string(), + ErrorStatus::UnknownError, + "Failed to interpret path as String")).into_string()) + })); + let domain = try!( + Nullable::from_json(try_opt!(x.find("domain"), + ErrorStatus::UnknownError, + "Failed to find domain field"), + |x| { + Ok((try_opt!(x.as_string(), + ErrorStatus::UnknownError, + "Failed to interpret domain as String")).into_string()) + })); + let expiry = try!( + Nullable::from_json(try_opt!(x.find("expiry"), + ErrorStatus::UnknownError, + "Failed to find expiry field"), + |x| { + Ok(Date::new((try_opt!( + x.as_u64(), + ErrorStatus::UnknownError, + "Failed to interpret domain as String")))) + })); + let max_age = Date::new(try_opt!( + try_opt!(x.find("maxAge"), + ErrorStatus::UnknownError, + "Failed to find maxAge field").as_u64(), + ErrorStatus::UnknownError, + "Failed to interpret maxAge as u64")); + let secure = match x.find("secure") { + Some(x) => try_opt!(x.as_boolean(), + ErrorStatus::UnknownError, + "Failed to interpret secure as boolean"), + None => false + }; + let http_only = match x.find("httpOnly") { + Some(x) => try_opt!(x.as_boolean(), + ErrorStatus::UnknownError, + "Failed to interpret http_only as boolean"), + None => false + }; + Ok(Cookie::new(name, value, path, domain, expiry, max_age, secure, http_only)) + }).collect::, _>>()); + Ok(Some(WebDriverResponse::Cookie(CookieResponse::new(cookies)))) + }, NewSession => { let session_id = try_opt!( try_opt!(json_data.get("sessionId"), ErrorStatus::InvalidSessionId, "Failed to find sessionId field").as_string(), ErrorStatus::InvalidSessionId, - "sessionId was not a string"); + "sessionId was not a string").into_string(); let value = try_opt!( try_opt!(json_data.get("value"), @@ -226,7 +306,7 @@ impl MarionetteSession { "value field was not an Object"); Ok(Some(WebDriverResponse::NewSession(NewSessionResponse::new( - session_id.to_string(), json::Object(value.clone()))))) + session_id, json::Object(value.clone()))))) } DeleteSession => { Ok(Some(WebDriverResponse::DeleteSession)) @@ -310,9 +390,7 @@ impl MarionetteConnection { } pub fn send_message(&mut self, msg: &WebDriverMessage) -> WebDriverResult> { - let resp = { - self.session.msg_to_marionette(msg) - }; + let resp = try!(self.session.msg_to_marionette(msg)); let resp = match self.send(&resp) { Ok(resp_data) => self.session.response_from_json(msg, resp_data[]), Err(x) => Err(x) diff --git a/testing/geckodriver/src/messagebuilder.rs b/testing/geckodriver/src/messagebuilder.rs index 51d3a87fd6d3..50434591337c 100644 --- a/testing/geckodriver/src/messagebuilder.rs +++ b/testing/geckodriver/src/messagebuilder.rs @@ -18,13 +18,14 @@ pub enum MatchType { GetWindowHandle, GetWindowHandles, Close, - Timeouts, SetWindowSize, GetWindowSize, MaximizeWindow, SwitchToWindow, SwitchToFrame, SwitchToParentFrame, + FindElement, + FindElements, IsDisplayed, IsSelected, GetElementAttribute, @@ -35,6 +36,15 @@ pub enum MatchType { IsEnabled, ExecuteScript, ExecuteAsyncScript, + GetCookie, + AddCookie, + SetTimeouts, + //Actions XXX - once I understand the spec, perhaps + DismissAlert, + AcceptAlert, + GetAlertText, + SendAlertText, + TakeScreenshot } #[deriving(Clone)] @@ -131,13 +141,14 @@ pub fn get_builder() -> MessageBuilder { (Get, "/session/{sessionId}/window_handle", MatchType::GetWindowHandle), (Get, "/session/{sessionId}/window_handles", MatchType::GetWindowHandles), (Delete, "/session/{sessionId}/window_handle", MatchType::Close), - (Post, "/session/{sessionId}/timeouts", MatchType::Timeouts), (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/{element}/isDisplayed", MatchType::IsDisplayed), (Get, "/session/{sessionId}/element/{element}/isSelected", MatchType::IsSelected), (Get, "/session/{sessionId}/element/{element}/attribute/{name}", MatchType::GetElementAttribute), @@ -148,6 +159,15 @@ pub fn get_builder() -> MessageBuilder { (Get, "/session/{sessionId}/element/{element}/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}/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() { diff --git a/testing/geckodriver/src/response.rs b/testing/geckodriver/src/response.rs index 7a29954f6e38..5043409a70da 100644 --- a/testing/geckodriver/src/response.rs +++ b/testing/geckodriver/src/response.rs @@ -1,4 +1,7 @@ use serialize::json; +use serialize::json::ToJson; + +use common::Nullable; #[deriving(Show)] pub enum WebDriverResponse { @@ -6,6 +9,7 @@ pub enum WebDriverResponse { DeleteSession, WindowSize(WindowSizeResponse), ElementRect(ElementRectResponse), + Cookie(CookieResponse), Generic(ValueResponse), Void } @@ -17,6 +21,7 @@ impl WebDriverResponse { WebDriverResponse::DeleteSession => "".into_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 => "".into_string() } @@ -84,3 +89,61 @@ impl ElementRectResponse { } } } + +#[deriving(Encodable, 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 +#[deriving(Encodable, 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 + } + } +} + +#[deriving(Encodable, Show)] +pub struct CookieResponse { + value: Vec +} + +impl CookieResponse { + pub fn new(value: Vec) -> CookieResponse { + CookieResponse { + value: value + } + } +}