Bug 1640249 - Make marionette Python2/Python3 compatible. r=marionette-reviewers,maja_zf,whimboo
authorBob Clary <bclary@bclary.com>
Wed, 27 May 2020 19:21:07 +0000
changeset 532562 e35490feff3a6fe45a3558eef3890576665cb211
parent 532561 981d3e5d9a00c5dc197134266158a856813c3a0d
child 532563 d8a7c3cb03ce9a4edfcd91975be88a7781ba1c36
push id37456
push userrmaries@mozilla.com
push dateThu, 28 May 2020 03:25:13 +0000
treeherdermozilla-central@cfa4bd8e6f78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarionette-reviewers, maja_zf, whimboo
bugs1640249
milestone78.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 1640249 - Make marionette Python2/Python3 compatible. r=marionette-reviewers,maja_zf,whimboo Differential Revision: https://phabricator.services.mozilla.com/D76648
testing/marionette/client/marionette_driver/errors.py
testing/marionette/client/marionette_driver/geckoinstance.py
testing/marionette/client/marionette_driver/marionette.py
testing/marionette/client/marionette_driver/wait.py
testing/marionette/harness/marionette_harness/marionette_test/testcases.py
testing/marionette/harness/marionette_harness/runner/base.py
testing/marionette/harness/marionette_harness/tests/unit/test_click.py
testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py
testing/marionette/harness/marionette_harness/tests/unit/test_crash.py
testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py
testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py
testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py
testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py
testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py
testing/marionette/harness/marionette_harness/tests/unit/test_errors.py
testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
testing/marionette/harness/marionette_harness/tests/unit/test_expected.py
testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py
testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py
testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py
testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py
testing/marionette/harness/marionette_harness/tests/unit/test_position.py
testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
testing/marionette/harness/marionette_harness/tests/unit/test_prefs_enforce.py
testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py
testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
testing/marionette/harness/marionette_harness/tests/unit/test_select.py
testing/marionette/harness/marionette_harness/tests/unit/test_session.py
testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py
testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_title.py
testing/marionette/harness/marionette_harness/tests/unit/test_typing.py
testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py
testing/marionette/harness/marionette_harness/tests/unit/test_wait.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
--- a/testing/marionette/client/marionette_driver/errors.py
+++ b/testing/marionette/client/marionette_driver/errors.py
@@ -1,17 +1,20 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import traceback
 
+import six
 
+
+@six.python_2_unicode_compatible
 class MarionetteException(Exception):
 
     """Raised when a generic non-recoverable exception has occured."""
 
     status = "webdriver error"
 
     def __init__(self, message=None, cause=None, stacktrace=None):
         """Construct new MarionetteException instance.
@@ -24,24 +27,20 @@ class MarionetteException(Exception):
 
         :param stacktrace: Optional string containing a stacktrace
             (typically from a failed JavaScript execution) that will
             be displayed in the exception's string representation.
 
         """
         self.cause = cause
         self.stacktrace = stacktrace
-
-        super(MarionetteException, self).__init__(message)
+        self._message = six.text_type(message)
 
     def __str__(self):
-        return unicode(self).encode("utf-8")
-
-    def __unicode__(self):
-        msg = unicode(self.message)
+        msg = self.message
         tb = None
 
         if self.cause:
             if type(self.cause) is tuple:
                 msg += u", caused by {0!r}".format(self.cause[0])
                 tb = self.cause[2]
             else:
                 msg += u", caused by {}".format(self.cause)
@@ -49,17 +48,21 @@ class MarionetteException(Exception):
         if self.stacktrace:
             st = u"".join(["\t{}\n".format(x)
                            for x in self.stacktrace.splitlines()])
             msg += u"\nstacktrace:\n{}".format(st)
 
         if tb:
             msg += u": " + u"".join(traceback.format_tb(tb))
 
-        return msg
+        return six.text_type(msg)
+
+    @property
+    def message(self):
+        return self._message
 
 
 class ElementNotSelectableException(MarionetteException):
     status = "element not selectable"
 
 
 class ElementClickInterceptedException(MarionetteException):
     status = "element click intercepted"
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -212,17 +212,17 @@ class GeckoInstance(object):
             if hasattr(self, "_profile") and profile is self._profile:
                 return
 
         else:
             profile_args = self.profile_args
             profile_path = profile
 
             # If a path to a profile is given then clone it
-            if isinstance(profile_path, basestring):
+            if isinstance(profile_path, six.string_types):
                 profile_args["path_from"] = profile_path
                 profile_args["path_to"] = tempfile.mkdtemp(
                     suffix=u".{}".format(profile_name or os.path.basename(profile_path)),
                     dir=self.workspace)
                 # The target must not exist yet
                 os.rmdir(profile_args["path_to"])
 
                 profile = Profile.clone(**profile_args)
@@ -291,18 +291,18 @@ class GeckoInstance(object):
         try:
             if not app and kwargs["bin"] is not None:
                 app_id = mozversion.get_version(binary=kwargs["bin"])["application_id"]
                 app = app_ids[app_id]
 
             instance_class = apps[app]
         except (IOError, KeyError):
             exc, val, tb = sys.exc_info()
-            msg = 'Application "{0}" unknown (should be one of {1})'
-            reraise(NotImplementedError, msg.format(app, apps.keys()), tb)
+            msg = 'Application "{0}" unknown (should be one of {1})'.format(app, apps.keys())
+            reraise(NotImplementedError, NotImplementedError(msg), tb)
 
         return instance_class(*args, **kwargs)
 
     def start(self):
         self._update_profile(self.profile)
         self.runner = self.runner_class(**self._get_runner_args())
         self.runner.start()
 
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -10,16 +10,17 @@ import json
 import os
 import socket
 import sys
 import time
 import traceback
 
 from contextlib import contextmanager
 
