Bug 1194968: Support for Marionette protocol adjustments in Python client
authorAndreas Tolfsen <ato@mozilla.com>
Sat, 15 Aug 2015 15:46:31 +0100
changeset 258360 92d63c478838c60bcbc56b94bea71a0ebaa7caba
parent 258359 f14a286c6dc7b3dd6b835c20a13a76484528ae08
child 258381 4cb5a8a692e516d4bfe0d55fe28f94b0aa0b467f
push id63895
push useratolfsen@mozilla.com
push dateWed, 19 Aug 2015 11:43:21 +0000
treeherdermozilla-inbound@92d63c478838 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1194968, 1153822
milestone43.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 1194968: Support for Marionette protocol adjustments in Python client Whilst it is not a goal for the Marionette protocol to be fully compatible with WebDriver due to the different transport mechanisms, bug 1153822 adjusts the Marionette protocol closer to what is prescribed in the WebDriver specification. This patch adds support for the new protocol (level or version 2) to the Python bindings with backwards compatibility for the earlier, existing protocol (1). r=jgriffin
testing/marionette/client/marionette/tests/unit/test_marionette.py
testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py
testing/marionette/client/marionette/tests/unit/test_with_using_context.py
testing/marionette/driver/marionette_driver/application_cache.py
testing/marionette/driver/marionette_driver/marionette.py
testing/marionette/transport/marionette_transport/transport.py
--- a/testing/marionette/client/marionette/tests/unit/test_marionette.py
+++ b/testing/marionette/client/marionette/tests/unit/test_marionette.py
@@ -1,17 +1,28 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+import itertools
+
 from marionette_driver import errors
