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

--HG--
extra : commitid : CiAKTE3h1nG
extra : rebase_source : ea3ed4ec021febac7f3b2ae2c10ec583a3ddfcff
This commit is contained in:
Andreas Tolfsen 2015-08-15 15:46:31 +01:00
parent 2211b5dea1
commit 9351a4ab23
6 changed files with 567 additions and 535 deletions

View File

@ -2,11 +2,22 @@
# 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"):
@ -29,3 +40,51 @@ class TestHandleError(marionette_test.MarionetteTestCase):
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})

View File

@ -15,7 +15,7 @@ class TestTearDownContext(MarionetteTestCase):
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")

View File

@ -14,13 +14,13 @@ class TestSetContext(MarionetteTestCase):
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):

View File

@ -1,21 +1,19 @@
"""
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
@ -28,4 +26,4 @@ class ApplicationCache(object):
@property
def status(self):
return self.driver._send_message('getAppCacheStatus', 'value')
return self.driver._send_message("getAppCacheStatus", key="value")

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,11 @@ 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
@ -25,26 +25,25 @@ class MarionetteTransport(object):
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()
@ -69,8 +68,10 @@ class MarionetteTransport(object):
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)
@ -82,27 +83,22 @@ class MarionetteTransport(object):
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:
@ -110,12 +106,10 @@ class MarionetteTransport(object):
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