Bug 1481776 - [geckodriver] Move WebDriver related code into the command module. r=ato
authorHenrik Skupin <mail@hskupin.info>
Fri, 24 Aug 2018 14:23:34 +0200
changeset 481875 fb80a4f44ac2a1ea9007d72593521dde33e4d2bc
parent 481874 275602e7cfcdd2e9c7326bbd190525cd92f21832
child 481876 64213b300680bd87b79990b63a9304f80034a664
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersato
bugs1481776
milestone63.0a1
Bug 1481776 - [geckodriver] Move WebDriver related code into the command module. r=ato
testing/geckodriver/src/capabilities.rs
testing/geckodriver/src/command.rs
testing/geckodriver/src/main.rs
testing/geckodriver/src/marionette.rs
--- a/testing/geckodriver/src/capabilities.rs
+++ b/testing/geckodriver/src/capabilities.rs
@@ -1,11 +1,11 @@
 use base64;
+use command::LogOptions;
 use logging::Level;
-use marionette::LogOptions;
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
 use mozrunner::runner::platform::firefox_default_path;
 use mozversion::{self, firefox_version, Version};
 use regex::bytes::Regex;
 use serde_json::{Map, Value};
 use std::collections::BTreeMap;
 use std::default::Default;
new file mode 100644
--- /dev/null
+++ b/testing/geckodriver/src/command.rs
@@ -0,0 +1,459 @@
+use base64;
+use hyper::Method;
+use logging;
+use regex::Captures;
+use serde::de::{self, Deserialize, Deserializer};
+use serde_json::{self, Value};
+use std::env;
+use std::fs::File;
+use std::io::prelude::*;
+use uuid::Uuid;
+use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand};
+use webdriver::common::WebElement;
+use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
+use webdriver::httpapi::WebDriverExtensionRoute;
+
+pub const CHROME_ELEMENT_KEY: &'static str = "chromeelement-9fc5-4b51-a3c8-01716eedeb04";
+pub const LEGACY_ELEMENT_KEY: &'static str = "ELEMENT";
+
+pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
+    return vec![
+        (
+            Method::GET,
+            "/session/{sessionId}/moz/context",
+            GeckoExtensionRoute::GetContext,
+        ),
+        (
+            Method::POST,
+            "/session/{sessionId}/moz/context",
+            GeckoExtensionRoute::SetContext,
+        ),
+        (
+            Method::POST,
+            "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",
+            GeckoExtensionRoute::XblAnonymousChildren,
+        ),
+        (
+            Method::POST,
+            "/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute",
+            GeckoExtensionRoute::XblAnonymousByAttribute,
+        ),
+        (
+            Method::POST,
+            "/session/{sessionId}/moz/addon/install",
+            GeckoExtensionRoute::InstallAddon,
+        ),
+        (
+            Method::POST,
+            "/session/{sessionId}/moz/addon/uninstall",
+            GeckoExtensionRoute::UninstallAddon,
+        ),
+    ];
+}
+
+#[derive(Clone, PartialEq)]
+pub enum GeckoExtensionRoute {
+    GetContext,
+    SetContext,
+    XblAnonymousChildren,
+    XblAnonymousByAttribute,
+    InstallAddon,
+    UninstallAddon,
+}
+
+impl WebDriverExtensionRoute for GeckoExtensionRoute {
+    type Command = GeckoExtensionCommand;
+
+    fn command(
+        &self,
+        params: &Captures,
+        body_data: &Value,
+    ) -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> {
+        let command = match self {
+            &GeckoExtensionRoute::GetContext => GeckoExtensionCommand::GetContext,
+            &GeckoExtensionRoute::SetContext => {
+                GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?)
+            }
+            &GeckoExtensionRoute::XblAnonymousChildren => {
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
+                let element = WebElement::new(element_id.as_str().to_string());
+                GeckoExtensionCommand::XblAnonymousChildren(element)
+            }
+            &GeckoExtensionRoute::XblAnonymousByAttribute => {
+                let element_id = try_opt!(
+                    params.name("elementId"),
+                    ErrorStatus::InvalidArgument,
+                    "Missing elementId parameter"
+                );
+                GeckoExtensionCommand::XblAnonymousByAttribute(
+                    WebElement::new(element_id.as_str().into()),
+                    serde_json::from_value(body_data.clone())?,
+                )
+            }
+            &GeckoExtensionRoute::InstallAddon => {
+                GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?)
+            }
+            &GeckoExtensionRoute::UninstallAddon => {
+                GeckoExtensionCommand::UninstallAddon(serde_json::from_value(body_data.clone())?)
+            }
+        };
+        Ok(WebDriverCommand::Extension(command))
+    }
+}
+
+#[derive(Clone, PartialEq)]
+pub enum GeckoExtensionCommand {
+    GetContext,
+    SetContext(GeckoContextParameters),
+    XblAnonymousChildren(WebElement),
+    XblAnonymousByAttribute(WebElement, XblLocatorParameters),
+    InstallAddon(AddonInstallParameters),
+    UninstallAddon(AddonUninstallParameters),
+}
+
+impl WebDriverExtensionCommand for GeckoExtensionCommand {
+    fn parameters_json(&self) -> Option<Value> {
+        match self {
+            &GeckoExtensionCommand::GetContext => None,
+            &GeckoExtensionCommand::InstallAddon(ref x) => {
+                Some(serde_json::to_value(x.clone()).unwrap())
+            }
+            &GeckoExtensionCommand::SetContext(ref x) => {
+                Some(serde_json::to_value(x.clone()).unwrap())
+            }
+            &GeckoExtensionCommand::UninstallAddon(ref x) => {
+                Some(serde_json::to_value(x.clone()).unwrap())
+            }
+            &GeckoExtensionCommand::XblAnonymousByAttribute(_, ref x) => {
+                Some(serde_json::to_value(x.clone()).unwrap())
+            }
+            &GeckoExtensionCommand::XblAnonymousChildren(_) => None,
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize)]
+pub struct AddonInstallParameters {
+    pub path: String,
+    pub temporary: bool,
+}
+
+impl<'de> Deserialize<'de> for AddonInstallParameters {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        #[derive(Debug, Deserialize)]
+        #[serde(deny_unknown_fields)]
+        struct Base64 {
+            addon: String,
+            temporary: bool,
+        };
+
+        #[derive(Debug, Deserialize)]
+        #[serde(deny_unknown_fields)]
+        struct Path {
+            path: String,
+            temporary: bool,
+        };
+
+        #[derive(Debug, Deserialize)]
+        #[serde(untagged)]
+        enum Helper {
+            Base64(Base64),
+            Path(Path),
+        };
+
+        let params = match Helper::deserialize(deserializer)? {
+            Helper::Path(ref mut data) => AddonInstallParameters {
+                path: data.path.clone(),
+                temporary: data.temporary,
+            },
+            Helper::Base64(ref mut data) => {
+                let content = base64::decode(&data.addon).map_err(de::Error::custom)?;
+
+                let path = env::temp_dir()
+                    .as_path()
+                    .join(format!("addon-{}.xpi", Uuid::new_v4()));
+                let mut xpi_file = File::create(&path).map_err(de::Error::custom)?;
+                xpi_file
+                    .write(content.as_slice())
+                    .map_err(de::Error::custom)?;
+
+                let path = match path.to_str() {
+                    Some(path) => path.to_string(),
+                    None => return Err(de::Error::custom("could not write addon to file")),
+                };
+
+                AddonInstallParameters {
+                    path: path,
+                    temporary: data.temporary,
+                }
+            }
+        };
+
+        Ok(params)
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct AddonUninstallParameters {
+    pub id: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum GeckoContext {
+    Content,
+    Chrome,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct GeckoContextParameters {
+    pub context: GeckoContext,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct XblLocatorParameters {
+    pub name: String,
+    pub value: String,
+}
+
+#[derive(Default, Debug)]
+pub struct LogOptions {
+    pub level: Option<logging::Level>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fs::File;
+    use std::io::Read;
+    use test::check_deserialize;
+
+    #[test]
+    fn test_json_addon_install_parameters_null() {
+        let json = r#""#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_empty() {
+        let json = r#"{}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_path() {
+        let json = r#"{"path": "/path/to.xpi", "temporary": true}"#;
+        let data = AddonInstallParameters {
+            path: "/path/to.xpi".to_string(),
+            temporary: true,
+        };
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_path_invalid_type() {
+        let json = r#"{"path": true, "temporary": true}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_path_and_temporary_invalid_type() {
+        let json = r#"{"path": "/path/to.xpi", "temporary": "foo"}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_path_only() {
+        let json = r#"{"path": "/path/to.xpi"}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_addon() {
+        let json = r#"{"addon": "aGVsbG8=", "temporary": true}"#;
+        let data = serde_json::from_str::<AddonInstallParameters>(&json).unwrap();
+
+        assert_eq!(data.temporary, true);
+        let mut file = File::open(data.path).unwrap();
+        let mut contents = String::new();
+        file.read_to_string(&mut contents).unwrap();
+        assert_eq!(contents, "hello");
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_addon_invalid_type() {
+        let json = r#"{"addon": true, "temporary": true}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_addon_and_temporary_invalid_type() {
+        let json = r#"{"addon": "aGVsbG8=", "temporary": "foo"}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_addon_only() {
+        let json = r#"{"addon": "aGVsbG8="}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_install_parameters_with_temporary_only() {
+        let json = r#"{"temporary": true}"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_install_parameters_with_both_path_and_addon() {
+        let json = r#"{
+            "path":"/path/to.xpi",
+            "addon":"aGVsbG8=",
+            "temporary":true
+        }"#;
+
+        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_uninstall_parameters_null() {
+        let json = r#""#;
+
+        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_uninstall_parameters_empty() {
+        let json = r#"{}"#;
+
+        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_addon_uninstall_parameters() {
+        let json = r#"{"id": "foo"}"#;
+        let data = AddonUninstallParameters {
+            id: "foo".to_string(),
+        };
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_addon_uninstall_parameters_id_invalid_type() {
+        let json = r#"{"id": true}"#;
+
+        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_gecko_context_parameters_content() {
+        let json = r#"{"context": "content"}"#;
+        let data = GeckoContextParameters {
+            context: GeckoContext::Content,
+        };
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_gecko_context_parameters_chrome() {
+        let json = r#"{"context": "chrome"}"#;
+        let data = GeckoContextParameters {
+            context: GeckoContext::Chrome,
+        };
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_gecko_context_parameters_context_missing() {
+        let json = r#"{}"#;
+
+        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_gecko_context_parameters_context_null() {
+        let json = r#"{"context": null}"#;
+
+        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_gecko_context_parameters_context_invalid_value() {
+        let json = r#"{"context": "foo"}"#;
+
+        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_xbl_anonymous_by_attribute() {
+        let json = r#"{
+            "name": "foo",
+            "value": "bar"
+        }"#;
+
+        let data = XblLocatorParameters {
+            name: "foo".to_string(),
+            value: "bar".to_string(),
+        };
+
+        check_deserialize(&json, &data);
+    }
+
+    #[test]
+    fn test_json_xbl_anonymous_by_attribute_with_name_missing() {
+        let json = r#"{
+            "value": "bar"
+        }"#;
+
+        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_xbl_anonymous_by_attribute_with_name_invalid_type() {
+        let json = r#"{
+            "name": null,
+            "value": "bar"
+        }"#;
+
+        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_xbl_anonymous_by_attribute_with_value_missing() {
+        let json = r#"{
+            "name": "foo",
+        }"#;
+
+        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+    }
+
+    #[test]
+    fn test_json_xbl_anonymous_by_attribute_with_value_invalid_type() {
+        let json = r#"{
+            "name": "foo",
+            "value": null
+        }"#;
+
+        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+    }
+}
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -33,25 +33,27 @@ macro_rules! try_opt {
             Some(x) => x,
             None => return Err(WebDriverError::new($err_type, $err_msg)),
         }
     }};
 }
 
 mod build;
 mod capabilities;
+mod command;
 mod logging;
 mod marionette;
 mod prefs;
 
 #[cfg(test)]
 pub mod test;
 
 use build::BuildInfo;
-use marionette::{extension_routes, MarionetteHandler, MarionetteSettings};
+use command::extension_routes;
+use marionette::{MarionetteHandler, MarionetteSettings};
 
 type ProgramResult = std::result::Result<(), (ExitCode, String)>;
 
 enum ExitCode {
     Ok = 0,
     Usage = 64,
     Unavailable = 69,
 }
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -1,30 +1,27 @@
-use base64;
-use hyper::Method;
+use command::{AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
+              GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters,
+              CHROME_ELEMENT_KEY, LEGACY_ELEMENT_KEY};
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
 use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
-use regex::Captures;
 use serde::de::{self, Deserialize, Deserializer};
 use serde::ser::{Serialize, Serializer};
 use serde_json::{self, Map, Value};
-use std::env;
 use std::error::Error;
-use std::fs::File;
 use std::io::prelude::*;
 use std::io::Error as IoError;
 use std::io::ErrorKind;
 use std::io::Result as IoResult;
 use std::net::{TcpListener, TcpStream};
 use std::path::PathBuf;
 use std::sync::Mutex;
 use std::thread;
 use std::time;
-use uuid::Uuid;
 use webdriver::capabilities::CapabilitiesMatching;
 use webdriver::command::WebDriverCommand::{AcceptAlert, AddCookie, CloseWindow, DeleteCookie,
                                            DeleteCookies, DeleteSession, DismissAlert,
                                            ElementClear, ElementClick, ElementSendKeys,
                                            ElementTap, ExecuteAsyncScript, ExecuteScript,
                                            Extension, FindElement, FindElementElement,
                                            FindElementElements, FindElements, FullscreenWindow,
                                            Get, GetActiveElement, GetAlertText, GetCSSValue,
@@ -38,292 +35,33 @@ use webdriver::command::WebDriverCommand
                                            ReleaseActions, SendAlertText, SetTimeouts,
                                            SetWindowRect, Status, SwitchToFrame,
                                            SwitchToParentFrame, SwitchToWindow,
                                            TakeElementScreenshot, TakeScreenshot};
 use webdriver::command::{ActionsParameters, AddCookieParameters, GetNamedCookieParameters,
                          GetParameters, JavascriptCommandParameters, LocatorParameters,
                          NewSessionParameters, SwitchToFrameParameters, SwitchToWindowParameters,
                          TakeScreenshotParameters, TimeoutsParameters, WindowRectParameters};
-use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand, WebDriverMessage};
+use webdriver::command::{WebDriverCommand, WebDriverMessage};
 use webdriver::common::{Cookie, FrameId, WebElement, ELEMENT_KEY, FRAME_KEY, WINDOW_KEY};
 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
-use webdriver::httpapi::WebDriverExtensionRoute;
 use webdriver::response::{CloseWindowResponse, CookieResponse, CookiesResponse,
                           ElementRectResponse, NewSessionResponse, TimeoutsResponse,
                           ValueResponse, WebDriverResponse, WindowRectResponse};
 use webdriver::server::{Session, WebDriverHandler};
 
 use build::BuildInfo;
 use capabilities::{FirefoxCapabilities, FirefoxOptions};
 use logging;
 use prefs;
 
 // localhost may be routed to the IPv6 stack on certain systems,
 // and nsIServerSocket in Marionette only supports IPv4
 const DEFAULT_HOST: &'static str = "127.0.0.1";
 
-const CHROME_ELEMENT_KEY: &'static str = "chromeelement-9fc5-4b51-a3c8-01716eedeb04";
-const LEGACY_ELEMENT_KEY: &'static str = "ELEMENT";
-
-pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
-    return vec![
-        (
-            Method::GET,
-            "/session/{sessionId}/moz/context",
-            GeckoExtensionRoute::GetContext,
-        ),
-        (
-            Method::POST,
-            "/session/{sessionId}/moz/context",
-            GeckoExtensionRoute::SetContext,
-        ),
-        (
-            Method::POST,
-            "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",
-            GeckoExtensionRoute::XblAnonymousChildren,
-        ),
-        (
-            Method::POST,
-            "/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute",
-            GeckoExtensionRoute::XblAnonymousByAttribute,
-        ),
-        (
-            Method::POST,
-            "/session/{sessionId}/moz/addon/install",
-            GeckoExtensionRoute::InstallAddon,
-        ),
-        (
-            Method::POST,
-            "/session/{sessionId}/moz/addon/uninstall",
-            GeckoExtensionRoute::UninstallAddon,
-        ),
-    ];
-}
-
-#[derive(Clone, PartialEq)]
-pub enum GeckoExtensionRoute {
-    GetContext,
-    SetContext,
-    XblAnonymousChildren,
-    XblAnonymousByAttribute,
-    InstallAddon,
-    UninstallAddon,
-}
-
-impl WebDriverExtensionRoute for GeckoExtensionRoute {
-    type Command = GeckoExtensionCommand;
-
-    fn command(
-        &self,
-        params: &Captures,
-        body_data: &Value,
-    ) -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> {
-        let command = match self {
-            &GeckoExtensionRoute::GetContext => GeckoExtensionCommand::GetContext,
-            &GeckoExtensionRoute::SetContext => {
-                GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?)
-            }
-            &GeckoExtensionRoute::XblAnonymousChildren => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
-                let element = WebElement::new(element_id.as_str().to_string());
-                GeckoExtensionCommand::XblAnonymousChildren(element)
-            }
-            &GeckoExtensionRoute::XblAnonymousByAttribute => {
-                let element_id = try_opt!(
-                    params.name("elementId"),
-                    ErrorStatus::InvalidArgument,
-                    "Missing elementId parameter"
-                );
-                GeckoExtensionCommand::XblAnonymousByAttribute(
-                    WebElement::new(element_id.as_str().into()),
-                    serde_json::from_value(body_data.clone())?,
-                )
-            }
-            &GeckoExtensionRoute::InstallAddon => {
-                GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?)
-            }
-            &GeckoExtensionRoute::UninstallAddon => {
-                GeckoExtensionCommand::UninstallAddon(serde_json::from_value(body_data.clone())?)
-            }
-        };
-        Ok(WebDriverCommand::Extension(command))
-    }
-}
-
-#[derive(Clone, PartialEq)]
-pub enum GeckoExtensionCommand {
-    GetContext,
-    SetContext(GeckoContextParameters),
-    XblAnonymousChildren(WebElement),
-    XblAnonymousByAttribute(WebElement, XblLocatorParameters),
-    InstallAddon(AddonInstallParameters),
-    UninstallAddon(AddonUninstallParameters),
-}
-
-impl WebDriverExtensionCommand for GeckoExtensionCommand {
-    fn parameters_json(&self) -> Option<Value> {
-        match self {
-            &GeckoExtensionCommand::GetContext => None,
-            &GeckoExtensionCommand::InstallAddon(ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::SetContext(ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::UninstallAddon(ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::XblAnonymousByAttribute(_, ref x) => {
-                Some(serde_json::to_value(x.clone()).unwrap())
-            }
-            &GeckoExtensionCommand::XblAnonymousChildren(_) => None,
-        }
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize)]
-pub struct AddonInstallParameters {
-    pub path: String,
-    pub temporary: bool,
-}
-
-impl<'de> Deserialize<'de> for AddonInstallParameters {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        #[derive(Debug, Deserialize)]
-        #[serde(deny_unknown_fields)]
-        struct Base64 {
-            addon: String,
-            temporary: bool,
-        };
-
-        #[derive(Debug, Deserialize)]
-        #[serde(deny_unknown_fields)]
-        struct Path {
-            path: String,
-            temporary: bool,
-        };
-
-        #[derive(Debug, Deserialize)]
-        #[serde(untagged)]
-        enum Helper {
-            Base64(Base64),
-            Path(Path),
-        };
-
-        let params = match Helper::deserialize(deserializer)? {
-            Helper::Path(ref mut data) => AddonInstallParameters {
-                path: data.path.clone(),
-                temporary: data.temporary,
-            },
-            Helper::Base64(ref mut data) => {
-                let content = base64::decode(&data.addon).map_err(de::Error::custom)?;
-
-                let path = env::temp_dir()
-                    .as_path()
-                    .join(format!("addon-{}.xpi", Uuid::new_v4()));
-                let mut xpi_file = File::create(&path).map_err(de::Error::custom)?;
-                xpi_file
-                    .write(content.as_slice())
-                    .map_err(de::Error::custom)?;
-
-                let path = match path.to_str() {
-                    Some(path) => path.to_string(),
-                    None => return Err(de::Error::custom("could not write addon to file")),
-                };
-
-                AddonInstallParameters {
-                    path: path,
-                    temporary: data.temporary,
-                }
-            }
-        };
-
-        Ok(params)
-    }
-}
-
-impl ToMarionette for AddonInstallParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert("path".to_string(), Value::String(self.path.clone()));
-        data.insert("temporary".to_string(), Value::Bool(self.temporary));
-        Ok(data)
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct AddonUninstallParameters {
-    pub id: String,
-}
-
-impl ToMarionette for AddonUninstallParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert("id".to_string(), Value::String(self.id.clone()));
-        Ok(data)
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "lowercase")]
-enum GeckoContext {
-    Content,
-    Chrome,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct GeckoContextParameters {
-    context: GeckoContext,
-}
-
-impl ToMarionette for GeckoContextParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut data = Map::new();
-        data.insert(
-            "value".to_owned(),
-            serde_json::to_value(self.context.clone())?,
-        );
-        Ok(data)
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct XblLocatorParameters {
-    name: String,
-    value: String,
-}
-
-impl ToMarionette for XblLocatorParameters {
-    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
-        let mut value = Map::new();
-        value.insert(self.name.to_owned(), Value::String(self.value.clone()));
-
-        let mut data = Map::new();
-        data.insert(
-            "using".to_owned(),
-            Value::String("anon attribute".to_string()),
-        );
-        data.insert("value".to_owned(), Value::Object(value));
-        Ok(data)
-    }
-}
-
-#[derive(Default, Debug)]
-pub struct LogOptions {
-    pub level: Option<logging::Level>,
-}
-
 #[derive(Debug, PartialEq, Deserialize)]
 pub struct MarionetteHandshake {
     #[serde(rename = "marionetteProtocol")]
     protocol: u16,
     #[serde(rename = "applicationType")]
     application_type: String,
 }
 
@@ -335,31 +73,31 @@ pub struct MarionetteSettings {
 
     /// Brings up the Browser Toolbox when starting Firefox,
     /// letting you debug internals.
     pub jsdebugger: bool,
 }
 
 #[derive(Default)]
 pub struct MarionetteHandler {
-    connection: Mutex<Option<MarionetteConnection>>,
-    settings: MarionetteSettings,
-    browser: Option<FirefoxProcess>,
+    pub connection: Mutex<Option<MarionetteConnection>>,
+    pub settings: MarionetteSettings,
+    pub browser: Option<FirefoxProcess>,
 }
 
 impl MarionetteHandler {
     pub fn new(settings: MarionetteSettings) -> MarionetteHandler {
         MarionetteHandler {
             connection: Mutex::new(None),
             settings,
             browser: None,
         }
     }
 
-    fn create_connection(
+    pub fn create_connection(
         &mut self,
         session_id: &Option<String>,
         new_session_parameters: &NewSessionParameters,
     ) -> WebDriverResult<Map<String, Value>> {
         let (options, capabilities) = {
             let mut fx_capabilities = FirefoxCapabilities::new(self.settings.binary.as_ref());
             let mut capabilities = try!(
                 try!(new_session_parameters.match_browser(&mut fx_capabilities)).ok_or(
@@ -1507,16 +1245,59 @@ impl MarionetteConnection {
         Ok(String::from_utf8(payload).unwrap())
     }
 }
 
 trait ToMarionette {
     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>>;
 }
 
+impl ToMarionette for AddonInstallParameters {
+    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
+        let mut data = Map::new();
+        data.insert("path".to_string(), Value::String(self.path.clone()));
+        data.insert("temporary".to_string(), Value::Bool(self.temporary));
+        Ok(data)
+    }
+}
+
+impl ToMarionette for AddonUninstallParameters {
+    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
+        let mut data = Map::new();
+        data.insert("id".to_string(), Value::String(self.id.clone()));
+        Ok(data)
+    }
+}
+
+impl ToMarionette for GeckoContextParameters {
+    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
+        let mut data = Map::new();
+        data.insert(
+            "value".to_owned(),
+            serde_json::to_value(self.context.clone())?,
+        );
+        Ok(data)
+    }
+}
+
+impl ToMarionette for XblLocatorParameters {
+    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
+        let mut value = Map::new();
+        value.insert(self.name.to_owned(), Value::String(self.value.clone()));
+
+        let mut data = Map::new();
+        data.insert(
+            "using".to_owned(),
+            Value::String("anon attribute".to_string()),
+        );
+        data.insert("value".to_owned(), Value::Object(value));
+        Ok(data)
+    }
+}
+
 impl ToMarionette for ActionsParameters {
     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
         Ok(try_opt!(
             serde_json::to_value(self)?.as_object(),
             ErrorStatus::UnknownError,
             "Expected an object"
         ).clone())
     }
@@ -1661,236 +1442,9 @@ impl ToMarionette for WindowRectParamete
             serde_json::to_value(self)?.as_object(),
             ErrorStatus::UnknownError,
             "Expected an object"
         ).clone())
     }
 }
 
 #[cfg(test)]
-mod tests {
-    use super::*;
-    use std::fs::File;
-    use std::io::Read;
-    use test::check_deserialize;
-
-    #[test]
-    fn test_json_addon_install_parameters_null() {
-        let json = r#""#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_empty() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path() {
-        let json = r#"{"path": "/path/to.xpi", "temporary": true}"#;
-        let data = AddonInstallParameters {
-            path: "/path/to.xpi".to_string(),
-            temporary: true,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_invalid_type() {
-        let json = r#"{"path": true, "temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_and_temporary_invalid_type() {
-        let json = r#"{"path": "/path/to.xpi", "temporary": "foo"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_only() {
-        let json = r#"{"path": "/path/to.xpi"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon() {
-        let json = r#"{"addon": "aGVsbG8=", "temporary": true}"#;
-        let data = serde_json::from_str::<AddonInstallParameters>(&json).unwrap();
-
-        assert_eq!(data.temporary, true);
-        let mut file = File::open(data.path).unwrap();
-        let mut contents = String::new();
-        file.read_to_string(&mut contents).unwrap();
-        assert_eq!(contents, "hello");
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon_invalid_type() {
-        let json = r#"{"addon": true, "temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon_and_temporary_invalid_type() {
-        let json = r#"{"addon": "aGVsbG8=", "temporary": "foo"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_addon_only() {
-        let json = r#"{"addon": "aGVsbG8="}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_install_parameters_with_temporary_only() {
-        let json = r#"{"temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_both_path_and_addon() {
-        let json = r#"{
-            "path":"/path/to.xpi",
-            "addon":"aGVsbG8=",
-            "temporary":true
-        }"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters_null() {
-        let json = r#""#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters_empty() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters() {
-        let json = r#"{"id": "foo"}"#;
-        let data = AddonUninstallParameters {
-            id: "foo".to_string(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters_id_invalid_type() {
-        let json = r#"{"id": true}"#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_content() {
-        let json = r#"{"context": "content"}"#;
-        let data = GeckoContextParameters {
-            context: GeckoContext::Content,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_chrome() {
-        let json = r#"{"context": "chrome"}"#;
-        let data = GeckoContextParameters {
-            context: GeckoContext::Chrome,
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_missing() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_null() {
-        let json = r#"{"context": null}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_invalid_value() {
-        let json = r#"{"context": "foo"}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute() {
-        let json = r#"{
-            "name": "foo",
-            "value": "bar"
-        }"#;
-
-        let data = XblLocatorParameters {
-            name: "foo".to_string(),
-            value: "bar".to_string(),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_name_missing() {
-        let json = r#"{
-            "value": "bar"
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_name_invalid_type() {
-        let json = r#"{
-            "name": null,
-            "value": "bar"
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_value_missing() {
-        let json = r#"{
-            "name": "foo",
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_value_invalid_type() {
-        let json = r#"{
-            "name": "foo",
-            "value": null
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-}
+mod tests {}