Bug 1442028 - Move webdriver actions into its own module. r=ato
authorHenrik Skupin <mail@hskupin.info>
Wed, 28 Feb 2018 22:31:47 +0100
changeset 406136 413a684727a20b4c4a0e0aa06244faa14e1be401
parent 406135 18dd09f4f5ba3a2d1f0f11ae8fa192b5805b728c
child 406137 406c8d7d134fd094469ed7910a28696a8a5330af
push id33545
push useraciure@mozilla.com
push dateFri, 02 Mar 2018 10:14:13 +0000
treeherdermozilla-central@b5159a80934f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1442028
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1442028 - Move webdriver actions into its own module. r=ato MozReview-Commit-ID: Bf0Ut8AGwtl
testing/webdriver/src/actions.rs
testing/webdriver/src/command.rs
testing/webdriver/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/testing/webdriver/src/actions.rs
@@ -0,0 +1,659 @@
+use command::Parameters;
+use common::{Nullable, WebElement};
+use error::{WebDriverResult, WebDriverError, ErrorStatus};
+use rustc_serialize::json::{ToJson, Json};
+use std::collections::BTreeMap;
+use std::default::Default;
+
+#[derive(Debug, PartialEq)]
+pub struct ActionSequence {
+    pub id: Nullable<String>,
+    pub actions: ActionsType
+}
+
+impl Parameters for ActionSequence {
+    fn from_json(body: &Json) -> WebDriverResult<ActionSequence> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Actions chain was not an object");
+
+        let type_name = try_opt!(try_opt!(data.get("type"),
+                                          ErrorStatus::InvalidArgument,
+                                          "Missing type parameter").as_string(),
+                                 ErrorStatus::InvalidArgument,
+                                 "Parameter ;type' was not a string");
+
+        let id = match data.get("id") {
+            Some(x) => Some(try_opt!(x.as_string(),
+                                     ErrorStatus::InvalidArgument,
+                                     "Parameter 'id' was not a string").to_owned()),
+            None => None
+        };
+
+
+        // Note that unlike the spec we get the pointer parameters in ActionsType::from_json
+
+        let actions = match type_name {
+            "none" | "key" | "pointer" => try!(ActionsType::from_json(&body)),
+            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                "Invalid action type"))
+        };
+
+        Ok(ActionSequence {
+            id: id.into(),
+            actions: actions
+        })
+    }
+}
+
+impl ToJson for ActionSequence {
+    fn to_json(&self) -> Json {
+        let mut data: BTreeMap<String, Json> = BTreeMap::new();
+        data.insert("id".into(), self.id.to_json());
+        let (action_type, actions) = match self.actions {
+            ActionsType::Null(ref actions) => {
+                ("none",
+                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
+            }
+            ActionsType::Key(ref actions) => {
+                ("key",
+                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
+            }
+            ActionsType::Pointer(ref parameters, ref actions) => {
+                data.insert("parameters".into(), parameters.to_json());
+                ("pointer",
+                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
+            }
+        };
+        data.insert("type".into(), action_type.to_json());
+        data.insert("actions".into(), actions.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ActionsType {
+    Null(Vec<NullActionItem>),
+    Key(Vec<KeyActionItem>),
+    Pointer(PointerActionParameters, Vec<PointerActionItem>)
+}
+
+impl Parameters for ActionsType {
+    fn from_json(body: &Json) -> WebDriverResult<ActionsType> {
+        // These unwraps are OK as long as this is only called from ActionSequence::from_json
+        let data = body.as_object().expect("Body should be a JSON Object");
+        let actions_type = body.find("type").and_then(|x| x.as_string()).expect("Type should be a string");
+        let actions_chain = try_opt!(try_opt!(data.get("actions"),
+                                              ErrorStatus::InvalidArgument,
+                                              "Missing actions parameter").as_array(),
+                                     ErrorStatus::InvalidArgument,
+                                     "Parameter 'actions' was not an array");
+        match actions_type {
+            "none" => {
+                let mut actions = Vec::with_capacity(actions_chain.len());
+                for action_body in actions_chain.iter() {
+                    actions.push(try!(NullActionItem::from_json(action_body)));
+                };
+                Ok(ActionsType::Null(actions))
+            },
+            "key" => {
+                let mut actions = Vec::with_capacity(actions_chain.len());
+                for action_body in actions_chain.iter() {
+                    actions.push(try!(KeyActionItem::from_json(action_body)));
+                };
+                Ok(ActionsType::Key(actions))
+            },
+            "pointer" => {
+                let mut actions = Vec::with_capacity(actions_chain.len());
+                let parameters = match data.get("parameters") {
+                    Some(x) => try!(PointerActionParameters::from_json(x)),
+                    None => Default::default()
+                };
+
+                for action_body in actions_chain.iter() {
+                    actions.push(try!(PointerActionItem::from_json(action_body)));
+                }
+                Ok(ActionsType::Pointer(parameters, actions))
+            }
+            _ => panic!("Got unexpected action type after checking type")
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum PointerType {
+    Mouse,
+    Pen,
+    Touch,
+}
+
+impl Parameters for PointerType {
+    fn from_json(body: &Json) -> WebDriverResult<PointerType> {
+        match body.as_string() {
+            Some("mouse") => Ok(PointerType::Mouse),
+            Some("pen") => Ok(PointerType::Pen),
+            Some("touch") => Ok(PointerType::Touch),
+            Some(_) => Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Unsupported pointer type"
+            )),
+            None => Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Pointer type was not a string"
+            ))
+        }
+    }
+}
+
+impl ToJson for PointerType {
+    fn to_json(&self) -> Json {
+        match *self {
+            PointerType::Mouse => "mouse".to_json(),
+            PointerType::Pen => "pen".to_json(),
+            PointerType::Touch => "touch".to_json(),
+        }.to_json()
+    }
+}
+
+impl Default for PointerType {
+    fn default() -> PointerType {
+        PointerType::Mouse
+    }
+}
+
+#[derive(Debug, Default, PartialEq)]
+pub struct PointerActionParameters {
+    pub pointer_type: PointerType
+}
+
+impl Parameters for PointerActionParameters {
+    fn from_json(body: &Json) -> WebDriverResult<PointerActionParameters> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Parameter 'parameters' was not an object");
+        let pointer_type = match data.get("pointerType") {
+            Some(x) => try!(PointerType::from_json(x)),
+            None => PointerType::default()
+        };
+        Ok(PointerActionParameters {
+            pointer_type: pointer_type
+        })
+    }
+}
+
+impl ToJson for PointerActionParameters {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("pointerType".to_owned(),
+                    self.pointer_type.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum NullActionItem {
+    General(GeneralAction)
+}
+
+impl Parameters for NullActionItem {
+    fn from_json(body: &Json) -> WebDriverResult<NullActionItem> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Actions chain was not an object");
+        let type_name = try_opt!(
+            try_opt!(data.get("type"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'type' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'type' was not a string");
+        match type_name {
+            "pause" => Ok(NullActionItem::General(
+                try!(GeneralAction::from_json(body)))),
+            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                "Invalid type attribute"))
+        }
+    }
+}
+
+impl ToJson for NullActionItem {
+    fn to_json(&self) -> Json {
+        match self {
+            &NullActionItem::General(ref x) => x.to_json(),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum KeyActionItem {
+    General(GeneralAction),
+    Key(KeyAction)
+}
+
+impl Parameters for KeyActionItem {
+    fn from_json(body: &Json) -> WebDriverResult<KeyActionItem> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Key action item was not an object");
+        let type_name = try_opt!(
+            try_opt!(data.get("type"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'type' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'type' was not a string");
+        match type_name {
+            "pause" => Ok(KeyActionItem::General(
+                try!(GeneralAction::from_json(body)))),
+            _ => Ok(KeyActionItem::Key(
+                try!(KeyAction::from_json(body))))
+        }
+    }
+}
+
+impl ToJson for KeyActionItem {
+    fn to_json(&self) -> Json {
+        match *self {
+            KeyActionItem::General(ref x) => x.to_json(),
+            KeyActionItem::Key(ref x) => x.to_json()
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum PointerActionItem {
+    General(GeneralAction),
+    Pointer(PointerAction)
+}
+
+impl Parameters for PointerActionItem {
+    fn from_json(body: &Json) -> WebDriverResult<PointerActionItem> {
+        let data = try_opt!(body.as_object(),
+                            ErrorStatus::InvalidArgument,
+                            "Pointer action item was not an object");
+        let type_name = try_opt!(
+            try_opt!(data.get("type"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing 'type' parameter").as_string(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'type' was not a string");
+
+        match type_name {
+            "pause" => Ok(PointerActionItem::General(try!(GeneralAction::from_json(body)))),
+            _ => Ok(PointerActionItem::Pointer(try!(PointerAction::from_json(body))))
+        }
+    }
+}
+
+impl ToJson for PointerActionItem {
+    fn to_json(&self) -> Json {
+        match self {
+            &PointerActionItem::General(ref x) => x.to_json(),
+            &PointerActionItem::Pointer(ref x) => x.to_json()
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum GeneralAction {
+    Pause(PauseAction)
+}
+
+impl Parameters for GeneralAction {
+    fn from_json(body: &Json) -> WebDriverResult<GeneralAction> {
+        match body.find("type").and_then(|x| x.as_string()) {
+            Some("pause") => Ok(GeneralAction::Pause(try!(PauseAction::from_json(body)))),
+            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                         "Invalid or missing type attribute"))
+        }
+    }
+}
+
+impl ToJson for GeneralAction {
+    fn to_json(&self) -> Json {
+        match self {
+            &GeneralAction::Pause(ref x) => x.to_json()
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PauseAction {
+    pub duration: u64
+}
+
+impl Parameters for PauseAction {
+    fn from_json(body: &Json) -> WebDriverResult<PauseAction> {
+        let default = Json::U64(0);
+        Ok(PauseAction {
+            duration: try_opt!(body.find("duration").unwrap_or(&default).as_u64(),
+                               ErrorStatus::InvalidArgument,
+                               "Parameter 'duration' was not a positive integer")
+        })
+    }
+}
+
+impl ToJson for PauseAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "pause".to_json());
+        data.insert("duration".to_owned(),
+                    self.duration.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum KeyAction {
+    Up(KeyUpAction),
+    Down(KeyDownAction)
+}
+
+impl Parameters for KeyAction {
+    fn from_json(body: &Json) -> WebDriverResult<KeyAction> {
+        match body.find("type").and_then(|x| x.as_string()) {
+            Some("keyDown") => Ok(KeyAction::Down(try!(KeyDownAction::from_json(body)))),
+            Some("keyUp") => Ok(KeyAction::Up(try!(KeyUpAction::from_json(body)))),
+            Some(_) | None => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                      "Invalid type attribute value for key action"))
+        }
+    }
+}
+
+impl ToJson for KeyAction {
+    fn to_json(&self) -> Json {
+        match self {
+            &KeyAction::Down(ref x) => x.to_json(),
+            &KeyAction::Up(ref x) => x.to_json(),
+        }
+    }
+}
+
+fn validate_key_value(value_str: &str) -> WebDriverResult<char> {
+    let mut chars = value_str.chars();
+    let value = if let Some(c) = chars.next() {
+        c
+    } else {
+        return Err(WebDriverError::new(
+            ErrorStatus::InvalidArgument,
+            "Parameter 'value' was an empty string"))
+    };
+    if chars.next().is_some() {
+        return Err(WebDriverError::new(
+            ErrorStatus::InvalidArgument,
+            "Parameter 'value' contained multiple characters"))
+    };
+    Ok(value)
+}
+
+#[derive(Debug, PartialEq)]
+pub struct KeyUpAction {
+    pub value: char
+}
+
+impl Parameters for KeyUpAction {
+    fn from_json(body: &Json) -> WebDriverResult<KeyUpAction> {
+        let value_str = try_opt!(
+                try_opt!(body.find("value"),
+                         ErrorStatus::InvalidArgument,
+                         "Missing value parameter").as_string(),
+                ErrorStatus::InvalidArgument,
+            "Parameter 'value' was not a string");
+
+        let value = try!(validate_key_value(value_str));
+        Ok(KeyUpAction {
+            value: value
+        })
+    }
+}
+
+impl ToJson for KeyUpAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "keyUp".to_json());
+        data.insert("value".to_string(),
+                    self.value.to_string().to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct KeyDownAction {
+    pub value: char
+}
+
+impl Parameters for KeyDownAction {
+    fn from_json(body: &Json) -> WebDriverResult<KeyDownAction> {
+        let value_str = try_opt!(
+                try_opt!(body.find("value"),
+                         ErrorStatus::InvalidArgument,
+                         "Missing value parameter").as_string(),
+                ErrorStatus::InvalidArgument,
+            "Parameter 'value' was not a string");
+        let value = try!(validate_key_value(value_str));
+        Ok(KeyDownAction {
+            value: value
+        })
+    }
+}
+
+impl ToJson for KeyDownAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "keyDown".to_json());
+        data.insert("value".to_owned(),
+                    self.value.to_string().to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum PointerOrigin {
+    Viewport,
+    Pointer,
+    Element(WebElement),
+}
+
+impl Parameters for PointerOrigin {
+    fn from_json(body: &Json) -> WebDriverResult<PointerOrigin> {
+        match *body {
+            Json::String(ref x) => {
+                match &**x {
+                    "viewport" => Ok(PointerOrigin::Viewport),
+                    "pointer" => Ok(PointerOrigin::Pointer),
+                    _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                                                 "Unknown pointer origin"))
+                }
+            },
+            Json::Object(_) => Ok(PointerOrigin::Element(try!(WebElement::from_json(body)))),
+            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
+                        "Pointer origin was not a string or an object"))
+        }
+    }
+}
+
+impl ToJson for PointerOrigin {
+    fn to_json(&self) -> Json {
+        match *self {
+            PointerOrigin::Viewport => "viewport".to_json(),
+            PointerOrigin::Pointer => "pointer".to_json(),
+            PointerOrigin::Element(ref x) => x.to_json(),
+        }
+    }
+}
+
+impl Default for PointerOrigin {
+    fn default() -> PointerOrigin {
+        PointerOrigin::Viewport
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum PointerAction {
+    Up(PointerUpAction),
+    Down(PointerDownAction),
+    Move(PointerMoveAction),
+    Cancel
+}
+
+impl Parameters for PointerAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerAction> {
+        match body.find("type").and_then(|x| x.as_string()) {
+            Some("pointerUp") => Ok(PointerAction::Up(try!(PointerUpAction::from_json(body)))),
+            Some("pointerDown") => Ok(PointerAction::Down(try!(PointerDownAction::from_json(body)))),
+            Some("pointerMove") => Ok(PointerAction::Move(try!(PointerMoveAction::from_json(body)))),
+            Some("pointerCancel") => Ok(PointerAction::Cancel),
+            Some(_) | None => Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Missing or invalid type argument for pointer action"))
+        }
+    }
+}
+
+impl ToJson for PointerAction {
+    fn to_json(&self) -> Json {
+        match self {
+            &PointerAction::Down(ref x) => x.to_json(),
+            &PointerAction::Up(ref x) => x.to_json(),
+            &PointerAction::Move(ref x) => x.to_json(),
+            &PointerAction::Cancel => {
+                let mut data = BTreeMap::new();
+                data.insert("type".to_owned(),
+                            "pointerCancel".to_json());
+                Json::Object(data)
+            }
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PointerUpAction {
+    pub button: u64,
+}
+
+impl Parameters for PointerUpAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerUpAction> {
+        let button = try_opt!(
+            try_opt!(body.find("button"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing button parameter").as_u64(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'button' was not a positive integer");
+
+        Ok(PointerUpAction {
+            button: button
+        })
+    }
+}
+
+impl ToJson for PointerUpAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "pointerUp".to_json());
+        data.insert("button".to_owned(), self.button.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PointerDownAction {
+    pub button: u64,
+}
+
+impl Parameters for PointerDownAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerDownAction> {
+        let button = try_opt!(
+            try_opt!(body.find("button"),
+                     ErrorStatus::InvalidArgument,
+                     "Missing button parameter").as_u64(),
+            ErrorStatus::InvalidArgument,
+            "Parameter 'button' was not a positive integer");
+
+        Ok(PointerDownAction {
+            button: button
+        })
+    }
+}
+
+impl ToJson for PointerDownAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(),
+                    "pointerDown".to_json());
+        data.insert("button".to_owned(), self.button.to_json());
+        Json::Object(data)
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct PointerMoveAction {
+    pub duration: Nullable<u64>,
+    pub origin: PointerOrigin,
+    pub x: Nullable<i64>,
+    pub y: Nullable<i64>
+}
+
+impl Parameters for PointerMoveAction {
+    fn from_json(body: &Json) -> WebDriverResult<PointerMoveAction> {
+        let duration = match body.find("duration") {
+            Some(duration) => Some(try_opt!(duration.as_u64(),
+                                            ErrorStatus::InvalidArgument,
+                                            "Parameter 'duration' was not a positive integer")),
+            None => None
+
+        };
+
+        let origin = match body.find("origin") {
+            Some(o) => try!(PointerOrigin::from_json(o)),
+            None => PointerOrigin::default()
+        };
+
+        let x = match body.find("x") {
+            Some(x) => {
+                Some(try_opt!(x.as_i64(),
+                              ErrorStatus::InvalidArgument,
+                              "Parameter 'x' was not an integer"))
+            },
+            None => None
+        };
+
+        let y = match body.find("y") {
+            Some(y) => {
+                Some(try_opt!(y.as_i64(),
+                              ErrorStatus::InvalidArgument,
+                              "Parameter 'y' was not an integer"))
+            },
+            None => None
+        };
+
+        Ok(PointerMoveAction {
+            duration: duration.into(),
+            origin: origin.into(),
+            x: x.into(),
+            y: y.into(),
+        })
+    }
+}
+
+impl ToJson for PointerMoveAction {
+    fn to_json(&self) -> Json {
+        let mut data = BTreeMap::new();
+        data.insert("type".to_owned(), "pointerMove".to_json());
+        if self.duration.is_value() {
+            data.insert("duration".to_owned(),
+                        self.duration.to_json());
+        }
+
+        data.insert("origin".to_owned(), self.origin.to_json());
+
+        if self.x.is_value() {
+            data.insert("x".to_owned(), self.x.to_json());
+        }
+        if self.y.is_value() {
+            data.insert("y".to_owned(), self.y.to_json());
+        }
+        Json::Object(data)
+    }
+}
--- a/testing/webdriver/src/command.rs
+++ b/testing/webdriver/src/command.rs
@@ -1,18 +1,18 @@
+use actions::{ActionSequence};
 use capabilities::{SpecNewSessionParameters, LegacyNewSessionParameters,
                    CapabilitiesMatching, BrowserCapabilities, Capabilities};
 use common::{Date, Nullable, WebElement, FrameId, LocatorStrategy};
 use error::{WebDriverResult, WebDriverError, ErrorStatus};
 use httpapi::{Route, WebDriverExtensionRoute, VoidWebDriverExtensionRoute};
 use regex::Captures;
 use rustc_serialize::json;
 use rustc_serialize::json::{ToJson, Json};
 use std::collections::BTreeMap;
-use std::default::Default;
 
 #[derive(Debug, PartialEq)]
 pub enum WebDriverCommand<T: WebDriverExtensionCommand> {
     NewSession(NewSessionParameters),
     DeleteSession,
     Get(GetParameters),
     GetCurrentUrl,
     GoBack,
@@ -1077,669 +1077,16 @@ impl ToJson for ActionsParameters {
     fn to_json(&self) -> Json {
         let mut data = BTreeMap::new();
         data.insert("actions".to_owned(),
                     self.actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>().to_json());
         Json::Object(data)
     }
 }
 
-#[derive(Debug, PartialEq)]
-pub struct ActionSequence {
-    pub id: Nullable<String>,
-    pub actions: ActionsType
-}
-
-impl Parameters for ActionSequence {
-    fn from_json(body: &Json) -> WebDriverResult<ActionSequence> {
-        let data = try_opt!(body.as_object(),
-                            ErrorStatus::InvalidArgument,
-                            "Actions chain was not an object");
-
-        let type_name = try_opt!(try_opt!(data.get("type"),
-                                          ErrorStatus::InvalidArgument,
-                                          "Missing type parameter").as_string(),
-                                 ErrorStatus::InvalidArgument,
-                                 "Parameter ;type' was not a string");
-
-        let id = match data.get("id") {
-            Some(x) => Some(try_opt!(x.as_string(),
-                                     ErrorStatus::InvalidArgument,
-                                     "Parameter 'id' was not a string").to_owned()),
-            None => None
-        };
-
-
-        // Note that unlike the spec we get the pointer parameters in ActionsType::from_json
-
-        let actions = match type_name {
-            "none" | "key" | "pointer" => try!(ActionsType::from_json(&body)),
-            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                "Invalid action type"))
-        };
-
-        Ok(ActionSequence {
-            id: id.into(),
-            actions: actions
-        })
-    }
-}
-
-impl ToJson for ActionSequence {
-    fn to_json(&self) -> Json {
-        let mut data: BTreeMap<String, Json> = BTreeMap::new();
-        data.insert("id".into(), self.id.to_json());
-        let (action_type, actions) = match self.actions {
-            ActionsType::Null(ref actions) => {
-                ("none",
-                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
-            }
-            ActionsType::Key(ref actions) => {
-                ("key",
-                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
-            }
-            ActionsType::Pointer(ref parameters, ref actions) => {
-                data.insert("parameters".into(), parameters.to_json());
-                ("pointer",
-                 actions.iter().map(|x| x.to_json()).collect::<Vec<Json>>())
-            }
-        };
-        data.insert("type".into(), action_type.to_json());
-        data.insert("actions".into(), actions.to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum ActionsType {
-    Null(Vec<NullActionItem>),
-    Key(Vec<KeyActionItem>),
-    Pointer(PointerActionParameters, Vec<PointerActionItem>)
-}
-
-impl Parameters for ActionsType {
-    fn from_json(body: &Json) -> WebDriverResult<ActionsType> {
-        // These unwraps are OK as long as this is only called from ActionSequence::from_json
-        let data = body.as_object().expect("Body should be a JSON Object");
-        let actions_type = body.find("type").and_then(|x| x.as_string()).expect("Type should be a string");
-        let actions_chain = try_opt!(try_opt!(data.get("actions"),
-                                              ErrorStatus::InvalidArgument,
-                                              "Missing actions parameter").as_array(),
-                                     ErrorStatus::InvalidArgument,
-                                     "Parameter 'actions' was not an array");
-        match actions_type {
-            "none" => {
-                let mut actions = Vec::with_capacity(actions_chain.len());
-                for action_body in actions_chain.iter() {
-                    actions.push(try!(NullActionItem::from_json(action_body)));
-                };
-                Ok(ActionsType::Null(actions))
-            },
-            "key" => {
-                let mut actions = Vec::with_capacity(actions_chain.len());
-                for action_body in actions_chain.iter() {
-                    actions.push(try!(KeyActionItem::from_json(action_body)));
-                };
-                Ok(ActionsType::Key(actions))
-            },
-            "pointer" => {
-                let mut actions = Vec::with_capacity(actions_chain.len());
-                let parameters = match data.get("parameters") {
-                    Some(x) => try!(PointerActionParameters::from_json(x)),
-                    None => Default::default()
-                };
-
-                for action_body in actions_chain.iter() {
-                    actions.push(try!(PointerActionItem::from_json(action_body)));
-                }
-                Ok(ActionsType::Pointer(parameters, actions))
-            }
-            _ => panic!("Got unexpected action type after checking type")
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum PointerType {
-    Mouse,
-    Pen,
-    Touch,
-}
-
-impl Parameters for PointerType {
-    fn from_json(body: &Json) -> WebDriverResult<PointerType> {
-        match body.as_string() {
-            Some("mouse") => Ok(PointerType::Mouse),
-            Some("pen") => Ok(PointerType::Pen),
-            Some("touch") => Ok(PointerType::Touch),
-            Some(_) => Err(WebDriverError::new(
-                ErrorStatus::InvalidArgument,
-                "Unsupported pointer type"
-            )),
-            None => Err(WebDriverError::new(
-                ErrorStatus::InvalidArgument,
-                "Pointer type was not a string"
-            ))
-        }
-    }
-}
-
-impl ToJson for PointerType {
-    fn to_json(&self) -> Json {
-        match *self {
-            PointerType::Mouse => "mouse".to_json(),
-            PointerType::Pen => "pen".to_json(),
-            PointerType::Touch => "touch".to_json(),
-        }.to_json()
-    }
-}
-
-impl Default for PointerType {
-    fn default() -> PointerType {
-        PointerType::Mouse
-    }
-}
-
-#[derive(Debug, Default, PartialEq)]
-pub struct PointerActionParameters {
-    pub pointer_type: PointerType
-}
-
-impl Parameters for PointerActionParameters {
-    fn from_json(body: &Json) -> WebDriverResult<PointerActionParameters> {
-        let data = try_opt!(body.as_object(),
-                            ErrorStatus::InvalidArgument,
-                            "Parameter 'parameters' was not an object");
-        let pointer_type = match data.get("pointerType") {
-            Some(x) => try!(PointerType::from_json(x)),
-            None => PointerType::default()
-        };
-        Ok(PointerActionParameters {
-            pointer_type: pointer_type
-        })
-    }
-}
-
-impl ToJson for PointerActionParameters {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("pointerType".to_owned(),
-                    self.pointer_type.to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum NullActionItem {
-    General(GeneralAction)
-}
-
-impl Parameters for NullActionItem {
-    fn from_json(body: &Json) -> WebDriverResult<NullActionItem> {
-        let data = try_opt!(body.as_object(),
-                            ErrorStatus::InvalidArgument,
-                            "Actions chain was not an object");
-        let type_name = try_opt!(
-            try_opt!(data.get("type"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'type' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "Parameter 'type' was not a string");
-        match type_name {
-            "pause" => Ok(NullActionItem::General(
-                try!(GeneralAction::from_json(body)))),
-            _ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                "Invalid type attribute"))
-        }
-    }
-}
-
-impl ToJson for NullActionItem {
-    fn to_json(&self) -> Json {
-        match self {
-            &NullActionItem::General(ref x) => x.to_json(),
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum KeyActionItem {
-    General(GeneralAction),
-    Key(KeyAction)
-}
-
-impl Parameters for KeyActionItem {
-    fn from_json(body: &Json) -> WebDriverResult<KeyActionItem> {
-        let data = try_opt!(body.as_object(),
-                            ErrorStatus::InvalidArgument,
-                            "Key action item was not an object");
-        let type_name = try_opt!(
-            try_opt!(data.get("type"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'type' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "Parameter 'type' was not a string");
-        match type_name {
-            "pause" => Ok(KeyActionItem::General(
-                try!(GeneralAction::from_json(body)))),
-            _ => Ok(KeyActionItem::Key(
-                try!(KeyAction::from_json(body))))
-        }
-    }
-}
-
-impl ToJson for KeyActionItem {
-    fn to_json(&self) -> Json {
-        match *self {
-            KeyActionItem::General(ref x) => x.to_json(),
-            KeyActionItem::Key(ref x) => x.to_json()
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum PointerActionItem {
-    General(GeneralAction),
-    Pointer(PointerAction)
-}
-
-impl Parameters for PointerActionItem {
-    fn from_json(body: &Json) -> WebDriverResult<PointerActionItem> {
-        let data = try_opt!(body.as_object(),
-                            ErrorStatus::InvalidArgument,
-                            "Pointer action item was not an object");
-        let type_name = try_opt!(
-            try_opt!(data.get("type"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'type' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "Parameter 'type' was not a string");
-
-        match type_name {
-            "pause" => Ok(PointerActionItem::General(try!(GeneralAction::from_json(body)))),
-            _ => Ok(PointerActionItem::Pointer(try!(PointerAction::from_json(body))))
-        }
-    }
-}
-
-impl ToJson for PointerActionItem {
-    fn to_json(&self) -> Json {
-        match self {
-            &PointerActionItem::General(ref x) => x.to_json(),
-            &PointerActionItem::Pointer(ref x) => x.to_json()
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum GeneralAction {
-    Pause(PauseAction)
-}
-
-impl Parameters for GeneralAction {
-    fn from_json(body: &Json) -> WebDriverResult<GeneralAction> {
-        match body.find("type").and_then(|x| x.as_string()) {
-            Some("pause") => Ok(GeneralAction::Pause(try!(PauseAction::from_json(body)))),
-            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                         "Invalid or missing type attribute"))
-        }
-    }
-}
-
-impl ToJson for GeneralAction {
-    fn to_json(&self) -> Json {
-        match self {
-            &GeneralAction::Pause(ref x) => x.to_json()
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct PauseAction {
-    pub duration: u64
-}
-
-impl Parameters for PauseAction {
-    fn from_json(body: &Json) -> WebDriverResult<PauseAction> {
-        let default = Json::U64(0);
-        Ok(PauseAction {
-            duration: try_opt!(body.find("duration").unwrap_or(&default).as_u64(),
-                               ErrorStatus::InvalidArgument,
-                               "Parameter 'duration' was not a positive integer")
-        })
-    }
-}
-
-impl ToJson for PauseAction {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("type".to_owned(),
-                    "pause".to_json());
-        data.insert("duration".to_owned(),
-                    self.duration.to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum KeyAction {
-    Up(KeyUpAction),
-    Down(KeyDownAction)
-}
-
-impl Parameters for KeyAction {
-    fn from_json(body: &Json) -> WebDriverResult<KeyAction> {
-        match body.find("type").and_then(|x| x.as_string()) {
-            Some("keyDown") => Ok(KeyAction::Down(try!(KeyDownAction::from_json(body)))),
-            Some("keyUp") => Ok(KeyAction::Up(try!(KeyUpAction::from_json(body)))),
-            Some(_) | None => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                      "Invalid type attribute value for key action"))
-        }
-    }
-}
-
-impl ToJson for KeyAction {
-    fn to_json(&self) -> Json {
-        match self {
-            &KeyAction::Down(ref x) => x.to_json(),
-            &KeyAction::Up(ref x) => x.to_json(),
-        }
-    }
-}
-
-fn validate_key_value(value_str: &str) -> WebDriverResult<char> {
-    let mut chars = value_str.chars();
-    let value = if let Some(c) = chars.next() {
-        c
-    } else {
-        return Err(WebDriverError::new(
-            ErrorStatus::InvalidArgument,
-            "Parameter 'value' was an empty string"))
-    };
-    if chars.next().is_some() {
-        return Err(WebDriverError::new(
-            ErrorStatus::InvalidArgument,
-            "Parameter 'value' contained multiple characters"))
-    };
-    Ok(value)
-}
-
-#[derive(Debug, PartialEq)]
-pub struct KeyUpAction {
-    pub value: char
-}
-
-impl Parameters for KeyUpAction {
-    fn from_json(body: &Json) -> WebDriverResult<KeyUpAction> {
-        let value_str = try_opt!(
-                try_opt!(body.find("value"),
-                         ErrorStatus::InvalidArgument,
-                         "Missing value parameter").as_string(),
-                ErrorStatus::InvalidArgument,
-            "Parameter 'value' was not a string");
-
-        let value = try!(validate_key_value(value_str));
-        Ok(KeyUpAction {
-            value: value
-        })
-    }
-}
-
-impl ToJson for KeyUpAction {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("type".to_owned(),
-                    "keyUp".to_json());
-        data.insert("value".to_string(),
-                    self.value.to_string().to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct KeyDownAction {
-    pub value: char
-}
-
-impl Parameters for KeyDownAction {
-    fn from_json(body: &Json) -> WebDriverResult<KeyDownAction> {
-        let value_str = try_opt!(
-                try_opt!(body.find("value"),
-                         ErrorStatus::InvalidArgument,
-                         "Missing value parameter").as_string(),
-                ErrorStatus::InvalidArgument,
-            "Parameter 'value' was not a string");
-        let value = try!(validate_key_value(value_str));
-        Ok(KeyDownAction {
-            value: value
-        })
-    }
-}
-
-impl ToJson for KeyDownAction {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("type".to_owned(),
-                    "keyDown".to_json());
-        data.insert("value".to_owned(),
-                    self.value.to_string().to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum PointerOrigin {
-    Viewport,
-    Pointer,
-    Element(WebElement),
-}
-
-impl Parameters for PointerOrigin {
-    fn from_json(body: &Json) -> WebDriverResult<PointerOrigin> {
-        match *body {
-            Json::String(ref x) => {
-                match &**x {
-                    "viewport" => Ok(PointerOrigin::Viewport),
-                    "pointer" => Ok(PointerOrigin::Pointer),
-                    _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                                                 "Unknown pointer origin"))
-                }
-            },
-            Json::Object(_) => Ok(PointerOrigin::Element(try!(WebElement::from_json(body)))),
-            _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument,
-                        "Pointer origin was not a string or an object"))
-        }
-    }
-}
-
-impl ToJson for PointerOrigin {
-    fn to_json(&self) -> Json {
-        match *self {
-            PointerOrigin::Viewport => "viewport".to_json(),
-            PointerOrigin::Pointer => "pointer".to_json(),
-            PointerOrigin::Element(ref x) => x.to_json(),
-        }
-    }
-}
-
-impl Default for PointerOrigin {
-    fn default() -> PointerOrigin {
-        PointerOrigin::Viewport
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum PointerAction {
-    Up(PointerUpAction),
-    Down(PointerDownAction),
-    Move(PointerMoveAction),
-    Cancel
-}
-
-impl Parameters for PointerAction {
-    fn from_json(body: &Json) -> WebDriverResult<PointerAction> {
-        match body.find("type").and_then(|x| x.as_string()) {
-            Some("pointerUp") => Ok(PointerAction::Up(try!(PointerUpAction::from_json(body)))),
-            Some("pointerDown") => Ok(PointerAction::Down(try!(PointerDownAction::from_json(body)))),
-            Some("pointerMove") => Ok(PointerAction::Move(try!(PointerMoveAction::from_json(body)))),
-            Some("pointerCancel") => Ok(PointerAction::Cancel),
-            Some(_) | None => Err(WebDriverError::new(
-                ErrorStatus::InvalidArgument,
-                "Missing or invalid type argument for pointer action"))
-        }
-    }
-}
-
-impl ToJson for PointerAction {
-    fn to_json(&self) -> Json {
-        match self {
-            &PointerAction::Down(ref x) => x.to_json(),
-            &PointerAction::Up(ref x) => x.to_json(),
-            &PointerAction::Move(ref x) => x.to_json(),
-            &PointerAction::Cancel => {
-                let mut data = BTreeMap::new();
-                data.insert("type".to_owned(),
-                            "pointerCancel".to_json());
-                Json::Object(data)
-            }
-        }
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct PointerUpAction {
-    pub button: u64,
-}
-
-impl Parameters for PointerUpAction {
-    fn from_json(body: &Json) -> WebDriverResult<PointerUpAction> {
-        let button = try_opt!(
-            try_opt!(body.find("button"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing button parameter").as_u64(),
-            ErrorStatus::InvalidArgument,
-            "Parameter 'button' was not a positive integer");
-
-        Ok(PointerUpAction {
-            button: button
-        })
-    }
-}
-
-impl ToJson for PointerUpAction {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("type".to_owned(),
-                    "pointerUp".to_json());
-        data.insert("button".to_owned(), self.button.to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct PointerDownAction {
-    pub button: u64,
-}
-
-impl Parameters for PointerDownAction {
-    fn from_json(body: &Json) -> WebDriverResult<PointerDownAction> {
-        let button = try_opt!(
-            try_opt!(body.find("button"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing button parameter").as_u64(),
-            ErrorStatus::InvalidArgument,
-            "Parameter 'button' was not a positive integer");
-
-        Ok(PointerDownAction {
-            button: button
-        })
-    }
-}
-
-impl ToJson for PointerDownAction {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("type".to_owned(),
-                    "pointerDown".to_json());
-        data.insert("button".to_owned(), self.button.to_json());
-        Json::Object(data)
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub struct PointerMoveAction {
-    pub duration: Nullable<u64>,
-    pub origin: PointerOrigin,
-    pub x: Nullable<i64>,
-    pub y: Nullable<i64>
-}
-
-impl Parameters for PointerMoveAction {
-    fn from_json(body: &Json) -> WebDriverResult<PointerMoveAction> {
-        let duration = match body.find("duration") {
-            Some(duration) => Some(try_opt!(duration.as_u64(),
-                                            ErrorStatus::InvalidArgument,
-                                            "Parameter 'duration' was not a positive integer")),
-            None => None
-
-        };
-
-        let origin = match body.find("origin") {
-            Some(o) => try!(PointerOrigin::from_json(o)),
-            None => PointerOrigin::default()
-        };
-
-        let x = match body.find("x") {
-            Some(x) => {
-                Some(try_opt!(x.as_i64(),
-                              ErrorStatus::InvalidArgument,
-                              "Parameter 'x' was not an integer"))
-            },
-            None => None
-        };
-
-        let y = match body.find("y") {
-            Some(y) => {
-                Some(try_opt!(y.as_i64(),
-                              ErrorStatus::InvalidArgument,
-                              "Parameter 'y' was not an integer"))
-            },
-            None => None
-        };
-
-        Ok(PointerMoveAction {
-            duration: duration.into(),
-            origin: origin.into(),
-            x: x.into(),
-            y: y.into(),
-        })
-    }
-}
-
-impl ToJson for PointerMoveAction {
-    fn to_json(&self) -> Json {
-        let mut data = BTreeMap::new();
-        data.insert("type".to_owned(), "pointerMove".to_json());
-        if self.duration.is_value() {
-            data.insert("duration".to_owned(),
-                        self.duration.to_json());
-        }
-
-        data.insert("origin".to_owned(), self.origin.to_json());
-
-        if self.x.is_value() {
-            data.insert("x".to_owned(), self.x.to_json());
-        }
-        if self.y.is_value() {
-            data.insert("y".to_owned(), self.y.to_json());
-        }
-        Json::Object(data)
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use rustc_serialize::json::Json;
     use super::{Nullable, Parameters, WindowRectParameters};
 
     #[test]
     fn test_window_rect() {
         let expected = WindowRectParameters {
--- a/testing/webdriver/src/lib.rs
+++ b/testing/webdriver/src/lib.rs
@@ -5,16 +5,17 @@ extern crate log;
 extern crate rustc_serialize;
 extern crate hyper;
 extern crate regex;
 extern crate cookie;
 extern crate time;
 extern crate url;
 
 #[macro_use] pub mod macros;
+pub mod actions;
 pub mod httpapi;
 pub mod capabilities;
 pub mod command;
 pub mod common;
 pub mod error;
 pub mod server;
 pub mod response;