Bug 1442028 - Move webdriver actions into its own module. draft
authorHenrik Skupin <mail@hskupin.info>
Wed, 28 Feb 2018 22:31:47 +0100
changeset 762091 82b70b23d4d758e39ab29f03cb6feeadb6aa2ad3
parent 760185 580d833df9c44acec686a9fb88b5f27e9d29f68d
push id101083
push userbmo:hskupin@gmail.com
push dateThu, 01 Mar 2018 20:16:46 +0000
bugs1442028
milestone60.0a1
Bug 1442028 - Move webdriver actions into its own module. 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;