-from marionette import marionette_test
+from marionette.marionette_test import MarionetteTestCase as TC
 
 
-class TestHandleError(marionette_test.MarionetteTestCase):
+class TestProtocol1Errors(TC):
+    def setUp(self):
+        TC.setUp(self)
+        self.op = self.marionette.protocol
+        self.marionette.protocol = 1
+
+    def tearDown(self):
+        self.marionette.protocol = self.op
+        TC.tearDown(self)
+
     def test_malformed_packet(self):
         for t in [{}, {"error": None}]:
             with self.assertRaisesRegexp(errors.MarionetteException, "Malformed packet"):
                 self.marionette._handle_error(t)
 
     def test_known_error_code(self):
         with self.assertRaises(errors.NoSuchElementException):
             self.marionette._handle_error(
@@ -24,8 +35,56 @@ class TestHandleError(marionette_test.Ma
 
     def test_unknown_error_code(self):
         with self.assertRaises(errors.MarionetteException):
             self.marionette._handle_error({"error": {"status": 123456}})
 
     def test_unknown_error_status(self):
         with self.assertRaises(errors.MarionetteException):
             self.marionette._handle_error({"error": {"status": "barbera"}})
+
+
+class TestProtocol2Errors(TC):
+    def setUp(self):
+        TC.setUp(self)
+        self.op = self.marionette.protocol
+        self.marionette.protocol = 2
+
+    def tearDown(self):
+        self.marionette.protocol = self.op
+        TC.tearDown(self)
+
+    def test_malformed_packet(self):
+        req = ["error", "message", "stacktrace"]
+        ps = []
+        for p in [p for i in range(0, len(req) + 1) for p in itertools.permutations(req, i)]:
+            ps.append(dict((x, None) for x in p))
+
+        for p in filter(lambda p: len(p) < 3, ps):
+            self.assertRaises(KeyError, self.marionette._handle_error, p)
+
+    def test_known_error_code(self):
+        with self.assertRaises(errors.NoSuchElementException):
+            self.marionette._handle_error(
+                {"error": errors.NoSuchElementException.code[0],
+                 "message": None,
+                 "stacktrace": None})
+
+    def test_known_error_status(self):
+        with self.assertRaises(errors.NoSuchElementException):
+            self.marionette._handle_error(
+                {"error": errors.NoSuchElementException.status,
+                 "message": None,
+                 "stacktrace": None})
+
+    def test_unknown_error_code(self):
+        with self.assertRaises(errors.MarionetteException):
+            self.marionette._handle_error(
+                {"error": 123456,
+                 "message": None,
+                 "stacktrace": None})
+
+    def test_unknown_error_status(self):
+        with self.assertRaises(errors.MarionetteException):
+            self.marionette._handle_error(
+                {"error": "barbera",
+                 "message": None,
+                 "stacktrace": None})
--- a/testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py
+++ b/testing/marionette/client/marionette/tests/unit/test_teardown_context_preserved.py
@@ -10,12 +10,12 @@ class TestTearDownContext(MarionetteTest
         MarionetteTestCase.setUp(self)
         self.marionette.set_context(self.marionette.CONTEXT_CHROME)
 
     def tearDown(self):
         self.assertEqual(self.get_context(), self.marionette.CONTEXT_CHROME)
         MarionetteTestCase.tearDown(self)
 
     def get_context(self):
-        return self.marionette._send_message('getContext', 'value')
+        return self.marionette._send_message("getContext", key="value")
 
     def test_skipped_teardown_ok(self):
         raise SkipTest("This should leave our teardown method in chrome context")
--- a/testing/marionette/client/marionette/tests/unit/test_with_using_context.py
+++ b/testing/marionette/client/marionette/tests/unit/test_with_using_context.py
@@ -9,23 +9,23 @@ from marionette_driver.errors import Mar
 class TestSetContext(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
 
         # shortcuts to improve readability of these tests
         self.chrome = self.marionette.CONTEXT_CHROME
         self.content = self.marionette.CONTEXT_CONTENT
 
-        test_url = self.marionette.absolute_url('empty.html')
+        test_url = self.marionette.absolute_url("empty.html")
         self.marionette.navigate(test_url)
         self.marionette.set_context(self.content)
         self.assertEquals(self.get_context(), self.content)
 
     def get_context(self):
-        return self.marionette._send_message('getContext', 'value')
+        return self.marionette._send_message("getContext", key="value")
 
     def test_set_different_context_using_with_block(self):
         with self.marionette.using_context(self.chrome):
             self.assertEquals(self.get_context(), self.chrome)
         self.assertEquals(self.get_context(), self.content)
 
     def test_set_same_context_using_with_block(self):
         with self.marionette.using_context(self.content):
--- a/testing/marionette/driver/marionette_driver/application_cache.py
+++ b/testing/marionette/driver/marionette_driver/application_cache.py
@@ -1,31 +1,29 @@
-"""
-Copyright 2011 Software Freedom Conservancy.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
+# Copyright 2015 Mozilla Foundation
+# Copyright 2011 Software Freedom Conservancy.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 class ApplicationCache(object):
-
     UNCACHED = 0
     IDLE = 1
     CHECKING = 2
     DOWNLOADING = 3
     UPDATE_READY = 4
     OBSOLETE = 5
 
     def __init__(self, driver):
         self.driver = driver
 
     @property
     def status(self):
-        return self.driver._send_message('getAppCacheStatus', 'value')
+        return self.driver._send_message("getAppCacheStatus", key="value")
--- a/testing/marionette/driver/marionette_driver/marionette.py
+++ b/testing/marionette/driver/marionette_driver/marionette.py
@@ -35,159 +35,153 @@ class HTMLElement(object):
 
     def __str__(self):
         return self.id
 
     def __eq__(self, other_element):
         return self.id == other_element.id
 
     def find_element(self, method, target):
-        '''
-        Returns an HTMLElement instance that matches the specified method and target, relative to the current element.
+        """Returns an ``HTMLElement`` instance that matches the specified
+        method and target, relative to the current element.
 
-        For more details on this function, see the find_element method in the
-        Marionette class.
-        '''
+        For more details on this function, see the `find_element` method
+        in the Marionette class.
+        """
         return self.marionette.find_element(method, target, self.id)
 
     def find_elements(self, method, target):
-        '''
-        Returns a list of all HTMLElement instances that match the specified method and target in the current context.
+        """Returns a list of all ``HTMLElement`` instances that match the
+        specified method and target in the current context.
 
-        For more details on this function, see the find_elements method in the
-        Marionette class.
-        '''
+        For more details on this function, see the find_elements method
+        in the Marionette class.
+        """
         return self.marionette.find_elements(method, target, self.id)
 
     def get_attribute(self, attribute):
-        '''
-        Returns the requested attribute (or None, if no attribute is set).
+        """Returns the requested attribute, or None if no attribute
+        is set.
 
         :param attribute: The name of the attribute.
-        '''
-        return self.marionette._send_message('getElementAttribute', 'value', id=self.id, name=attribute)
+        """
+        body = {"id": self.id, "name": attribute}
+        return self.marionette._send_message("getElementAttribute", body, key="value")
 
     def click(self):
-        return self.marionette._send_message('clickElement', 'ok', id=self.id)
+        self.marionette._send_message("clickElement", {"id": self.id})
 
     def tap(self, x=None, y=None):
-        '''
-        Simulates a set of tap events on the element.
+        """Simulates a set of tap events on the element.
 
-        :param x: X-coordinate of tap event. If not given, default to the
-         center of the element.
-        :param y: Y-coordinate of tap event. If not given, default to the
-         center of the element.
-        '''
-        return self.marionette._send_message('singleTap', 'ok', id=self.id, x=x, y=y)
+        :param x: X coordinate of tap event.  If not given, default to
+            the centre of the element.
+        :param y: Y coordinate of tap event. If not given, default to
+            the centre of the element.
+        """
+        body = {"id": self.id, "x": x, "y": y}
+        self.marionette._send_message("singleTap", body)
 
     @property
     def text(self):
-        '''
-        Returns the visible text of the element, and its child elements.
-        '''
-        return self.marionette._send_message('getElementText', 'value', id=self.id)
+        """Returns the visible text of the element, and its child elements."""
+        body = {"id": self.id}
+        return self.marionette._send_message("getElementText", body, key="value")
 
     def send_keys(self, *string):
-        '''
-        Sends the string via synthesized keypresses to the element.
-        '''
+        """Sends the string via synthesized keypresses to the element."""
         keys = Marionette.convert_keys(*string)
-        return self.marionette._send_message('sendKeysToElement', 'ok', id=self.id, value=keys)
+        body = {"id": self.id, "value": keys}
+        self.marionette._send_message("sendKeysToElement", body)
 
     def clear(self):
-        '''
-        Clears the input of the element.
-        '''
-        return self.marionette._send_message('clearElement', 'ok', id=self.id)
+        """Clears the input of the element."""
+        self.marionette._send_message("clearElement", {"id": self.id})
 
     def is_selected(self):
-        '''
-        Returns True if the element is selected.
-        '''
-        return self.marionette._send_message('isElementSelected', 'value', id=self.id)
+        """Returns True if the element is selected."""
+        body = {"id": self.id}
+        return self.marionette._send_message("isElementSelected", body, key="value")
 
     def is_enabled(self):
-        '''
-        This command will return False if all the following criteria are met otherwise return True:
+        """This command will return False if all the following criteria
+        are met otherwise return True:
 
         * A form control is disabled.
-        * A HtmlElement has a disabled boolean attribute.
-
-        '''
-        return self.marionette._send_message('isElementEnabled', 'value', id=self.id)
+        * A ``HTMLElement`` has a disabled boolean attribute.
+        """
+        body = {"id": self.id}
+        return self.marionette._send_message("isElementEnabled", body, key="value")
 
     def is_displayed(self):
-        '''
-        Returns True if the element is displayed.
-        '''
-        return self.marionette._send_message('isElementDisplayed', 'value', id=self.id)
+        """Returns True if the element is displayed, False otherwise."""
+        body = {"id": self.id}
+        return self.marionette._send_message("isElementDisplayed", body, key="value")
 
     @property
     def size(self):
         """A dictionary with the size of the element."""
         warnings.warn("The size property has been deprecated and will be removed in a future version. \
             Please use HTMLElement#rect", DeprecationWarning)
         rect = self.rect
         return {"width": rect["width"], "height": rect["height"]}
 
     @property
     def tag_name(self):
-        '''
-        The tag name of the element.
-        '''
-        return self.marionette._send_message('getElementTagName', 'value', id=self.id)
+        """The tag name of the element."""
+        body = {"id": self.id}
+        return self.marionette._send_message("getElementTagName", body, key="value")
 
     @property
     def location(self):
         """Get an element's location on the page.
 
         The returned point will contain the x and y coordinates of the
         top left-hand corner of the given element.  The point (0,0)
         refers to the upper-left corner of the document.
 
         :returns: a dictionary containing x and y as entries
-
         """
         warnings.warn("The location property has been deprecated and will be removed in a future version. \
             Please use HTMLElement#rect", DeprecationWarning)
         rect = self.rect
         return {"x": rect["x"], "y": rect["y"]}
 
     @property
     def rect(self):
         """Gets the element's bounding rectangle.
-        
+
         This will return a dictionary with the following:
 
           * x and y represent the top left coordinates of the ``HTMLElement``
             relative to top left corner of the document.
           * height and the width will contain the height and the width
             of the DOMRect of the ``HTMLElement``.
         """
-        return self.marionette._send_message("getElementRect", "value", id=self.id)
+        body = {"id": self.id}
+        return self.marionette._send_message(
+            "getElementRect", body, key="value" if self.marionette.protocol == 1 else None)
 
     def value_of_css_property(self, property_name):
-        '''
-        Gets the value of the specified CSS property name.
+        """Gets the value of the specified CSS property name.
 
         :param property_name: Property name to get the value of.
-        '''
-        return self.marionette._send_message('getElementValueOfCssProperty', 'value',
-                                             id=self.id,
-                                             propertyName=property_name)
+        """
+        body = {"id": self.id, "propertyName": property_name}
+        return self.marionette._send_message(
+            "getElementValueOfCssProperty", body, key="value")
+
 
 class MouseButton(object):
-    '''
-    Enum-like class for mouse button constants
-    '''
+    """Enum-like class for mouse button constants."""
     LEFT = 0
     MIDDLE = 1
     RIGHT = 2
 
+
 class Actions(object):
     '''
     An Action object represents a set of actions that are executed in a particular order.
 
     All action methods (press, etc.) return the Actions object itself, to make
     it easy to create a chain of events.
 
     Example usage:
@@ -441,20 +435,20 @@ class Actions(object):
         Perform a "keyUp" action for the given key code. Modifier keys are
         respected by the server for the course of an action chain.
         :param key_up: The key to release as a result of this action.
         """
         self.action_chain.append(['keyUp', key_code])
         return self
 
     def perform(self):
-        '''
-        Sends the action chain built so far to the server side for execution and clears the current chain of actions.
-        '''
-        self.current_id = self.marionette._send_message('actionChain', 'value', chain=self.action_chain, nextId=self.current_id)
+        """Sends the action chain built so far to the server side for
+        execution and clears the current chain of actions."""
+        body = {"chain": self.action_chain, "nextId": self.current_id}
+        self.current_id = self.marionette._send_message("actionChain", body, key="value")
         self.action_chain = []
         return self
 
 class MultiActions(object):
     '''
     A MultiActions object represents a sequence of actions that may be
     performed at the same time. Its intent is to allow the simulation
     of multi-touch gestures.
@@ -486,62 +480,55 @@ class MultiActions(object):
         :param action: An Actions object.
         '''
         self.multi_actions.append(action.action_chain)
         if len(action.action_chain) > self.max_length:
           self.max_length = len(action.action_chain)
         return self
 
     def perform(self):
-        '''
-        Perform all the actions added to this object.
-        '''
-        return self.marionette._send_message('multiAction', 'ok', value=self.multi_actions, max_length=self.max_length)
+        """Perform all the actions added to this object."""
+        body = {"value": self.multi_actions, "max_length": self.max_length}
+        self.marionette._send_message("multiAction", body)
+
 
 class Alert(object):
-    '''
-    A class for interacting with alerts.
+    """A class for interacting with alerts.
 
     ::
 
-      Alert(marionette).accept()
-      Alert(merionette).dismiss()
-    '''
+        Alert(marionette).accept()
+        Alert(merionette).dismiss()
+    """
 
     def __init__(self, marionette):
         self.marionette = marionette
 
     def accept(self):
-        """Accept a currently displayed modal dialog.
-        """
-        self.marionette._send_message('acceptDialog', 'ok')
+        """Accept a currently displayed modal dialog."""
+        self.marionette._send_message("acceptDialog")
 
     def dismiss(self):
-        """Dismiss a currently displayed modal dialog.
-        """
-        self.marionette._send_message('dismissDialog', 'ok')
+        """Dismiss a currently displayed modal dialog."""
+        self.marionette._send_message("dismissDialog")
 
     @property
     def text(self):
-        """Return the currently displayed text in a tab modal.
-        """
-        return self.marionette._send_message('getTextFromDialog', 'value')
+        """Return the currently displayed text in a tab modal."""
+        return self.marionette._send_message("getTextFromDialog", key="value")
 
     def send_keys(self, *string):
         """Send keys to the currently displayed text input area in an open
-        tab modal dialog.
-        """
-        keys = Marionette.convert_keys(*string)
-        self.marionette._send_message('sendKeysToDialog', 'ok', value=keys)
+        tab modal dialog."""
+        body = {"value": Marionette.convert_keys(*string)}
+        self.marionette._send_message("sendKeysToDialog", body)
 
 
 class Marionette(object):
-    """
-    Represents a Marionette connection to a browser or device.
-    """
+    """Represents a Marionette connection to a browser or device."""
 
     CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
     CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
     TIMEOUT_SEARCH = 'implicit'
     TIMEOUT_SCRIPT = 'script'
     TIMEOUT_PAGE = 'page load'
 
     def __init__(self, host='localhost', port=2828, app=None, app_args=None, bin=None,
@@ -675,95 +662,99 @@ class Marionette(object):
             s.close()
 
     def wait_for_port(self, timeout=60):
         return MarionetteTransport.wait_for_port(self.host,
                                                  self.port,
                                                  timeout=timeout)
 
     @do_crash_check
-    def _send_message(self, command, response_key="ok", **kwargs):
+    def _send_message(self, command, body=None, key=None):
         if not self.session_id and command != "newSession":
             raise errors.MarionetteException("Please start a session")
 
         message = {"name": command}
-        if self.session_id:
-            message["sessionId"] = self.session_id
-        if kwargs:
-            message["parameters"] = kwargs
+        if body:
+            message["parameters"] = body
+
+        packet = json.dumps(message)
 
         try:
-            response = self.client.send(message)
+            resp = self.client.send(packet)
         except IOError:
             if self.instance and not hasattr(self.instance, 'detached'):
                 # If we've launched the binary we've connected to, wait
                 # for it to shut down.
                 returncode = self.instance.runner.wait()
                 raise IOError("process died with returncode %d" % returncode)
             raise
         except socket.timeout:
             self.session = None
             self.window = None
             self.client.close()
             raise errors.TimeoutException("Connection timed out")
 
         # Process any emulator commands that are sent from a script
-        # while it's executing.
-        while True:
-            if response.get("emulator_cmd"):
-                response = self._handle_emulator_cmd(response)
-                continue;
-
-            if response.get("emulator_shell"):
-                response = self._handle_emulator_shell(response)
-                continue;
+        # while it's executing
+        if isinstance(resp, dict) and any (k in resp for k in ("emulator_cmd", "emulator_shell")):
+            while True:
+                id = resp.get("id")
+                cmd = resp.get("emulator_cmd")
+                shell = resp.get("emulator_shell")
+                if cmd:
+                    resp = self._emulator_cmd(id, cmd)
+                    continue
+                if shell:
+                    resp = self._emulator_shell(id, shell)
+                    continue
+                break
 
-            break;
-        if not self.session_id:
-            self.session_id = response.get("sessionId", None)
+        if "error" in resp:
+            self._handle_error(resp)
 
-        if response_key in response:
-            return response[response_key]
-        self._handle_error(response)
+        if key is not None:
+            resp = resp[key]
+        return resp
 
-    def _handle_emulator_cmd(self, response):
-        cmd = response.get("emulator_cmd")
-        if not cmd or not self.emulator:
+    def _emulator_cmd(self, id, cmd):
+        if not self.emulator:
             raise errors.MarionetteException(
                 "No emulator in this test to run command against")
-        cmd = cmd.encode("ascii")
-        result = self.emulator._run_telnet(cmd)
-        return self.client.send({"name": "emulatorCmdResult",
-                                 "id": response.get("id"),
-                                 "result": result})
+        payload = cmd.encode("ascii")
+        result = self.emulator._run_telnet(payload)
+        return self._send_emulator_result(id, result)
 
-    def _handle_emulator_shell(self, response):
-        args = response.get("emulator_shell")
-        if not isinstance(args, list) or not self.emulator:
+    def _emulator_shell(self, id, args):
+        if not self.emulator:
             raise errors.MarionetteException(
                 "No emulator in this test to run shell command against")
         buf = StringIO.StringIO()
         self.emulator.dm.shell(args, buf)
         result = str(buf.getvalue()[0:-1]).rstrip().splitlines()
         buf.close()
+        return self._send_emulator_result(id, result)
+
+    def _send_emulator_result(self, id, result):
         return self.client.send({"name": "emulatorCmdResult",
-                                 "id": response.get("id"),
+                                 "id": id,
                                  "result": result})
 
-    def _handle_error(self, response):
-        if "error" not in response or not isinstance(response["error"], dict):
-            raise errors.MarionetteException(
-                "Malformed packet, expected key 'error' to be a dict: %s" % response)
-
-        error = response["error"]
-        status = error.get("status")
-        message = error.get("message")
-        stacktrace = error.get("stacktrace")
-
-        raise errors.lookup(status)(message, stacktrace=stacktrace)
+    def _handle_error(self, resp):
+        if self.protocol == 1:
+            if "error" not in resp or not isinstance(resp["error"], dict):
+                raise errors.MarionetteException(
+                    "Malformed packet, expected key 'error' to be a dict: %s" % resp)
+            error = resp["error"].get("status")
+            message = resp["error"].get("message")
+            stacktrace = resp["error"].get("stacktrace")
+        else:
+            error = resp["error"]
+            message = resp["message"]
+            stacktrace = resp["stacktrace"]
+        raise errors.lookup(error)(message, stacktrace=stacktrace)
 
     def _reset_timeouts(self):
         if self.timeout is not None:
             self.timeouts(self.TIMEOUT_SEARCH, self.timeout)
             self.timeouts(self.TIMEOUT_SCRIPT, self.timeout)
             self.timeouts(self.TIMEOUT_PAGE, self.timeout)
         else:
             self.timeouts(self.TIMEOUT_PAGE, 30000)
@@ -979,17 +970,17 @@ class Marionette(object):
             if clean:
                 raise ValueError
             # Values here correspond to constants in nsIAppStartup.
             # See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup
             restart_flags = [
                 "eForceQuit",
                 "eRestart",
             ]
-            self._send_message('quitApplication', flags=restart_flags)
+            self._send_message("quitApplication", {"flags": restart_flags})
             self.client.close()
             # The instance is restarting itself; we will no longer be able to
             # track it by pid, so mark it as 'detached'.
             self.instance.detached = True
         else:
             self.delete_session()
             self.instance.restart(clean=clean)
         assert(self.wait_for_port()), "Timed out waiting for port!"
@@ -1016,261 +1007,247 @@ class Marionette(object):
             passed in then one will be generated by the marionette server.
 
         :returns: A dict of the capabilities offered."""
         if self.instance:
             returncode = self.instance.runner.process_handler.proc.returncode
             if returncode is not None:
                 # We're managing a binary which has terminated, so restart it.
                 self.instance.restart()
+
+        self.protocol, _ = self.client.connect()
         self.wait_for_port(timeout=timeout)
-        self.session = self._send_message('newSession', 'value', capabilities=desired_capabilities, sessionId=session_id)
-        self.b2g = 'b2g' in self.session
+
+        body = {"capabilities": desired_capabilities, "sessionId": session_id}
+        resp = self._send_message("newSession", body)
+
+        self.session_id = resp["sessionId"]
+        self.session = resp["value"] if self.protocol == 1 else resp["capabilities"]
+        self.b2g = "b2g" in self.session
+
         return self.session
 
     @property
     def test_name(self):
         return self._test_name
 
     @test_name.setter
     def test_name(self, test_name):
-        if self._send_message('setTestName', 'ok', value=test_name):
+        if self._send_message("setTestName", {"value": test_name}):
             self._test_name = test_name
 
     def delete_session(self):
         """Close the current session and disconnect from the server."""
-        response = self._send_message('deleteSession', 'ok')
+        self._send_message("deleteSession")
         self.session_id = None
         self.session = None
         self.window = None
         self.client.close()
-        return response
 
     @property
     def session_capabilities(self):
         '''
         A JSON dictionary representing the capabilities of the current session.
         '''
         return self.session
 
     def set_script_timeout(self, timeout):
-        '''
-        Sets the maximum number of ms that an asynchronous script is allowed to run.
+        """Sets the maximum number of ms that an asynchronous script is
+        allowed to run.
 
-        If a script does not return in the specified amount of time, a
-        ScriptTimeoutException is raised.
+        If a script does not return in the specified amount of time,
+        a ScriptTimeoutException is raised.
 
         :param timeout: The maximum number of milliseconds an asynchronous
-         script can run without causing an ScriptTimeoutException to be raised
-        '''
-        response = self._send_message('setScriptTimeout', 'ok', ms=timeout)
-        return response
+            script can run without causing an ScriptTimeoutException to
+            be raised
+        """
+        self._send_message("setScriptTimeout", {"ms": timeout})
 
     def set_search_timeout(self, timeout):
-        '''
-        Sets a timeout for the find methods.
+        """Sets a timeout for the find methods.
 
-        When searching for an element using either
-        :class:`Marionette.find_element` or :class:`Marionette.find_elements`,
-        the method will continue trying to locate the element for up to timeout
-        ms. This can be useful if, for example, the element you're looking for
-        might not exist immediately, because it belongs to a page which is
+        When searching for an element using
+        either :class:`Marionette.find_element` or
+        :class:`Marionette.find_elements`, the method will continue
+        trying to locate the element for up to timeout ms. This can be
+        useful if, for example, the element you're looking for might
+        not exist immediately, because it belongs to a page which is
         currently being loaded.
 
         :param timeout: Timeout in milliseconds.
-        '''
-        response = self._send_message('setSearchTimeout', 'ok', ms=timeout)
-        return response
+        """
+        self._send_message("setSearchTimeout", {"ms": timeout})
 
     @property
     def current_window_handle(self):
         """Get the current window's handle.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
 
         :returns: unique window handle
         :rtype: string
-
         """
-        self.window = self._send_message("getWindowHandle", "value")
+        self.window = self._send_message("getWindowHandle", key="value")
         return self.window
 
     @property
     def current_chrome_window_handle(self):
         """Get the current chrome window's handle. Corresponds to
         a chrome window that may itself contain tabs identified by
         window_handles.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
 
         :returns: unique window handle
         :rtype: string
-
         """
-        self.chrome_window = self._send_message("getCurrentChromeWindowHandle", "value")
+        self.chrome_window = self._send_message(
+            "getCurrentChromeWindowHandle", key="value")
         return self.chrome_window
 
-
     def get_window_position(self):
-        """Get the current window's position
-           Return a dictionary with the keys x and y
-           :returns: a dictionary with x and y
+        """Get the current window's position.
+
+        :returns: a dictionary with x and y
         """
-        return self._send_message("getWindowPosition", "value")
+        return self._send_message(
+            "getWindowPosition", key="value" if self.protocol == 1 else None)
 
     def set_window_position(self, x, y):
+        """Set the position of the current window
+
+        :param x: x coordinate for the top left of the window
+        :param y: y coordinate for the top left of the window
         """
-           Set the position of the current window
-            :param x: x coordinate for the top left of the window
-            :param y: y coordinate for the top left of the window
-        """
-        response = self._send_message("setWindowPosition", "ok", x=x, y=y)
-        return response
+        self._send_message("setWindowPosition", {"x": x, "y": y})
 
     @property
     def title(self):
-        '''
-        Current title of the active window.
-        '''
-        response = self._send_message('getTitle', 'value')
-        return response
+        """Current title of the active window."""
+        return self._send_message("getTitle", key="value")
 
     @property
     def window_handles(self):
         """Get list of windows in the current context.
 
         If called in the content context it will return a list of
         references to all available browser windows.  Called in the
         chrome context, it will list all available windows, not just
         browser windows (e.g. not just navigator.browser).
 
         Each window handle is assigned by the server, and the list of
         strings returned does not have a guaranteed ordering.
 
         :returns: unordered list of unique window handles as strings
-
         """
-
-        response = self._send_message("getWindowHandles", "value")
-        return response
+        return self._send_message(
+            "getWindowHandles", key="value" if self.protocol == 1 else None)
 
     @property
     def chrome_window_handles(self):
         """Get a list of currently open chrome windows.
 
         Each window handle is assigned by the server, and the list of
         strings returned does not have a guaranteed ordering.
 
         :returns: unordered list of unique window handles as strings
-
         """
-
-        response = self._send_message("getChromeWindowHandles", "value")
-        return response
-
+        return self._send_message(
+            "getChromeWindowHandles", key="value" if self.protocol == 1 else None)
 
     @property
     def page_source(self):
-        '''
-        A string representation of the DOM.
-        '''
-        response = self._send_message('getPageSource', 'value')
-        return response
+        """A string representation of the DOM."""
+        return self._send_message("getPageSource", key="value")
+
     def close(self):
         """Close the current window, ending the session if it's the last
         window currently open.
 
         On B2G this method is a noop and will return immediately.
-
         """
-
-        response = self._send_message("close", "ok")
-        return response
+        self._send_message("close")
 
     def close_chrome_window(self):
         """Close the currently selected chrome window, ending the session
         if it's the last window open.
 
         On B2G this method is a noop and will return immediately.
-
         """
-
-        response = self._send_message("closeChromeWindow", "ok")
-        return response
+        self._send_message("closeChromeWindow")
 
     def set_context(self, context):
-        '''
-        Sets the context that Marionette commands are running in.
+        """Sets the context that Marionette commands are running in.
 
         :param context: Context, may be one of the class properties
-         `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
+            `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
-          marionette.set_context(marionette.CONTEXT_CHROME)
-        '''
+            marionette.set_context(marionette.CONTEXT_CHROME)
+        """
         assert(context == self.CONTEXT_CHROME or context == self.CONTEXT_CONTENT)
-        return self._send_message('setContext', 'ok', value=context)
+        if context not in [self.CONTEXT_CHROME, self.CONTEXT_CONTENT]:
+            raise ValueError("Unknown context: %s" % context)
+        self._send_message("setContext", {"value": context})
 
     @contextmanager
     def using_context(self, context):
-        '''
-        Sets the context that Marionette commands are running in using
+        """Sets the context that Marionette commands are running in using
         a `with` statement. The state of the context on the server is
         saved before entering the block, and restored upon exiting it.
 
         :param context: Context, may be one of the class properties
-         `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
+            `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
-          with marionette.using_context(marionette.CONTEXT_CHROME):
-              # chrome scope
-              ... do stuff ...
-        '''
-        scope = self._send_message('getContext', 'value')
+            with marionette.using_context(marionette.CONTEXT_CHROME):
+                # chrome scope
+                ... do stuff ...
+        """
+        scope = self._send_message("getContext", key="value")
         self.set_context(context)
         try:
             yield
         finally:
             self.set_context(scope)
 
     def switch_to_alert(self):
-        '''
-        Returns an Alert object for interacting with a currently displayed alert.
+        """Returns an Alert object for interacting with a currently
+        displayed alert.
 
         ::
 
             alert = self.marionette.switch_to_alert()
             text = alert.text
             alert.accept()
-        '''
+        """
         return Alert(self)
 
     def switch_to_window(self, window_id):
-        '''
-        Switch to the specified window; subsequent commands will be directed at the new window.
+        """Switch to the specified window; subsequent commands will be
+        directed at the new window.
 
         :param window_id: The id or name of the window to switch to.
-        '''
-        response = self._send_message('switchToWindow', 'ok', name=window_id)
+        """
+        self._send_message("switchToWindow", {"name": window_id})
         self.window = window_id
-        return response
 
     def get_active_frame(self):
-        '''
-        Returns an HTMLElement representing the frame Marionette is currently acting on.
-        '''
-        response = self._send_message('getActiveFrame', 'value')
-        if response:
-            return HTMLElement(self, response)
+        """Returns an HTMLElement representing the frame Marionette is
+        currently acting on."""
+        element = self._send_message("getActiveFrame", key="value")
+        if element:
+            return HTMLElement(self, element)
         return None
 
     def switch_to_default_content(self):
         """Switch the current context to page's default content."""
         return self.switch_to_frame()
 
     def switch_to_frame(self, frame=None, focus=True):
         """Switch the current context to the specified frame. Subsequent
@@ -1280,49 +1257,45 @@ class Marionette(object):
         :param frame: A reference to the frame to switch to.  This can
             be an ``HTMLElement``, an integer index, string name, or an
             ID attribute.  If you call ``switch_to_frame`` without an
             argument, it will switch to the top-level frame.
 
         :param focus: A boolean value which determins whether to focus
             the frame that we just switched to.
         """
-        kwargs = {"focus": focus}
+        body = {"focus": focus}
         if isinstance(frame, HTMLElement):
-            kwargs["element"] = frame.id
+            body["element"] = frame.id
         elif frame is not None:
-            kwargs["id"] = frame
-        return self._send_message("switchToFrame", "ok", **kwargs)
+            body["id"] = frame
+        self._send_message("switchToFrame", body)
 
     def get_url(self):
         """Get a string representing the current URL.
 
         On Desktop this returns a string representation of the URL of
         the current top level browsing context.  This is equivalent to
         document.location.href.
 
         When in the context of the chrome, this returns the canonical
         URL of the current resource.
 
         :returns: string representation of URL
-
         """
-
-        response = self._send_message("getCurrentUrl", "value")
-        return response
+        return self._send_message("getCurrentUrl", key="value")
 
     def get_window_type(self):
-        '''
-        Gets the windowtype attribute of the window Marionette is currently acting on.
+        """Gets the windowtype attribute of the window Marionette is
+        currently acting on.
 
         This command only makes sense in a chrome context. You might use this
         method to distinguish a browser window from an editor window.
-        '''
-        response = self._send_message('getWindowType', 'value')
-        return response
+        """
+        return self._send_message("getWindowType", key="value")
 
     def navigate(self, url):
         """Navigate to given `url`.
 
         Navigates the current top-level browsing context's content
         frame to the given URL and waits for the document to load or
         the session's page timeout duration to elapse before returning.
 
@@ -1337,440 +1310,446 @@ class Marionette(object):
         `window` triggers and `document.readState` is "complete".
 
         In chrome context it will change the current `window`'s location
         to the supplied URL and wait until `document.readState` equals
         "complete" or the page timeout duration has elapsed.
 
         :param url: The URL to navigate to.
         """
-        return self._send_message("get", "ok", url=url)
+        self._send_message("get", {"url": url})
 
     def timeouts(self, timeout_type, ms):
-        """An interface for managing timeout behaviour of a Marionette instance.
+        """An interface for managing timeout behaviour of a Marionette
+        instance.
+
+        Setting timeouts specifies the type and amount of time the
+        Marionette instance should wait during requests.
 
-        Setting timeouts specifies the type and amount of time the Marionette instance should wait during requests.
+        There are three types of timeouts that can be set: implicit,
+        script and page load.
 
-        There are three types of timeouts that can be set: implicit, script and page load.
+        * An implicit  timeout specifies the amount of time a Marionette
+        instance should wait when searching for elements. Here, marionette
+        polls a page until an element is found or the timeout expires,
+        whichever occurs first. When searching for multiple elements,
+        the driver should poll the page until at least one element is
+        found or the timeout expires, at which point it should return
+        an empty list.
 
-        * An implicit  timeout specifies the amount of time a Marionette instance should wait when searching for elements. Here, marionette polls a page until an element is found or the timeout expires, whichever occurs first. When searching for multiple elements, the driver should poll the page until at least one element is found or the timeout expires, at which point it should return an empty list.
-        * A script timeout specifies the amount of time the Marionette instance should wait after calling executeAsyncScript for the callback to have executed before returning a timeout response.
-        * A page load timeout specifies the amount of time the Marionette instance should wait for a page load operation to complete. If this limit is exceeded, the Marionette instance will return a "timeout" response status.
+        * A script timeout specifies the amount of time the Marionette
+        instance should wait after calling executeAsyncScript for the
+        callback to have executed before returning a timeout response.
+
+        * A page load timeout specifies the amount of time the Marionette
+        instance should wait for a page load operation to complete. If
+        this limit is exceeded, the Marionette instance will return a
+        "timeout" response status.
 
-        :param timeout_type: A string value specifying the timeout type. This must be one of three types: 'implicit', 'script' or 'page load'
-        :param ms: A number value specifying the timeout length in milliseconds (ms)
+        :param timeout_type: A string value specifying the timeout
+            type. This must be one of three types: 'implicit', 'script'
+            or 'page load'
+        :param ms: A number value specifying the timeout length in
+            milliseconds (ms)
         """
-        assert(timeout_type == self.TIMEOUT_SEARCH or timeout_type == self.TIMEOUT_SCRIPT or timeout_type == self.TIMEOUT_PAGE)
-        response = self._send_message('timeouts', 'ok', type=timeout_type, ms=ms)
-        return response
+        if timeout_type not in [self.TIMEOUT_SEARCH, self.TIMEOUT_SCRIPT, self.TIMEOUT_PAGE]:
+            raise ValueError("Unknown timeout type: %s" % timeout_type)
+        body = {"type": timeout_type, "ms": ms}
+        self._send_message("timeouts", body)
 
     def go_back(self):
-        '''
-        Causes the browser to perform a back navigation.
-        '''
-        response = self._send_message('goBack', 'ok')
-        return response
+        """Causes the browser to perform a back navigation."""
+        self._send_message("goBack")
 
     def go_forward(self):
-        '''
-        Causes the browser to perform a forward navigation.
-        '''
-        response = self._send_message('goForward', 'ok')
-        return response
+        """Causes the browser to perform a forward navigation."""
+        self._send_message("goForward")
 
     def refresh(self):
-        '''
-        Causes the browser to perform to refresh the current page.
-        '''
-        response = self._send_message('refresh', 'ok')
-        return response
+        """Causes the browser to perform to refresh the current page."""
+        self._send_message("refresh")
 
     def wrapArguments(self, args):
         if isinstance(args, list):
             wrapped = []
             for arg in args:
                 wrapped.append(self.wrapArguments(arg))
         elif isinstance(args, dict):
             wrapped = {}
             for arg in args:
                 wrapped[arg] = self.wrapArguments(args[arg])
         elif type(args) == HTMLElement:
-            wrapped = {'element-6066-11e4-a52e-4f735466cecf': args.id,
-                       'ELEMENT': args.id }
+            wrapped = {"element-6066-11e4-a52e-4f735466cecf": args.id,
+                       "ELEMENT": args.id}
         elif (isinstance(args, bool) or isinstance(args, basestring) or
               isinstance(args, int) or isinstance(args, float) or args is None):
             wrapped = args
-
         return wrapped
 
     def unwrapValue(self, value):
         if isinstance(value, list):
             unwrapped = []
             for item in value:
                 unwrapped.append(self.unwrapValue(item))
         elif isinstance(value, dict):
             unwrapped = {}
             for key in value:
-                if key == 'element-6066-11e4-a52e-4f735466cecf':
+                if key == "element-6066-11e4-a52e-4f735466cecf":
                     unwrapped = HTMLElement(self, value[key])
                     break
-                elif key == 'ELEMENT':
+                elif key == "ELEMENT":
                     unwrapped = HTMLElement(self, value[key])
                     break
                 else:
                     unwrapped[key] = self.unwrapValue(value[key])
         else:
             unwrapped = value
-
         return unwrapped
 
     def execute_js_script(self, script, script_args=None, async=True,
                           new_sandbox=True, script_timeout=None,
                           inactivity_timeout=None, filename=None,
                           sandbox='default'):
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
-        response = self._send_message('executeJSScript',
-                                      'value',
-                                      script=script,
-                                      args=args,
-                                      async=async,
-                                      newSandbox=new_sandbox,
-                                      scriptTimeout=script_timeout,
-                                      inactivityTimeout=inactivity_timeout,
-                                      filename=filename,
-                                      line=None)
-        return self.unwrapValue(response)
+        body = {"script": script,
+                "args": args,
+                "async": async,
+                "newSandbox": new_sandbox,
+                "scriptTimeout": script_timeout,
+                "inactivityTimeout": inactivity_timeout,
+                "filename": filename,
+                "line": None}
+        rv = self._send_message("executeJSScript", body, key="value")
+        return self.unwrapValue(rv)
 
     def execute_script(self, script, script_args=None, new_sandbox=True,
-                       sandbox='default', script_timeout=None):
-        '''
-        Executes a synchronous JavaScript script, and returns the result (or None if the script does return a value).
+                       sandbox="default", script_timeout=None):
+        """Executes a synchronous JavaScript script, and returns the
+        result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
         set_context() call, or to the CONTEXT_CONTENT context if set_context()
         has not been called.
 
         :param script: A string containing the JavaScript to execute.
         :param script_args: A list of arguments to pass to the script.
-        :param sandbox: A tag referring to the sandbox you wish to use; if
-         you specify a new tag, a new sandbox will be created.  If you use the
-         special tag 'system', the sandbox will be created using the system
-         principal which has elevated privileges.
-        :param new_sandbox: If False, preserve global variables from the last
-         execute_*script call. This is True by default, in which case no
-         globals are preserved.
+        :param sandbox: A tag referring to the sandbox you wish to use;
+            if you specify a new tag, a new sandbox will be created.
+            If you use the special tag `system`, the sandbox will
+            be created using the system principal which has elevated
+            privileges.
+        :param new_sandbox: If False, preserve global variables from
+            the last execute_*script call. This is True by default, in which
+            case no globals are preserved.
 
         Simple usage example:
 
         ::
 
-          result = marionette.execute_script("return 1;")
-          assert result == 1
+            result = marionette.execute_script("return 1;")
+            assert result == 1
 
         You can use the `script_args` parameter to pass arguments to the
         script:
 
         ::
 
-          result = marionette.execute_script("return arguments[0] + arguments[1];",
-                                             script_args=[2, 3])
-          assert result == 5
-          some_element = marionette.find_element("id", "someElement")
-          sid = marionette.execute_script("return arguments[0].id;", script_args=[some_element])
-          assert some_element.get_attribute("id") == sid
+            result = marionette.execute_script("return arguments[0] + arguments[1];",
+                                               script_args=[2, 3])
+            assert result == 5
+            some_element = marionette.find_element("id", "someElement")
+            sid = marionette.execute_script("return arguments[0].id;", script_args=[some_element])
+            assert some_element.get_attribute("id") == sid
 
-        Scripts wishing to access non-standard properties of the window object must use
-        window.wrappedJSObject:
+        Scripts wishing to access non-standard properties of the window
+        object must use window.wrappedJSObject:
 
         ::
 
-          result = marionette.execute_script("""
-            window.wrappedJSObject.test1 = 'foo';
-            window.wrappedJSObject.test2 = 'bar';
-            return window.wrappedJSObject.test1 + window.wrappedJSObject.test2;
-            """)
-          assert result == "foobar"
+            result = marionette.execute_script('''
+              window.wrappedJSObject.test1 = "foo";
+              window.wrappedJSObject.test2 = "bar";
+              return window.wrappedJSObject.test1 + window.wrappedJSObject.test2;
+              ''')
+            assert result == "foobar"
 
         Global variables set by individual scripts do not persist between
-        script calls by default.  If you wish to persist data between script
-        calls, you can set new_sandbox to False on your next call, and add any
-        new variables to a new 'global' object like this:
+        script calls by default.  If you wish to persist data between
+        script calls, you can set new_sandbox to False on your next call,
+        and add any new variables to a new 'global' object like this:
 
         ::
 
-          marionette.execute_script("global.test1 = 'foo';")
-          result = self.marionette.execute_script("return global.test1;", new_sandbox=False)
-          assert result == 'foo'
+            marionette.execute_script("global.test1 = 'foo';")
+            result = self.marionette.execute_script("return global.test1;", new_sandbox=False)
+            assert result == "foo"
 
-        '''
+        """
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
         stack = traceback.extract_stack()
         frame = stack[-2:-1][0] # grab the second-to-last frame
-        response = self._send_message('executeScript',
-                                      'value',
-                                      script=script,
-                                      args=args,
-                                      newSandbox=new_sandbox,
-                                      sandbox=sandbox,
-                                      scriptTimeout=script_timeout,
-                                      line=int(frame[1]),
-                                      filename=os.path.basename(frame[0]))
-        return self.unwrapValue(response)
+        body = {"script": script,
+                "args": args,
+                "newSandbox": new_sandbox,
+                "sandbox": sandbox,
+                "scriptTimeout": script_timeout,
+                "line": int(frame[1]),
+                "filename": os.path.basename(frame[0])}
+        rv = self._send_message("executeScript", body, key="value")
+        return self.unwrapValue(rv)
 
     def execute_async_script(self, script, script_args=None, new_sandbox=True,
-                             sandbox='default', script_timeout=None,
+                             sandbox="default", script_timeout=None,
                              debug_script=False):
-        '''
-        Executes an asynchronous JavaScript script, and returns the result (or None if the script does return a value).
+        """Executes an asynchronous JavaScript script, and returns the
+        result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
-        set_context() call, or to the CONTEXT_CONTENT context if set_context()
-        has not been called.
+        set_context() call, or to the CONTEXT_CONTENT context if
+        set_context() has not been called.
 
         :param script: A string containing the JavaScript to execute.
         :param script_args: A list of arguments to pass to the script.
         :param sandbox: A tag referring to the sandbox you wish to use; if
-         you specify a new tag, a new sandbox will be created.  If you use the
-         special tag 'system', the sandbox will be created using the system
-         principal which has elevated privileges.
-        :param new_sandbox: If False, preserve global variables from the last
-         execute_*script call. This is True by default, in which case no
-         globals are preserved.
+            you specify a new tag, a new sandbox will be created.  If you
+            use the special tag `system`, the sandbox will be created
+            using the system principal which has elevated privileges.
+        :param new_sandbox: If False, preserve global variables from
+            the last execute_*script call. This is True by default,
+            in which case no globals are preserved.
         :param debug_script: Capture javascript exceptions when in
-         CONTEXT_CHROME context.
+            `CONTEXT_CHROME` context.
 
         Usage example:
 
         ::
 
-          marionette.set_script_timeout(10000) # set timeout period of 10 seconds
-          result = self.marionette.execute_async_script("""
-            // this script waits 5 seconds, and then returns the number 1
-            setTimeout(function() {
-              marionetteScriptFinished(1);
-            }, 5000);
-          """)
-          assert result == 1
-        '''
+            marionette.set_script_timeout(10000) # set timeout period of 10 seconds
+            result = self.marionette.execute_async_script('''
+              // this script waits 5 seconds, and then returns the number 1
+              setTimeout(function() {
+                marionetteScriptFinished(1);
+              }, 5000);
+            ''')
+            assert result == 1
+        """
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
         stack = traceback.extract_stack()
         frame = stack[-2:-1][0] # grab the second-to-last frame
-        response = self._send_message('executeAsyncScript',
-                                      'value',
-                                      script=script,
-                                      args=args,
-                                      newSandbox=new_sandbox,
-                                      sandbox=sandbox,
-                                      scriptTimeout=script_timeout,
-                                      line=int(frame[1]),
-                                      filename=os.path.basename(frame[0]),
-                                      debug_script=debug_script)
-        return self.unwrapValue(response)
+        body = {"script": script,
+                "args": args,
+                "newSandbox": new_sandbox,
+                "sandbox": sandbox,
+                "scriptTimeout": script_timeout,
+                "line": int(frame[1]),
+                "filename": os.path.basename(frame[0]),
+                "debug_script": debug_script}
+        rv = self._send_message("executeAsyncScript", body, key="value")
+        return self.unwrapValue(rv)
 
     def find_element(self, method, target, id=None):
-        '''
-        Returns an HTMLElement instances that matches the specified method and target in the current context.
+        """Returns an HTMLElement instances that matches the specified
+        method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
         element, such as click().  If no element is immediately found, the
         attempt to locate an element will be repeated for up to the amount of
         time set by set_search_timeout(). If multiple elements match the given
         criteria, only the first is returned. If no element matches, a
         NoSuchElementException will be raised.
 
-        :param method: The method to use to locate the element; one of: "id",
-                       "name", "class name", "tag name", "css selector", "link text",
-                       "partial link text", "xpath", "anon" and "anon attribute".
-                       Note that the "name", "link text" and
-                       "partial link test" methods are not supported in the chrome dom.
+        :param method: The method to use to locate the element; one of:
+            "id", "name", "class name", "tag name", "css selector",
+            "link text", "partial link text", "xpath", "anon" and "anon
+            attribute". Note that the "name", "link text" and "partial
+            link test" methods are not supported in the chrome DOM.
         :param target: The target of the search.  For example, if method =
-                       "tag", target might equal "div".  If method = "id", target would be
-                       an element id.
+            "tag", target might equal "div".  If method = "id", target would
+            be an element id.
         :param id: If specified, search for elements only inside the element
-                   with the specified id.
-        '''
-        kwargs = { 'value': target, 'using': method }
+            with the specified id.
+        """
+        body = {"value": target, "using": method}
         if id:
-            kwargs['element'] = id
-        response = self._send_message('findElement', 'value', **kwargs)
-        element = HTMLElement(self, response['ELEMENT'])
-        return element
+            body["element"] = id
+        el = self._send_message("findElement", body, key="value")
+        ref = el["ELEMENT"]
+        return HTMLElement(self, ref)
 
     def find_elements(self, method, target, id=None):
-        '''
-        Returns a list of all HTMLElement instances that match the specified method and target in the current context.
+        """Returns a list of all HTMLElement instances that match the
+        specified method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
-        element, such as click().  If no element is immediately found, the
-        attempt to locate an element will be repeated for up to the amount of
-        time set by set_search_timeout().
+        element, such as click().  If no element is immediately found,
+        the attempt to locate an element will be repeated for up to the
+        amount of time set by set_search_timeout().
 
-        :param method: The method to use to locate the elements; one of:
-                       "id", "name", "class name", "tag name", "css selector", "link text",
-                       "partial link text", "xpath", "anon" and "anon attribute".
-                       Note that the "name", "link text" and
-                       "partial link test" methods are not supported in the chrome dom.
+        :param method: The method to use to locate the elements; one
+            of: "id", "name", "class name", "tag name", "css selector",
+            "link text", "partial link text", "xpath", "anon" and "anon
+            attribute". Note that the "name", "link text" and "partial link
+            test" methods are not supported in the chrome DOM.
         :param target: The target of the search.  For example, if method =
-                       "tag", target might equal "div".  If method = "id", target would be
-                       an element id.
+            "tag", target might equal "div".  If method = "id", target would be
+            an element id.
         :param id: If specified, search for elements only inside the element
-                   with the specified id.
-        '''
-        kwargs = { 'value': target, 'using': method }
+            with the specified id.
+        """
+        body = {"value": target, "using": method}
         if id:
-            kwargs['element'] = id
-        response = self._send_message('findElements', 'value', **kwargs)
-        assert(isinstance(response, list))
-        elements = []
-        for x in response:
-            elements.append(HTMLElement(self, x['ELEMENT']))
-        return elements
+            body["element"] = id
+        els = self._send_message(
+            "findElements", body, key="value" if self.protocol == 1 else None)
+        assert(isinstance(els, list))
+        rv = []
+        for el in els:
+            rv.append(HTMLElement(self, el["ELEMENT"]))
+        return rv
 
     def get_active_element(self):
-        response = self._send_message('getActiveElement', 'value')
-        return HTMLElement(self, response)
+        el = self._send_message("getActiveElement", key="value")
+        return HTMLElement(self, el)
 
     def log(self, msg, level=None):
-        '''
-        Stores a timestamped log message in the Marionette server for later retrieval.
+        """Stores a timestamped log message in the Marionette server
+        for later retrieval.
 
         :param msg: String with message to log.
         :param level: String with log level (e.g. "INFO" or "DEBUG"). If None,
-         defaults to "INFO".
-        '''
-        return self._send_message('log', 'ok', value=msg, level=level)
+            defaults to "INFO".
+        """
+        body = {"value": msg, "level": level}
+        self._send_message("log", body)
 
     def get_logs(self):
-        '''
-        Returns the list of logged messages.
+        """Returns the list of logged messages.
 
         Each log message is an array with three string elements: the level,
         the message, and a date.
 
-        Usage example:
-
-        ::
+        Usage example::
 
-          marionette.log("I AM INFO")
-          marionette.log("I AM ERROR", "ERROR")
-          logs = marionette.get_logs()
-          assert logs[0][1] == "I AM INFO"
-          assert logs[1][1] == "I AM ERROR"
-        '''
-        return self._send_message('getLogs', 'value')
+            marionette.log("I AM INFO")
+            marionette.log("I AM ERROR", "ERROR")
+            logs = marionette.get_logs()
+            assert logs[0][1] == "I AM INFO"
+            assert logs[1][1] == "I AM ERROR"
+        """
+        return self._send_message("getLogs",
+                                  key="value" if self.protocol == 1 else None)
 
     def import_script(self, js_file):
-        '''
-        Imports a script into the scope of the execute_script and execute_async_script calls.
+        """Imports a script into the scope of the execute_script and
+        execute_async_script calls.
 
-        This is particularly useful if you wish to import your own libraries.
+        This is particularly useful if you wish to import your own
+        libraries.
 
         :param js_file: Filename of JavaScript file to import.
 
         For example, Say you have a script, importfunc.js, that contains:
 
         ::
 
-          let testFunc = function() { return "i'm a test function!";};
+            let testFunc = function() { return "i'm a test function!";};
 
-        Assuming this file is in the same directory as the test, you could do
-        something like:
+        Assuming this file is in the same directory as the test, you
+        could do something like:
 
         ::
 
-          js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importfunc.js"))
-          marionette.import_script(js)
-          assert "i'm a test function!" == self.marionette.execute_script("return testFunc();")
-        '''
-        js = ''
-        with open(js_file, 'r') as f:
+            js = os.path.abspath(os.path.join(__file__, os.path.pardir, "importfunc.js"))
+            marionette.import_script(js)
+            assert "i'm a test function!" == self.marionette.execute_script("return testFunc();")
+        """
+        js = ""
+        with open(js_file, "r") as f:
             js = f.read()
-        return self._send_message('importScript', 'ok', script=js)
+        body = {"script": js}
+        self._send_message("importScript", body)
 
     def clear_imported_scripts(self):
-        '''
-        Clears all imported scripts in this context, ie: calling clear_imported_scripts in chrome
-        context will clear only scripts you imported in chrome, and will leave the scripts
-        you imported in content context.
-        '''
-        return self._send_message('clearImportedScripts', 'ok')
+        """Clears all imported scripts in this context, ie: calling
+        clear_imported_scripts in chrome context will clear only scripts
+        you imported in chrome, and will leave the scripts you imported
+        in content context.
+        """
+        self._send_message("clearImportedScripts")
 
     def add_cookie(self, cookie):
-        """
-        Adds a cookie to your current session.
+        """Adds a cookie to your current session.
 
-        :param cookie: A dictionary object, with required keys - "name" and
-         "value"; optional keys - "path", "domain", "secure", "expiry".
+        :param cookie: A dictionary object, with required keys - "name"
+            and "value"; optional keys - "path", "domain", "secure",
+            "expiry".
 
         Usage example:
 
         ::
 
-          driver.add_cookie({'name': 'foo', 'value': 'bar'})
-          driver.add_cookie({'name': 'foo', 'value': 'bar', 'path': '/'})
-          driver.add_cookie({'name': 'foo', 'value': 'bar', 'path': '/',
-                             'secure': True})
+            driver.add_cookie({"name": "foo", "value": "bar"})
+            driver.add_cookie({"name": "foo", "value": "bar", "path": "/"})
+            driver.add_cookie({"name": "foo", "value": "bar", "path": "/",
+                               "secure": True})
         """
-        return self._send_message('addCookie', 'ok', cookie=cookie)
+        body = {"cookie": cookie}
+        self._send_message("addCookie", body)
 
     def delete_all_cookies(self):
-        """
-        Delete all cookies in the scope of the current session.
+        """Delete all cookies in the scope of the current session.
 
         Usage example:
 
         ::
 
-          driver.delete_all_cookies()
+            driver.delete_all_cookies()
         """
-        return self._send_message('deleteAllCookies', 'ok')
+        self._send_message("deleteAllCookies")
 
     def delete_cookie(self, name):
-        """
-        Delete a cookie by its name.
+        """Delete a cookie by its name.
 
         :param name: Name of cookie to delete.
 
         Usage example:
 
         ::
 
-          driver.delete_cookie('foo')
+            driver.delete_cookie("foo")
         """
-        return self._send_message('deleteCookie', 'ok', name=name);
+        self._send_message("deleteCookie", {"name": name})
 
     def get_cookie(self, name):
-        """
-        Get a single cookie by name. Returns the cookie if found, None if not.
+        """Get a single cookie by name. Returns the cookie if found,
+        None if not.
 
         :param name: Name of cookie to get.
         """
         cookies = self.get_cookies()
         for cookie in cookies:
-            if cookie['name'] == name:
+            if cookie["name"] == name:
                 return cookie
         return None
 
     def get_cookies(self):
         """Get all the cookies for the current domain.
 
         This is the equivalent of calling `document.cookie` and
         parsing the result.
 
-        :returns: A set of cookies for the current domain.
-
+        :returns: A list of cookies for the current domain.
         """
-
-        return self._send_message("getCookies", "value")
+        return self._send_message("getCookies", key="value" if self.protocol == 1 else None)
 
     @property
     def application_cache(self):
         return ApplicationCache(self)
 
     def screenshot(self, element=None, highlights=None, format="base64",
                    full=True):
         """Takes a screenshot of a web element or the current frame.
@@ -1796,83 +1775,85 @@ class Marionette(object):
             when `element` is None.
         """
 
         if element:
             element = element.id
         lights = None
         if highlights:
             lights = [highlight.id for highlight in highlights]
-        screenshot_data = self._send_message("takeScreenshot", "value",
-                                             id=element, highlights=lights,
-                                             full=full)
-        if format == 'base64':
-            return screenshot_data
-        elif format == 'binary':
-            return base64.b64decode(screenshot_data.encode('ascii'))
+
+        body = {"id": element,
+                "highlights": lights,
+                "full": full}
+        data = self._send_message("takeScreenshot", body, key="value")
+
+        if format == "base64":
+            return data
+        elif format == "binary":
+            return base64.b64decode(data.encode("ascii"))
         else:
             raise ValueError("format parameter must be either 'base64'"
                              " or 'binary', not {0}".format(repr(format)))
 
     @property
     def orientation(self):
         """Get the current browser orientation.
 
         Will return one of the valid primary orientation values
         portrait-primary, landscape-primary, portrait-secondary, or
         landscape-secondary.
-
         """
-        return self._send_message("getScreenOrientation", "value")
+        return self._send_message("getScreenOrientation", key="value")
 
     def set_orientation(self, orientation):
         """Set the current browser orientation.
 
         The supplied orientation should be given as one of the valid
         orientation values.  If the orientation is unknown, an error
         will be raised.
 
         Valid orientations are "portrait" and "landscape", which fall
         back to "portrait-primary" and "landscape-primary"
         respectively, and "portrait-secondary" as well as
         "landscape-secondary".
 
         :param orientation: The orientation to lock the screen in.
-
         """
-        self._send_message("setScreenOrientation", "ok", orientation=orientation)
+        body = {"orientation": orientation}
+        self._send_message("setScreenOrientation", body)
         if self.emulator:
             self.emulator.screen.orientation = orientation.lower()
 
     @property
     def window_size(self):
         """Get the current browser window size.
 
         Will return the current browser window size in pixels. Refers to
         window outerWidth and outerHeight values, which include scroll bars,
         title bars, etc.
 
         :returns: dictionary representation of current window width and height
-
         """
-        return self._send_message("getWindowSize", "value")
+        return self._send_message("getWindowSize",
+                                  key="value" if self.protocol == 1 else None)
 
     def set_window_size(self, width, height):
         """Resize the browser window currently in focus.
 
         The supplied width and height values refer to the window outerWidth
         and outerHeight values, which include scroll bars, title bars, etc.
 
         An error will be returned if the requested window size would result
         in the window being in the maximised state.
 
         :param width: The width to resize the window to.
         :param height: The height to resize the window to.
 
         """
-        self._send_message("setWindowSize", "ok", width=width, height=height)
+        body = {"width": width, "height": height}
+        self._send_message("setWindowSize", body)
 
     def maximize_window(self):
         """ Resize the browser window currently receiving commands. The action
         should be equivalent to the user pressing the the maximize button
         """
-
-        return self._send_message("maximizeWindow", "ok")
+        return self._send_message("maximizeWindow")
--- a/testing/marionette/transport/marionette_transport/transport.py
+++ b/testing/marionette/transport/marionette_transport/transport.py
@@ -5,51 +5,50 @@
 import datetime
 import errno
 import json
 import socket
 import time
 
 
 class MarionetteTransport(object):
-    """ The Marionette socket client.  This speaks the same protocol
-        as the remote debugger inside Gecko, in which messages are
-        always preceded by the message length and a colon, e.g.,
+    """The Marionette socket client.  This speaks the same protocol
+    as the remote debugger inside Gecko, in which messages are always
+    preceded by the message length and a colon, e.g.:
 
-        20:{'command': 'test'}
+        20:{"command": "test"}
     """
 
     max_packet_length = 4096
     connection_lost_msg = "Connection to Marionette server is lost. Check gecko.log (desktop firefox) or logcat (b2g) for errors."
 
     def __init__(self, addr, port, socket_timeout=360.0):
         self.addr = addr
         self.port = port
         self.socket_timeout = socket_timeout
         self.sock = None
-        self.traits = None
-        self.applicationType = None
-        self.actor = 'root'
+        self.protocol = 1
+        self.application_type = None
 
     def _recv_n_bytes(self, n):
-        """ Convenience method for receiving exactly n bytes from
-            self.sock (assuming it's open and connected).
+        """Convenience method for receiving exactly n bytes from self.sock
+        (assuming it's open and connected).
         """
-        data = ''
+        data = ""
         while len(data) < n:
             chunk = self.sock.recv(n - len(data))
-            if chunk == '':
+            if chunk == "":
                 break
             data += chunk
         return data
 
     def receive(self):
-        """ Receive the next complete response from the server, and return
-            it as a dict.  Each response from the server is prepended by
-            len(message) + ':'.
+        """Receive the next complete response from the server, and
+        return it as a JSON structure.  Each response from the server
+        is prepended by len(message) + ":".
         """
         assert(self.sock)
         now = time.time()
         response = ''
         bytes_to_recv = 10
         while time.time() - now < self.socket_timeout:
             try:
                 data = self.sock.recv(bytes_to_recv)
@@ -64,63 +63,58 @@ class MarionetteTransport(object):
                 length = response[0:sep]
                 remaining = response[sep + 1:]
                 if len(remaining) == int(length):
                     return json.loads(remaining)
                 bytes_to_recv = int(length) - len(remaining)
         raise socket.timeout('connection timed out after %d s' % self.socket_timeout)
 
     def connect(self):
-        """ Connect to the server and process the hello message we expect
-            to receive in response.
+        """Connect to the server and process the hello message we expect
+        to receive in response.
+
+        Return a tuple of the protocol level and the application type.
         """
         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.sock.settimeout(self.socket_timeout)
         try:
             self.sock.connect((self.addr, self.port))
         except:
             # Unset self.sock so that the next attempt to send will cause
             # another connection attempt.
             self.sock = None
             raise
         self.sock.settimeout(2.0)
+
         hello = self.receive()
-        self.traits = hello.get('traits')
-        self.applicationType = hello.get('applicationType')
+        self.protocol = hello.get("marionetteProtocol", 1)
+        self.application_type = hello.get("applicationType")
 
-        # get the marionette actor id
-        response = self.send({'to': 'root', 'name': 'getMarionetteID'})
-        self.actor = response['id']
+        return (self.protocol, self.application_type)
 
-    def send(self, msg):
-        """ Send a message on the socket, prepending it with len(msg) + ':'.
-        """
+    def send(self, data):
+        """Send a message on the socket, prepending it with len(msg) + ":"."""
         if not self.sock:
             self.connect()
-        if 'to' not in msg:
-            msg['to'] = self.actor
-        data = json.dumps(msg)
-        data = '%s:%s' % (len(data), data)
+        data = "%s:%s" % (len(data), data)
 
         for packet in [data[i:i + self.max_packet_length] for i in
                        range(0, len(data), self.max_packet_length)]:
-            try: 
+            try:
                 self.sock.send(packet)
             except IOError as e:
                 if e.errno == errno.EPIPE:
                     raise IOError("%s: %s" % (str(e), self.connection_lost_msg))
                 else:
                     raise e
 
-        response = self.receive()
-        return response
+        return self.receive()
 
     def close(self):
-        """ Close the socket.
-        """
+        """Close the socket."""
         if self.sock:
             self.sock.close()
         self.sock = None
 
     @staticmethod
     def wait_for_port(host, port, timeout=60):
         """ Wait for the specified Marionette host/port to be available."""
         starttime = datetime.datetime.now()