+import six
 from six import reraise
 
 from . import errors
 from . import transport
 from .decorators import do_process_check
 from .geckoinstance import GeckoInstance
 from .keys import Keys
 from .timeout import Timeouts
@@ -819,17 +820,17 @@ class Marionette(object):
 
         :param prefs: A dictionary whose keys are preference names.
         """
         if not self.instance:
             raise errors.MarionetteException("enforce_gecko_prefs() can only be called "
                                              "on Gecko instances launched by Marionette")
         pref_exists = True
         with self.using_context(self.CONTEXT_CHROME):
-            for pref, value in prefs.iteritems():
+            for pref, value in six.iteritems(prefs):
                 if type(value) is not str:
                     value = json.dumps(value)
                 pref_exists = self.execute_script("""
                 let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
                                               .getService(Components.interfaces.nsIPrefBranch);
                 let pref = '{0}';
                 let value = '{1}';
                 let type = prefInterface.getPrefType(pref);
@@ -1489,17 +1490,17 @@ class Marionette(object):
                 wrapped.append(self._to_json(arg))
         elif isinstance(args, dict):
             wrapped = {}
             for arg in args:
                 wrapped[arg] = self._to_json(args[arg])
         elif type(args) == HTMLElement:
             wrapped = {WEB_ELEMENT_KEY: args.id,
                        CHROME_ELEMENT_KEY: args.id}
-        elif (isinstance(args, bool) or isinstance(args, basestring) or
+        elif (isinstance(args, bool) or isinstance(args, six.string_types) or
               isinstance(args, int) or isinstance(args, float) or args is None):
             wrapped = args
         return wrapped
 
     def _from_json(self, value):
         if isinstance(value, list):
             unwrapped = []
             for item in value:
--- a/testing/marionette/client/marionette_driver/wait.py
+++ b/testing/marionette/client/marionette_driver/wait.py
@@ -144,18 +144,18 @@ class Wait(object):
                 return rv
 
             self.clock.sleep(interval_new)
 
         if message:
             message = " with message: {}".format(message)
 
         raise errors.TimeoutException(
-            "Timed out after {0} seconds{1}".format(round((self.clock.now - start), 1),
-                                                    message if message else ""),
+            "Timed out after {0:.1f} seconds{1}".format(round((self.clock.now - start), 1),
+                                                        message if message else ""),
             cause=last_exc)
 
 
 def until_pred(clock, end):
     return clock.now >= end
 
 
 class SystemClock(object):
--- a/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
+++ b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
@@ -4,23 +4,24 @@
 
 from __future__ import absolute_import
 
 import imp
 import os
 import re
 import sys
 import time
-import types
 import unittest
 import warnings
 import weakref
 
 from unittest.case import SkipTest
 
+import six
+
 from marionette_driver.errors import (
     TimeoutException,
     UnresponsiveInstanceException
 )
 from mozlog import get_default_logger
 
 
 # ExpectedFailure and UnexpectedSuccess are adapted from the Python 2
@@ -73,33 +74,33 @@ class MetaParameterized(type):
 
     It can be used like :func:`parameterized`
     or :func:`with_parameters` to generate new methods.
     """
 
     RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]')
 
     def __new__(cls, name, bases, attrs):
-        for k, v in attrs.items():
+        for k, v in list(attrs.items()):
             if callable(v) and hasattr(v, 'metaparameters'):
                 for func_suffix, args, kwargs in v.metaparameters:
                     func_suffix = cls.RE_ESCAPE_BAD_CHARS.sub('_', func_suffix)
                     wrapper = _wraps_parameterized(v, func_suffix, args, kwargs)
                     if wrapper.__name__ in attrs:
                         raise KeyError("{0} is already a defined method on {1}"
                                        .format(wrapper.__name__, name))
                     attrs[wrapper.__name__] = wrapper
                 del attrs[k]
 
         return type.__new__(cls, name, bases, attrs)
 
 
+@six.add_metaclass(MetaParameterized)
 class CommonTestCase(unittest.TestCase):
 
-    __metaclass__ = MetaParameterized
     match_re = None
     failureException = AssertionError
     pydebugger = None
 
     def __init__(self, methodName, marionette_weakref, fixtures, **kwargs):
         super(CommonTestCase, self).__init__(methodName)
         self.methodName = methodName
 
@@ -345,17 +346,17 @@ class MarionetteTestCase(CommonTestCase)
         # imp.load_source call.
         if mod_name in sys.modules:
             del sys.modules[mod_name]
 
         test_mod = imp.load_source(mod_name, filepath)
 
         for name in dir(test_mod):
             obj = getattr(test_mod, name)
-            if (isinstance(obj, (type, types.ClassType)) and
+            if (isinstance(obj, six.class_types) and
                     issubclass(obj, unittest.TestCase)):
                 testnames = testloader.getTestCaseNames(obj)
                 for testname in testnames:
                     suite.addTest(obj(weakref.ref(marionette),
                                       fixtures,
                                       methodName=testname,
                                       filepath=filepath,
                                       testvars=testvars,
--- a/testing/marionette/harness/marionette_harness/runner/base.py
+++ b/testing/marionette/harness/marionette_harness/runner/base.py
@@ -1071,20 +1071,20 @@ class BaseMarionetteTestRunner(object):
         for test in tests:
             self.run_test(test['filepath'], test['expected'])
             if self.record_crash():
                 break
 
     def run_test_sets(self):
         if len(self.tests) < 1:
             raise Exception('There are no tests to run.')
-        elif self.total_chunks > len(self.tests):
+        elif self.total_chunks is not None and self.total_chunks > len(self.tests):
             raise ValueError('Total number of chunks must be between 1 and {}.'
                              .format(len(self.tests)))
-        if self.total_chunks > 1:
+        if self.total_chunks is not None and self.total_chunks > 1:
             chunks = [[] for i in range(self.total_chunks)]
             for i, test in enumerate(self.tests):
                 target_chunk = i % self.total_chunks
                 chunks[target_chunk].append(test)
 
             self.logger.info('Running chunk {0} of {1} ({2} tests selected from a '
                              'total of {3})'.format(self.this_chunk, self.total_chunks,
                                                     len(chunks[self.this_chunk - 1]),
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
@@ -1,27 +1,27 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver import By, errors
 from marionette_driver.marionette import Alert
 
 from marionette_harness import (
     MarionetteTestCase,
     WindowManagerMixin,
 )
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 # The <a> element in the following HTML is not interactable because it
 # is hidden by an overlay when scrolled into the top of the viewport.
 # It should be interactable when scrolled in at the bottom of the
 # viewport.
 fixed_overlay = inline("""
 <style>
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py
@@ -1,24 +1,24 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_driver.errors import MoveTargetOutOfBoundsException
 
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestClickScrolling(MarionetteTestCase):
 
     def test_clicking_on_anchor_scrolls_page(self):
         self.marionette.navigate(inline("""
           <a href="#content">Link to content</a>
           <div id="content" style="margin-top: 205vh;">Text</div>
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py
@@ -3,18 +3,21 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function
 
 import glob
 import os
 import shutil
 import sys
+import unittest
 from io import StringIO
 
+import six
+
 from marionette_driver import Wait
 from marionette_driver.errors import (
     InvalidSessionIdException,
     NoSuchWindowException,
     TimeoutException
 )
 
 from marionette_harness import MarionetteTestCase, expectedFailure
@@ -156,17 +159,18 @@ class TestCrash(BaseCrashTestCase):
         self.assertIsNone(self.marionette.session)
         with self.assertRaisesRegexp(InvalidSessionIdException, 'Please start a session'):
             self.marionette.get_url()
 
         self.marionette.start_session()
         self.assertNotEqual(self.marionette.process_id, self.pid)
         self.marionette.get_url()
 
-    @expectedFailure
+    @unittest.expectedFailure
+    @unittest.skipIf(six.PY3, "Bug 1641226 - Not supported in Python3.")
     def test_unexpected_crash(self):
         self.crash(parent=True)
 
 
 class TestCrashInSetUp(BaseCrashTestCase):
 
     def setUp(self):
         super(TestCrashInSetUp, self).setUp()
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_data_driven.py
@@ -1,23 +1,26 @@
 # 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/.
 
 from __future__ import absolute_import
 
+import six
+
 from marionette_harness.marionette_test import (
     parameterized,
     with_parameters,
     MetaParameterized,
     MarionetteTestCase
 )
 
+@six.add_metaclass(MetaParameterized)
 class Parameterizable(object):
-    __metaclass__ = MetaParameterized
+    pass
 
 class TestDataDriven(MarionetteTestCase):
     def test_parameterized(self):
         class Test(Parameterizable):
             def __init__(self):
                 self.parameters = []
 
             @parameterized('1', 'thing', named=43)
@@ -61,9 +64,9 @@ class TestDataDriven(MarionetteTestCase)
         with self.assertRaises(KeyError):
             class Test(Parameterizable):
                 @parameterized('1', 'thing', named=43)
                 @parameterized('1', 'thing2')
                 def test(self, thing, named=None):
                     pass
 
     def test_marionette_test_case_is_parameterizable(self):
-        self.assertTrue(issubclass(MarionetteTestCase.__metaclass__, MetaParameterized))
+        self.assertTrue(isinstance(MarionetteTestCase, MetaParameterized))
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_date_time_value.py
@@ -1,25 +1,25 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from datetime import datetime
 
-from datetime import datetime
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_driver.date_time_value import DateTimeValue
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestDateTime(MarionetteTestCase):
     def test_set_date(self):
         self.marionette.navigate(inline("<input id='date-test' type='date'/>"))
 
         element = self.marionette.find_element(By.ID, "date-test")
         dt_value = DateTimeValue(element)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_retrieval.py
@@ -1,29 +1,30 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import re
-import urllib
+
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_driver.errors import NoSuchElementException, InvalidSelectorException
 from marionette_driver.marionette import HTMLElement
 
 from marionette_harness import MarionetteTestCase, skip
 
 
 def inline(doc, doctype="html"):
     if doctype == "html":
-        return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+        return "data:text/html;charset=utf-8,{}".format(quote(doc))
     elif doctype == "xhtml":
-        return "data:application/xhtml+xml,{}".format(urllib.quote(
+        return "data:application/xhtml+xml,{}".format(quote(
 r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
   <head>
     <title>XHTML might be the future</title>
   </head>
 
   <body>
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_element_state.py
@@ -1,16 +1,18 @@
 # 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/.
 
 from __future__ import absolute_import, print_function
 
 import types
-import urllib
+
+import six
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_harness import MarionetteTestCase
 
 
 boolean_attributes = {
   "audio": ["autoplay", "controls", "loop", "muted"],
   "button": ["autofocus", "disabled", "formnovalidate"],
@@ -30,19 +32,19 @@ boolean_attributes = {
   "textarea": ["autofocus", "disabled", "readonly", "required"],
   "track": ["default"],
   "video": ["autoplay", "controls", "loop", "muted"],
 }
 
 
 def inline(doc, doctype="html"):
     if doctype == "html":
-        return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+        return "data:text/html;charset=utf-8,{}".format(quote(doc))
     elif doctype == "xhtml":
-        return "data:application/xhtml+xml,{}".format(urllib.quote(
+        return "data:application/xhtml+xml,{}".format(quote(
 r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
   <head>
     <title>XHTML might be the future</title>
   </head>
 
   <body>
@@ -77,61 +79,61 @@ class TestIsElementDisplayed(MarionetteT
         self.assertFalse(l.is_displayed())
 
 
 class TestGetElementAttribute(MarionetteTestCase):
     def test_normal_attribute(self):
         self.marionette.navigate(inline("<p style=foo>"))
         el = self.marionette.find_element(By.TAG_NAME, "p")
         attr = el.get_attribute("style")
-        self.assertIsInstance(attr, types.StringTypes)
+        self.assertIsInstance(attr, six.string_types)
         self.assertEqual("foo", attr)
 
     def test_boolean_attributes(self):
-        for tag, attrs in boolean_attributes.iteritems():
+        for tag, attrs in six.iteritems(boolean_attributes):
             for attr in attrs:
                 print("testing boolean attribute <{0} {1}>".format(tag, attr))
                 doc = inline("<{0} {1}>".format(tag, attr))
                 self.marionette.navigate(doc)
                 el = self.marionette.find_element(By.TAG_NAME, tag)
                 res = el.get_attribute(attr)
-                self.assertIsInstance(res, types.StringTypes)
+                self.assertIsInstance(res, six.string_types)
                 self.assertEqual("true", res)
 
     def test_global_boolean_attributes(self):
         self.marionette.navigate(inline("<p hidden>foo"))
         el = self.marionette.find_element(By.TAG_NAME, "p")
         attr = el.get_attribute("hidden")
-        self.assertIsInstance(attr, types.StringTypes)
+        self.assertIsInstance(attr, six.string_types)
         self.assertEqual("true", attr)
 
         self.marionette.navigate(inline("<p>foo"))
         el = self.marionette.find_element(By.TAG_NAME, "p")
         attr = el.get_attribute("hidden")
         self.assertIsNone(attr)
 
         self.marionette.navigate(inline("<p itemscope>foo"))
         el = self.marionette.find_element(By.TAG_NAME, "p")
         attr = el.get_attribute("itemscope")
-        self.assertIsInstance(attr, types.StringTypes)
+        self.assertIsInstance(attr, six.string_types)
         self.assertEqual("true", attr)
 
         self.marionette.navigate(inline("<p>foo"))
         el = self.marionette.find_element(By.TAG_NAME, "p")
         attr = el.get_attribute("itemscope")
         self.assertIsNone(attr)
 
     # TODO(ato): Test for custom elements
 
     def test_xhtml(self):
         doc = inline("<p hidden=\"true\">foo</p>", doctype="xhtml")
         self.marionette.navigate(doc)
         el = self.marionette.find_element(By.TAG_NAME, "p")
         attr = el.get_attribute("hidden")
-        self.assertIsInstance(attr, types.StringTypes)
+        self.assertIsInstance(attr, six.string_types)
         self.assertEqual("true", attr)
 
 
 class TestGetElementProperty(MarionetteTestCase):
     def test_get(self):
         self.marionette.navigate(disabled)
         el = self.marionette.find_element(By.TAG_NAME, "input")
         prop = el.get_property("disabled")
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_elementsize.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 
 from marionette_harness import MarionetteTestCase
 
 
 TEST_SIZE = """
     <?xml version="1.0"?>
@@ -19,17 +19,17 @@ TEST_SIZE = """
   </head>
   <body>
     <p>Let's get the size of <a href='#' id='linkId'>some really cool link</a></p>
   </body>
 </html>
 """
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 class TestElementSize(MarionetteTestCase):
     def testShouldReturnTheSizeOfALink(self):
         self.marionette.navigate(inline(TEST_SIZE))
         shrinko = self.marionette.find_element(By.ID, 'linkId')
         size = shrinko.rect
         self.assertTrue(size['width'] > 0)
         self.assertTrue(size['height'] > 0)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_errors.py
@@ -1,16 +1,18 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import sys
 
+import six
+
 from marionette_driver import errors
 
 from marionette_harness import marionette_test
 
 
 def fake_cause():
     try:
         raise ValueError("bar")
@@ -22,17 +24,17 @@ unicode_message = u"\u201Cfoo"
 cause = fake_cause()
 stacktrace = "first\nsecond"
 
 
 class TestErrors(marionette_test.MarionetteTestCase):
 
     def test_defaults(self):
         exc = errors.MarionetteException()
-        self.assertIsNone(exc.message)
+        self.assertEquals(str(exc), 'None')
         self.assertIsNone(exc.cause)
         self.assertIsNone(exc.stacktrace)
 
     def test_construction(self):
         exc = errors.MarionetteException(
             message=message, cause=cause, stacktrace=stacktrace)
         self.assertEquals(exc.message, message)
         self.assertEquals(exc.cause, cause)
@@ -44,26 +46,26 @@ class TestErrors(marionette_test.Marione
         r = str(exc)
         self.assertIn(message, r)
         self.assertIn(", caused by {0!r}".format(cause[0]), r)
         self.assertIn("\nstacktrace:\n\tfirst\n\tsecond", r)
 
     def test_unicode_message(self):
         exc = errors.MarionetteException(
             message=unicode_message, cause=cause, stacktrace=stacktrace)
-        r = unicode(exc)
+        r = six.text_type(exc)
         self.assertIn(unicode_message, r)
         self.assertIn(", caused by {0!r}".format(cause[0]), r)
         self.assertIn("\nstacktrace:\n\tfirst\n\tsecond", r)
 
     def test_unicode_message_as_str(self):
         exc = errors.MarionetteException(
             message=unicode_message, cause=cause, stacktrace=stacktrace)
         r = str(exc)
-        self.assertIn(unicode_message.encode("utf-8"), r)
+        self.assertIn(six.ensure_str(unicode_message, encoding="utf-8"), r)
         self.assertIn(", caused by {0!r}".format(cause[0]), r)
         self.assertIn("\nstacktrace:\n\tfirst\n\tsecond", r)
 
     def test_cause_string(self):
         exc = errors.MarionetteException(cause="foo")
         self.assertEqual(exc.cause, "foo")
         r = str(exc)
         self.assertIn(", caused by foo", r)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
@@ -1,22 +1,23 @@
 from __future__ import absolute_import
 
 import os
-import urllib
+
+from six.moves.urllib.parse import quote
 
 from marionette_driver import By, errors
 from marionette_driver.marionette import Alert, HTMLElement
 from marionette_driver.wait import Wait
 
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 elements = inline("<p>foo</p> <p>bar</p>")
 
 globals = set([
               "atob",
               "Audio",
               "btoa",
@@ -159,17 +160,17 @@ class TestExecuteContent(MarionetteTestC
                           self.marionette.execute_script, "return foo")
 
     def test_stacktrace(self):
         with self.assertRaises(errors.JavascriptException) as cm:
             self.marionette.execute_script("return b")
 
         # by default execute_script pass the name of the python file
         self.assertIn(os.path.relpath(__file__.replace(".pyc", ".py")), cm.exception.stacktrace)
-        self.assertIn("b is not defined", cm.exception.message)
+        self.assertIn("b is not defined", str(cm.exception))
 
     def test_permission(self):
         for sandbox in ["default", None]:
             with self.assertRaises(errors.JavascriptException):
                self.marionette.execute_script(
                     "Components.classes['@mozilla.org/preferences-service;1']")
 
     def test_return_web_element(self):
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_expected.py
@@ -1,24 +1,24 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver import expected
 from marionette_driver.by import By
 
 from marionette_harness import marionette_test
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 static_element = inline("""<p>foo</p>""")
 static_elements = static_element + static_element
 
 remove_element_by_tag_name = \
     """var el = document.getElementsByTagName('{}')[0];
     document.getElementsByTagName("body")[0].remove(el);"""
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_file_upload.py
@@ -1,27 +1,29 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import contextlib
-import urllib
 
 from tempfile import NamedTemporaryFile as tempfile
 
+import six
+from six.moves.urllib.parse import quote
+
 from marionette_driver import By, errors, expected
 from marionette_driver.wait import Wait
 from marionette_harness import MarionetteTestCase, skip
 
 
-single = "data:text/html,{}".format(urllib.quote("<input type=file>"))
-multiple = "data:text/html,{}".format(urllib.quote("<input type=file multiple>"))
-upload = lambda url: "data:text/html,{}".format(urllib.quote("""
+single = "data:text/html,{}".format(quote("<input type=file>"))
+multiple = "data:text/html,{}".format(quote("<input type=file multiple>"))
+upload = lambda url: "data:text/html,{}".format(quote("""
     <form action='{}' method=post enctype='multipart/form-data'>
      <input type=file>
      <input type=submit>
     </form>""".format(url)))
 
 
 class TestFileUpload(MarionetteTestCase):
     def test_sets_one_file(self):
@@ -37,17 +39,17 @@ class TestFileUpload(MarionetteTestCase)
         self.assertEqual(len(files), 1)
         self.assertFileNamesEqual(files, exp)
 
     def test_sets_multiple_files(self):
         self.marionette.navigate(multiple)
         input = self.input
 
         exp = None
-        with contextlib.nested(tempfile(), tempfile()) as (a, b):
+        with tempfile() as a, tempfile() as b:
             input.send_keys(a.name)
             input.send_keys(b.name)
             exp = [a.name, b.name]
 
         files = self.get_file_names(input)
         self.assertEqual(len(files), 2)
         self.assertFileNamesEqual(files, exp)
 
@@ -75,17 +77,17 @@ class TestFileUpload(MarionetteTestCase)
         self.assertEqual(len(self.get_files(input)), 1)
         input.clear()
         self.assertEqual(len(self.get_files(input)), 0)
 
     def test_clear_files(self):
         self.marionette.navigate(multiple)
         input = self.input
 
-        with contextlib.nested(tempfile(), tempfile()) as (a, b):
+        with tempfile() as a, tempfile() as b:
             input.send_keys(a.name)
             input.send_keys(b.name)
 
         self.assertEqual(len(self.get_files(input)), 2)
         input.clear()
         self.assertEqual(len(self.get_files(input)), 0)
 
     def test_illegal_file(self):
@@ -94,17 +96,17 @@ class TestFileUpload(MarionetteTestCase)
             self.input.send_keys("rochefort")
 
     def test_upload(self):
         self.marionette.navigate(
             upload(self.marionette.absolute_url("file_upload")))
         url = self.marionette.get_url()
 
         with tempfile() as f:
-            f.write("camembert")
+            f.write(six.ensure_binary("camembert"))
             f.flush()
             self.input.send_keys(f.name)
             self.submit.click()
 
         Wait(self.marionette, timeout=self.marionette.timeout.page_load).until(
             lambda m: m.get_url() != url,
             message="URL didn't change after submitting a file upload")
         self.assertIn("multipart/form-data", self.body.text)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_key_actions.py
@@ -1,23 +1,23 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_driver.keys import Keys
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestKeyActions(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestKeyActions, self).setUp()
         self.key_chain = self.marionette.actions.sequence("key", "keyboard_id")
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py
@@ -1,24 +1,24 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver import By, errors, Wait
 from marionette_driver.keys import Keys
 
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class BaseMouseAction(MarionetteTestCase):
 
     def setUp(self):
         super(BaseMouseAction, self).setUp()
         self.mouse_chain = self.marionette.actions.sequence(
             "pointer", "pointer_id", {"pointerType": "mouse"})
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -1,17 +1,18 @@
 # 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/.
 
 from __future__ import absolute_import, print_function
 
 import contextlib
 import os
-import urllib
+
+from six.moves.urllib.parse import quote
 
 from marionette_driver import By, errors, expected, Wait
 from marionette_driver.keys import Keys
 from marionette_driver.marionette import Alert
 from marionette_harness import (
     MarionetteTestCase,
     run_if_manage_instance,
     skip,
@@ -21,17 +22,17 @@ from marionette_harness import (
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 BLACK_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' # noqa
 RED_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=' # noqa
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+    return "data:text/html;charset=utf-8,%s" % quote(doc)
 
 
 def inline_image(data):
     return 'data:image/png;base64,%s' % data
 
 
 class BaseNavigationTestCase(WindowManagerMixin, MarionetteTestCase):
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_pagesource.py
@@ -1,22 +1,22 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_harness import MarionetteTestCase
 
 def inline(doc, mime=None, charset=None):
     mime = 'html' if mime is None else mime
     charset = 'utf-8' if (charset is None) else charset
-    return "data:text/{};charset={},{}".format(mime, charset, urllib.quote(doc))
+    return "data:text/{};charset={},{}".format(mime, charset, quote(doc))
 
 class TestPageSource(MarionetteTestCase):
     def testShouldReturnTheSourceOfAPage(self):
         test_html = inline("<body><p> Check the PageSource</body>")
         self.marionette.navigate(test_html)
         source = self.marionette.page_source
         from_web_api = self.marionette.execute_script("return document.documentElement.outerHTML")
         self.assertTrue("<html" in source)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_position.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_position.py
@@ -1,23 +1,23 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 class TestPosition(MarionetteTestCase):
     def test_should_get_element_position_back(self):
         doc = """
         <head>
             <title>Rectangles</title>
             <style>
                 div {
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
@@ -1,14 +1,16 @@
 # 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/.
 
 from __future__ import absolute_import
 
+import six
+
 from marionette_driver import geckoinstance
 from marionette_driver.errors import JavascriptException
 
 from marionette_harness import (
     MarionetteTestCase,
     run_if_manage_instance,
 )
 
@@ -24,24 +26,24 @@ class TestPreferences(MarionetteTestCase
         for pref in self.prefs.values():
             self.marionette.clear_pref(pref)
 
         super(TestPreferences, self).tearDown()
 
     def test_gecko_instance_preferences(self):
         required_prefs = geckoinstance.GeckoInstance.required_prefs
 
-        for key, value in required_prefs.iteritems():
+        for key, value in six.iteritems(required_prefs):
             self.assertEqual(self.marionette.get_pref(key), value,
                              "Preference {} hasn't been set to {}".format(key, repr(value)))
 
     def test_desktop_instance_preferences(self):
         required_prefs = geckoinstance.DesktopInstance.desktop_prefs
 
-        for key, value in required_prefs.iteritems():
+        for key, value in six.iteritems(required_prefs):
             if key in ["browser.tabs.remote.autostart"]:
                 return
 
             self.assertEqual(self.marionette.get_pref(key), value,
                              "Preference {} hasn't been set to {}".format(key, value))
 
     def test_clear_pref(self):
         self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
@@ -69,17 +71,17 @@ class TestPreferences(MarionetteTestCase
         value = self.marionette.get_pref(self.prefs["int"])
         self.assertEqual(value, 42)
         self.assertEqual(type(value), int)
 
         # Test string values
         self.marionette.set_pref(self.prefs["string"], "abc")
         value = self.marionette.get_pref(self.prefs["string"])
         self.assertEqual(value, "abc")
-        self.assertTrue(isinstance(value, basestring))
+        self.assertTrue(isinstance(value, six.string_types))
 
         # Test reset value
         self.marionette.set_pref(self.prefs["string"], None)
         self.assertIsNone(self.marionette.get_pref(self.prefs["string"]))
 
     def test_get_set_pref_default_branch(self):
         pref_default = "marionette.test.pref_default1"
         self.assertIsNone(self.marionette.get_pref(self.prefs["string"]))
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs_enforce.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs_enforce.py
@@ -1,14 +1,16 @@
 # 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/.
 
 from __future__ import absolute_import
 
+import six
+
 from marionette_harness import MarionetteTestCase
 
 
 class TestEnforcePreferences(MarionetteTestCase):
 
     def setUp(self):
         super(TestEnforcePreferences, self).setUp()
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_quit_restart.py
@@ -1,25 +1,26 @@
 # 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/.
 
 from __future__ import absolute_import, print_function
 
 import sys
 import unittest
-import urllib
+
+from six.moves.urllib.parse import quote
 
 from marionette_driver import errors
 from marionette_driver.by import By
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestServerQuitApplication(MarionetteTestCase):
 
     def tearDown(self):
         if self.marionette.session is None:
             self.marionette.start_session()
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_rendered_element.py
@@ -1,18 +1,18 @@
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class RenderedElementTests(MarionetteTestCase):
 
     def test_get_computed_style_value_from_element(self):
         self.marionette.navigate(inline("""
             <div style="color: green;" id="parent">
               <p id="green">This should be green</p>
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
@@ -5,31 +5,33 @@
 from __future__ import absolute_import
 
 import base64
 import hashlib
 import imghdr
 import struct
 import tempfile
 import unittest
-import urllib
+
+import six
+from six.moves.urllib.parse import quote
 
 import mozinfo
 
 from marionette_driver import By
 from marionette_driver.errors import NoSuchWindowException
 from marionette_harness import (
     MarionetteTestCase,
     skip,
     WindowManagerMixin,
 )
 
 
 def inline(doc, mime="text/html;charset=utf-8"):
-    return "data:{0},{1}".format(mime, urllib.quote(doc))
+    return "data:{0},{1}".format(mime, quote(doc))
 
 
 box = inline("<body><div id='box'><p id='green' style='width: 50px; height: 50px; "
              "background: silver;'></p></div></body>")
 input = inline("<body><input id='text-input'></input></body>")
 long = inline("<body style='height: 300vh'><p style='margin-top: 100vh'>foo</p></body>")
 short = inline("<body style='height: 10vh'></body>")
 svg = inline("""
@@ -68,25 +70,31 @@ class ScreenCaptureTestCase(MarionetteTe
         return self.marionette.execute_script("return window.pageYOffset")
 
     @property
     def viewport_dimensions(self):
         return self.marionette.execute_script("return [window.innerWidth, window.innerHeight];")
 
     def assert_png(self, screenshot):
         """Test that screenshot is a Base64 encoded PNG file."""
+        if six.PY3 and not isinstance(screenshot, bytes):
+            screenshot = bytes(screenshot, encoding='utf-8')
         image = base64.decodestring(screenshot)
         self.assertEqual(imghdr.what("", image), "png")
 
     def assert_formats(self, element=None):
         if element is None:
             element = self.document_element
 
         screenshot_default = self.marionette.screenshot(element=element)
+        if six.PY3 and not isinstance(screenshot_default, bytes):
+            screenshot_default = bytes(screenshot_default, encoding='utf-8')
         screenshot_image = self.marionette.screenshot(element=element, format="base64")
+        if six.PY3 and not isinstance(screenshot_image, bytes):
+            screenshot_image = bytes(screenshot_image, encoding='utf-8')
         binary1 = self.marionette.screenshot(element=element, format="binary")
         binary2 = self.marionette.screenshot(element=element, format="binary")
         hash1 = self.marionette.screenshot(element=element, format="hash")
         hash2 = self.marionette.screenshot(element=element, format="hash")
 
         # Valid data should have been returned
         self.assert_png(screenshot_image)
         self.assertEqual(imghdr.what("", binary1), "png")
@@ -103,16 +111,18 @@ class ScreenCaptureTestCase(MarionetteTe
         self.assertEqual(binary1, binary2)
         self.assertEqual(hash1, hash2)
 
     def get_element_dimensions(self, element):
         rect = element.rect
         return rect["width"], rect["height"]
 
     def get_image_dimensions(self, screenshot):
+        if six.PY3 and not isinstance(screenshot, bytes):
+            screenshot = bytes(screenshot, encoding='utf-8')
         self.assert_png(screenshot)
         image = base64.decodestring(screenshot)
         width, height = struct.unpack(">LL", image[16:24])
         return int(width), int(height)
 
     def scale(self, rect):
         return (int(rect[0] * self.device_pixel_ratio),
                 int(rect[1] * self.device_pixel_ratio))
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_select.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_select.py
@@ -1,23 +1,23 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class SelectTestCase(MarionetteTestCase):
     def assertSelected(self, option_element):
         self.assertTrue(option_element.is_selected(), "<option> element not selected")
         self.assertTrue(self.marionette.execute_script(
             "return arguments[0].selected", script_args=[option_element], sandbox=None),
             "<option> selected attribute not updated")
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_session.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_session.py
@@ -1,14 +1,16 @@
 # 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/.
 
 from __future__ import absolute_import
 
+import six
+
 from marionette_driver import errors
 
 from marionette_harness import MarionetteTestCase
 
 
 class TestSession(MarionetteTestCase):
 
     def setUp(self):
@@ -33,21 +35,21 @@ class TestSession(MarionetteTestCase):
         # Optional capabilities we want Marionette to support
         self.assertIn("rotatable", caps)
 
     def test_get_session_id(self):
         # Sends newSession
         self.marionette.start_session()
 
         self.assertTrue(self.marionette.session_id is not None)
-        self.assertTrue(isinstance(self.marionette.session_id, unicode))
+        self.assertTrue(isinstance(self.marionette.session_id, six.text_type))
 
     def test_session_already_started(self):
         self.marionette.start_session()
-        self.assertTrue(isinstance(self.marionette.session_id, unicode))
+        self.assertTrue(isinstance(self.marionette.session_id, six.text_type))
         with self.assertRaises(errors.SessionNotCreatedException):
             self.marionette._send_message("WebDriver:NewSession", {})
 
     def test_no_session(self):
         with self.assertRaises(errors.InvalidSessionIdException):
             self.marionette.get_url()
 
         self.marionette.start_session()
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame.py
@@ -70,17 +70,17 @@ class TestSwitchFrame(MarionetteTestCase
         self.marionette.switch_to_frame()
         self.assertEqual(verify_title, self.marionette.title)
         self.marionette.switch_to_frame(inner_frame_element)
         self.assertTrue(start_url in self.marionette.get_url())
 
         try:
             self.marionette.execute_async_script("foo();")
         except JavascriptException as e:
-            self.assertTrue("foo" in e.message)
+            self.assertTrue("foo" in str(e))
 
     def test_should_be_able_to_carry_on_working_if_the_frame_is_deleted_from_under_us(self):
         test_html = self.marionette.absolute_url("deletingFrame.html")
         self.marionette.navigate(test_html)
 
         self.marionette.switch_to_frame(self.marionette.find_element(By.ID, 'iframe1'))
         kill_iframe = self.marionette.find_element(By.ID, "killIframe")
         kill_iframe.click()
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_switch_frame_chrome.py
@@ -44,9 +44,9 @@ class TestSwitchFrameChrome(WindowManage
 
     def test_stack_trace(self):
         self.assertIn("test.xhtml", self.marionette.get_url(), "Initial navigation has failed")
         self.marionette.switch_to_frame(0)
         self.assertRaises(JavascriptException, self.marionette.execute_async_script, "foo();")
         try:
             self.marionette.execute_async_script("foo();")
         except JavascriptException as e:
-            self.assertIn("foo", e.message)
+            self.assertIn("foo", str(e))
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_title.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_title.py
@@ -1,22 +1,22 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_harness import MarionetteTestCase, skip
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestTitle(MarionetteTestCase):
     @property
     def document_title(self):
         return self.marionette.execute_script("return document.title", sandbox=None)
 
     def test_title_from_top(self):
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_typing.py
@@ -1,25 +1,25 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_driver.errors import ElementNotInteractableException
 from marionette_driver.keys import Keys
 
 from marionette_harness import MarionetteTestCase, skip
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TypingTestCase(MarionetteTestCase):
 
     def setUp(self):
         super(TypingTestCase, self).setUp()
 
         if self.marionette.session_capabilities["platformName"] == "mac":
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_visibility.py
@@ -1,23 +1,23 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 
 from marionette_harness import MarionetteTestCase
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 def element_direction_doc(direction):
     return inline("""
         <meta name="viewport" content="initial-scale=1,width=device-width">
         <style>
           .element{{
             position: absolute;
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_wait.py
@@ -2,16 +2,18 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import sys
 import time
 
+import six
+
 from marionette_driver import errors, wait
 from marionette_driver.wait import Wait
 
 from marionette_harness import MarionetteTestCase
 
 
 class TickingClock(object):
 
@@ -174,17 +176,17 @@ class FormalWaitTest(MarionetteTestCase)
         wt = Wait(self.m)
         self.assertEqual(wt.timeout, wait.DEFAULT_TIMEOUT)
 
 
 class PredicatesTest(MarionetteTestCase):
 
     def test_until(self):
         c = wait.SystemClock()
-        self.assertFalse(wait.until_pred(c, sys.maxint))
+        self.assertFalse(wait.until_pred(c, six.MAXSIZE))
         self.assertTrue(wait.until_pred(c, 0))
 
 
 class WaitUntilTest(MarionetteTestCase):
 
     def setUp(self):
         super(WaitUntilTest, self).setUp()
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_close_content.py
@@ -1,22 +1,22 @@
 # 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/.
 
 from __future__ import absolute_import
 
-import urllib
+from six.moves.urllib.parse import quote
 
 from marionette_driver.by import By
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestCloseWindow(WindowManagerMixin, MarionetteTestCase):
 
     def tearDown(self):
         self.close_all_windows()
         self.close_all_tabs()
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_chrome.py
@@ -1,16 +1,18 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import types
 
+import six
+
 from marionette_driver import errors
 
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
@@ -23,26 +25,26 @@ class TestWindowHandles(WindowManagerMix
     def tearDown(self):
         self.close_all_windows()
         self.close_all_tabs()
 
         super(TestWindowHandles, self).tearDown()
 
     def assert_window_handles(self):
         try:
-            self.assertIsInstance(self.marionette.current_chrome_window_handle, types.StringTypes)
-            self.assertIsInstance(self.marionette.current_window_handle, types.StringTypes)
+            self.assertIsInstance(self.marionette.current_chrome_window_handle,  six.string_types)
+            self.assertIsInstance(self.marionette.current_window_handle,  six.string_types)
         except errors.NoSuchWindowException:
             pass
 
         for handle in self.marionette.chrome_window_handles:
-            self.assertIsInstance(handle, types.StringTypes)
+            self.assertIsInstance(handle,  six.string_types)
 
         for handle in self.marionette.window_handles:
-            self.assertIsInstance(handle, types.StringTypes)
+            self.assertIsInstance(handle,  six.string_types)
 
     def test_chrome_window_handles_with_scopes(self):
         new_browser = self.open_window()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.chrome_window_handles), len(self.start_windows) + 1)
         self.assertIn(new_browser, self.marionette.chrome_window_handles)
         self.assertEqual(self.marionette.current_chrome_window_handle, self.start_window)
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_handles_content.py
@@ -1,46 +1,48 @@
 # 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/.
 
 from __future__ import absolute_import
 
 import types
-import urllib
+
+import six
+from six.moves.urllib.parse import quote
 
 from marionette_driver import errors
 
 from marionette_harness import MarionetteTestCase, WindowManagerMixin
 
 
 def inline(doc):
-    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
+    return "data:text/html;charset=utf-8,{}".format(quote(doc))
 
 
 class TestWindowHandles(WindowManagerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestWindowHandles, self).setUp()
 
         self.chrome_dialog = "chrome://marionette/content/test_dialog.xhtml"
 
     def tearDown(self):
         self.close_all_tabs()
 
         super(TestWindowHandles, self).tearDown()
 
     def assert_window_handles(self):
         try:
-            self.assertIsInstance(self.marionette.current_window_handle, types.StringTypes)
+            self.assertIsInstance(self.marionette.current_window_handle, six.string_types)
         except errors.NoSuchWindowException:
             pass
 
         for handle in self.marionette.window_handles:
-            self.assertIsInstance(handle, types.StringTypes)
+            self.assertIsInstance(handle,  six.string_types)
 
     def tst_window_handles_after_opening_new_tab(self):
         new_tab = self.open_tab()
         self.assert_window_handles()
         self.assertEqual(len(self.marionette.window_handles), len(self.start_tabs) + 1)
         self.assertEqual(self.marionette.current_window_handle, self.start_tab)
 
         self.marionette.switch_to_window(new_tab)