Bug 1323770 - Fix skip decorators for unit tests. draft
authorHenrik Skupin <mail@hskupin.info>
Thu, 29 Dec 2016 09:22:00 +0100
changeset 454412 a2ba031cd12797e61c40bdfc5553a901f40173c5
parent 454408 d2dcace560603d691f2d3345b7b05ae7bb93908c
child 454413 21fd9a20138642eea09a1b30c8b629aa3fd7d044
push id39920
push userbmo:hskupin@gmail.com
push dateThu, 29 Dec 2016 08:25:24 +0000
bugs1323770
milestone53.0a1
Bug 1323770 - Fix skip decorators for unit tests. Marionette's skip decorators are currently not conform with the ones from the Python's unittest module, which require a reason as parameter. As such Marionette should behave the same and should also require a reason for more detailed skip messages. This is done by wrapping the actual decorator with another enclosing method. With the changes we also ensure that the wrapper has the same attributes as the wrapped function by using functools.wraps(). This hasn't used so far and makes debugging harder. Further a couple of skip methods and classes were copied from the unittest module, which should be better imported instead to reduce code duplication. MozReview-Commit-ID: 6XT6M6cbCFW
testing/firefox-ui/tests/functional/security/test_ssl_status_after_restart.py
testing/marionette/harness/marionette_harness/marionette_test/__init__.py
testing/marionette/harness/marionette_harness/marionette_test/decorators.py
testing/marionette/harness/marionette_harness/marionette_test/errors.py
testing/marionette/harness/marionette_harness/marionette_test/testcases.py
testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.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_import_script.py
testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py
testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
testing/marionette/harness/marionette_harness/tests/unit/test_transport.py
--- a/testing/firefox-ui/tests/functional/security/test_ssl_status_after_restart.py
+++ b/testing/firefox-ui/tests/functional/security/test_ssl_status_after_restart.py
@@ -41,17 +41,17 @@ class TestSSLStatusAfterRestart(Puppetee
             self.puppeteer.windows.close_all([self.browser])
             self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
             self.browser.switch_to()
             self.identity_popup.close(force=True)
             self.marionette.clear_pref('browser.startup.page')
         finally:
             super(TestSSLStatusAfterRestart, self).tearDown()
 
-    @skip_if_e10s
+    @skip_if_e10s("Bug 1325047")
     def test_ssl_status_after_restart(self):
         for item in self.test_data:
             with self.marionette.using_context('content'):
                 self.marionette.navigate(item['url'])
             self.verify_certificate_status(item)
             self.browser.tabbar.open_tab()
 
         self.restart()
--- a/testing/marionette/harness/marionette_harness/marionette_test/__init__.py
+++ b/testing/marionette/harness/marionette_harness/marionette_test/__init__.py
@@ -1,30 +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/.
 
 __version__ = '3.1.0'
 
 
+from unittest.case import (
+    expectedFailure,
+    skip,
+    SkipTest,
+)
+
 from .decorators import (
-    expectedFailure,
     parameterized,
     run_if_e10s,
-    skip,
     skip_if_chrome,
     skip_if_desktop,
     skip_if_e10s,
     skip_if_mobile,
     skip_unless_browser_pref,
     skip_unless_protocol,
     with_parameters,
 )
 
-from .errors import (
-    SkipTest,
-)
-
 from .testcases import (
     CommonTestCase,
     MarionetteTestCase,
     MetaParameterized,
 )
--- a/testing/marionette/harness/marionette_harness/marionette_test/decorators.py
+++ b/testing/marionette/harness/marionette_harness/marionette_test/decorators.py
@@ -1,35 +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/.
 
 import functools
-import sys
 import types
 
-from .errors import (
-    _ExpectedFailure,
-    _UnexpectedSuccess,
+from unittest.case import (
     SkipTest,
 )
 
 
-def expectedFailure(func):
-    """Decorator which marks a test as expected fail."""
-    @functools.wraps(func)
-    def wrapper(*args, **kwargs):
-        try:
-            func(*args, **kwargs)
-        except Exception:
-            raise _ExpectedFailure(sys.exc_info())
-        raise _UnexpectedSuccess
-    return wrapper
-
-
 def parameterized(func_suffix, *args, **kwargs):
     r"""Decorator which generates methods given a base method and some data.
 
     **func_suffix** is used as a suffix for the new created method and must be
     unique given a base method. if **func_suffix** countains characters that
     are not allowed in normal python function name, these characters will be
     replaced with "_".
 
@@ -56,132 +41,161 @@ def parameterized(func_suffix, *args, **
     def wrapped(func):
         if not hasattr(func, 'metaparameters'):
             func.metaparameters = []
         func.metaparameters.append((func_suffix, args, kwargs))
         return func
     return wrapped
 
 
-def run_if_e10s(target):
+def run_if_e10s(reason):
     """Decorator which runs a test if e10s mode is active."""
-    def wrapper(self, *args, **kwargs):
-        with self.marionette.using_context('chrome'):
-            multi_process_browser = self.marionette.execute_script("""
-            try {
-              return Services.appinfo.browserTabsRemoteAutostart;
-            } catch (e) {
-              return false;
-            }""")
+    def decorator(test_item):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
 
-        if not multi_process_browser:
-            raise SkipTest('skipping due to e10s is disabled')
-        return target(self, *args, **kwargs)
-    return wrapper
+        @functools.wraps(test_item)
+        def skip_wrapper(self, *args, **kwargs):
+            with self.marionette.using_context('chrome'):
+                multi_process_browser = not self.marionette.execute_script("""
+                    try {
+                      return Services.appinfo.browserTabsRemoteAutostart;
+                    } catch (e) {
+                      return false;
+                    }
+                """)
+                if multi_process_browser:
+                    raise SkipTest(reason)
+            return test_item(self, *args, **kwargs)
+        return skip_wrapper
+    return decorator
 
 
-def skip(reason):
-    """Decorator which unconditionally skips a test."""
+def skip_if_chrome(reason):
+    """Decorator which skips a test if chrome context is active."""
     def decorator(test_item):
-        if not isinstance(test_item, (type, types.ClassType)):
-            @functools.wraps(test_item)
-            def skip_wrapper(*args, **kwargs):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
+
+        @functools.wraps(test_item)
+        def skip_wrapper(self, *args, **kwargs):
+            if self.marionette._send_message('getContext', key='value') == 'chrome':
                 raise SkipTest(reason)
-            test_item = skip_wrapper
-
-        test_item.__unittest_skip__ = True
-        test_item.__unittest_skip_why__ = reason
-        return test_item
+            return test_item(self, *args, **kwargs)
+        return skip_wrapper
     return decorator
 
 
-def skip_if_chrome(target):
-    """Decorator which skips a test if chrome context is active."""
-    def wrapper(self, *args, **kwargs):
-        if self.marionette._send_message("getContext", key="value") == "chrome":
-            raise SkipTest("skipping test in chrome context")
-        return target(self, *args, **kwargs)
-    return wrapper
+def skip_if_desktop(reason):
+    """Decorator which skips a test if run on desktop."""
+    def decorator(test_item):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
 
-
-def skip_if_desktop(target):
-    """Decorator which skips a test if run on desktop."""
-    def wrapper(self, *args, **kwargs):
-        if self.marionette.session_capabilities.get('browserName') == 'firefox':
-            raise SkipTest('skipping due to desktop')
-        return target(self, *args, **kwargs)
-    return wrapper
+        @functools.wraps(test_item)
+        def skip_wrapper(self, *args, **kwargs):
+            if self.marionette.session_capabilities.get('browserName') == 'firefox':
+                raise SkipTest(reason)
+            return test_item(self, *args, **kwargs)
+        return skip_wrapper
+    return decorator
 
 
-def skip_if_e10s(target):
+def skip_if_e10s(reason):
     """Decorator which skips a test if e10s mode is active."""
-    def wrapper(self, *args, **kwargs):
-        with self.marionette.using_context('chrome'):
-            multi_process_browser = self.marionette.execute_script("""
-            try {
-              return Services.appinfo.browserTabsRemoteAutostart;
-            } catch (e) {
-              return false;
-            }""")
+    def decorator(test_item):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
 
-        if multi_process_browser:
-            raise SkipTest('skipping due to e10s')
-        return target(self, *args, **kwargs)
-    return wrapper
+        @functools.wraps(test_item)
+        def skip_wrapper(self, *args, **kwargs):
+            with self.marionette.using_context('chrome'):
+                multi_process_browser = self.marionette.execute_script("""
+                    try {
+                      return Services.appinfo.browserTabsRemoteAutostart;
+                    } catch (e) {
+                      return false;
+                    }
+                """)
+                if multi_process_browser:
+                    raise SkipTest(reason)
+            return test_item(self, *args, **kwargs)
+        return skip_wrapper
+    return decorator
 
 
-def skip_if_mobile(target):
+def skip_if_mobile(reason):
     """Decorator which skips a test if run on mobile."""
-    def wrapper(self, *args, **kwargs):
-        if self.marionette.session_capabilities.get('browserName') == 'fennec':
-            raise SkipTest('skipping due to fennec')
-        return target(self, *args, **kwargs)
-    return wrapper
+    def decorator(test_item):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
+
+        @functools.wraps(test_item)
+        def skip_wrapper(self, *args, **kwargs):
+            if self.marionette.session_capabilities.get('browserName') == 'fennec':
+                raise SkipTest(reason)
+            return test_item(self, *args, **kwargs)
+        return skip_wrapper
+    return decorator
 
 
-def skip_unless_browser_pref(pref, predicate=bool):
+def skip_unless_browser_pref(reason, pref, predicate=bool):
     """Decorator which skips a test based on the value of a browser preference.
 
+    :param reason: Message describing why the test need to be skipped.
     :param pref: the preference name
     :param predicate: a function that should return false to skip the test.
                       The function takes one parameter, the preference value.
                       Defaults to the python built-in bool function.
 
     Note that the preference must exist, else a failure is raised.
 
     Example: ::
 
       class TestSomething(MarionetteTestCase):
-          @skip_unless_browser_pref("accessibility.tabfocus",
-                                    lambda value: value >= 7)
+          @skip_unless_browser_pref("Sessionstore needs to be enabled for crashes",
+                                    "browser.sessionstore.resume_from_crash",
+                                    lambda value: value is True,
+                                    )
           def test_foo(self):
               pass  # test implementation here
+
     """
-    def wrapper(target):
-        @functools.wraps(target)
-        def wrapped(self, *args, **kwargs):
+    def decorator(test_item):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
+        if not callable(predicate):
+            raise ValueError('predicate must be callable')
+
+        @functools.wraps(test_item)
+        def skip_wrapper(self, *args, **kwargs):
             value = self.marionette.get_pref(pref)
             if value is None:
                 self.fail("No such browser preference: {0!r}".format(pref))
             if not predicate(value):
-                raise SkipTest("browser preference {0!r}: {1!r}".format((pref, value)))
-            return target(self, *args, **kwargs)
-        return wrapped
-    return wrapper
+                raise SkipTest(reason)
+            return test_item(self, *args, **kwargs)
+        return skip_wrapper
+    return decorator
 
 
-def skip_unless_protocol(predicate):
+def skip_unless_protocol(reason, predicate):
     """Decorator which skips a test if the predicate does not match the current protocol level."""
     def decorator(test_item):
+        if not isinstance(test_item, types.FunctionType):
+            raise Exception('Decorator only supported for functions')
+        if not callable(predicate):
+            raise ValueError('predicate must be callable')
+
         @functools.wraps(test_item)
-        def skip_wrapper(self):
+        def skip_wrapper(self, *args, **kwargs):
             level = self.marionette.client.protocol
             if not predicate(level):
-                raise SkipTest('skipping because protocol level is {}'.format(level))
-            return test_item(self)
+                raise SkipTest(reason)
+            return test_item(self, *args, **kwargs)
         return skip_wrapper
     return decorator
 
 
 def with_parameters(parameters):
     """Decorator which generates methods given a base method and some data.
 
     Acts like :func:`parameterized`, but define all methods in one call.
deleted file mode 100644
--- a/testing/marionette/harness/marionette_harness/marionette_test/errors.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# 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/.
-
-
-class SkipTest(Exception):
-    """
-    Raise this exception in a test to skip it.
-
-    Usually you can use TestResult.skip() or one of the skipping decorators
-    instead of raising this directly.
-    """
-
-    pass
-
-
-class _ExpectedFailure(Exception):
-    """
-    Raise this when a test is expected to fail.
-
-    This is an implementation detail.
-    """
-
-    def __init__(self, exc_info):
-        super(_ExpectedFailure, self).__init__()
-        self.exc_info = exc_info
-
-
-class _UnexpectedSuccess(Exception):
-    """The test was supposed to fail, but it didn't."""
-
-    pass
--- a/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
+++ b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
@@ -7,29 +7,29 @@ import os
 import re
 import sys
 import time
 import types
 import unittest
 import warnings
 import weakref
 
+from unittest.case import (
+    _ExpectedFailure,
+    _UnexpectedSuccess,
+    SkipTest,
+)
+
 from marionette_driver.errors import (
     MarionetteException,
     ScriptTimeoutException,
     TimeoutException,
 )
 from mozlog import get_default_logger
 
-from .errors import (
-    _ExpectedFailure,
-    _UnexpectedSuccess,
-    SkipTest,
-)
-
 
 def _wraps_parameterized(func, func_suffix, args, kwargs):
     """Internal: Decorator used in class MetaParameterized."""
     def wrapper(self):
         return func(self, *args, **kwargs)
     wrapper.__name__ = func.__name__ + '_' + str(func_suffix)
     wrapper.__doc__ = '[{0}] {1}'.format(func_suffix, func.__doc__)
     return wrapper
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_about_pages.py
@@ -21,17 +21,17 @@ class TestAboutPages(WindowManagerMixin,
         self.remote_uri = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(self.remote_uri)
 
     def tearDown(self):
         self.close_all_tabs()
 
         super(TestAboutPages, self).tearDown()
 
-    @skip_if_mobile  # Bug 1323185 - Add Fennec support to getWindowHandles
+    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
     def test_back_forward(self):
         # Bug 1311041 - Prevent changing of window handle by forcing the test
         # to be run in a new tab.
         new_tab = self.open_tab(trigger='menu')
         self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate("about:blank")
         self.marionette.navigate(self.remote_uri)
@@ -41,74 +41,74 @@ class TestAboutPages(WindowManagerMixin,
         self.marionette.go_back()
 
         self.wait_for_condition(
             lambda mn: mn.get_url() == self.remote_uri)
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
-    @skip_if_mobile  # Bug 1323185 - Add Fennec support to getWindowHandles
+    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
     def test_navigate_non_remote_about_pages(self):
         # Bug 1311041 - Prevent changing of window handle by forcing the test
         # to be run in a new tab.
         new_tab = self.open_tab(trigger='menu')
         self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate("about:blank")
         self.assertEqual(self.marionette.get_url(), "about:blank")
         self.marionette.navigate("about:preferences")
         self.assertEqual(self.marionette.get_url(), "about:preferences")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
-    @skip_if_mobile  # On Android no shortcuts are available
+    @skip_if_mobile("On Android no shortcuts are available")
     def test_navigate_shortcut_key(self):
         def open_with_shortcut():
             with self.marionette.using_context("chrome"):
                 main_win = self.marionette.find_element(By.ID, "main-window")
                 main_win.send_keys(self.mod_key, Keys.SHIFT, 'a')
 
         new_tab = self.open_tab(trigger=open_with_shortcut)
         self.marionette.switch_to_window(new_tab)
 
         self.wait_for_condition(lambda mn: mn.get_url() == "about:addons")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
-    @skip_if_mobile  # Bug 1323185 - Add Fennec support to getWindowHandles
+    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
     def test_type_to_non_remote_tab(self):
         # Bug 1311041 - Prevent changing of window handle by forcing the test
         # to be run in a new tab.
         new_tab = self.open_tab(trigger='menu')
         self.marionette.switch_to_window(new_tab)
 
         with self.marionette.using_context("chrome"):
             urlbar = self.marionette.find_element(By.ID, 'urlbar')
             urlbar.send_keys(self.mod_key + 'a')
             urlbar.send_keys(self.mod_key + 'x')
             urlbar.send_keys('about:preferences' + Keys.ENTER)
         self.wait_for_condition(lambda mn: mn.get_url() == "about:preferences")
 
         self.marionette.close()
         self.marionette.switch_to_window(self.start_tab)
 
-    @skip_if_mobile  # Interacting with chrome elements not available for Fennec
+    @skip_if_mobile("Interacting with chrome elements not available for Fennec")
     def test_type_to_remote_tab(self):
         # about:blank keeps remoteness from remote_uri
         self.marionette.navigate("about:blank")
         with self.marionette.using_context("chrome"):
             urlbar = self.marionette.find_element(By.ID, 'urlbar')
             urlbar.send_keys(self.remote_uri + Keys.ENTER)
 
         self.wait_for_condition(lambda mn: mn.get_url() == self.remote_uri)
 
-    @skip_if_mobile  # Bug 1323185 - Add Fennec support to getWindowHandles
+    @skip_if_mobile("Bug 1323185 - Add Fennec support to getWindowHandles")
     def test_hang(self):
         # Open a new tab and close the first one
         new_tab = self.open_tab(trigger="menu")
 
         self.marionette.close()
         self.marionette.switch_to_window(new_tab)
 
         self.marionette.navigate(self.remote_uri)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click_scrolling.py
@@ -38,17 +38,17 @@ class TestClickScrolling(MarionetteTestC
         self.marionette.navigate(test_html)
 
         link = self.marionette.find_element(By.ID, "link")
         try:
             link.click()
         except MoveTargetOutOfBoundsException:
             self.fail("Should not be out of bounds")
 
-    @skip("Bug 1003682")
+    @skip("Bug 1200197 - Cannot interact with elements hidden inside overflow:scroll")
     def test_should_be_able_to_click_on_an_element_hidden_by_overflow(self):
         test_html = self.marionette.absolute_url("scroll.html")
         self.marionette.navigate(test_html)
 
         link = self.marionette.find_element(By.ID, "line8")
         link.click()
         self.assertEqual("line8", self.marionette.find_element(By.ID, "clicked").text)
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_crash.py
@@ -99,17 +99,17 @@ class TestCrash(BaseCrashTestCase):
 
         self.marionette.start_session()
         self.assertNotEqual(self.marionette.process_id, self.pid)
 
         # TODO: Bug 1314594 - Causes a hang for the communication between the
         # chrome and frame script.
         # self.marionette.get_url()
 
-    @run_if_e10s
+    @run_if_e10s("Content crashes only exist in e10s mode")
     def test_crash_content_process(self):
         # If e10s is disabled the chrome process crashes
         self.marionette.navigate(self.remote_uri)
 
         self.assertRaisesRegexp(IOError, 'Content process crashed',
                                 self.crash, chrome=False)
         self.assertEqual(self.marionette.crashed, 1)
         self.assertIsNone(self.marionette.session)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_import_script.py
@@ -26,16 +26,21 @@ class TestImportScriptContent(WindowMana
     def setUp(self):
         super(TestImportScriptContent, self).setUp()
 
         for context in self.contexts:
             with self.marionette.using_context(context):
                 self.marionette.clear_imported_scripts()
         self.reset_context()
 
+    def tearDown(self):
+        self.close_all_windows()
+
+        super(TestImportScriptContent, self).tearDown()
+
     def reset_context(self):
         self.marionette.set_context("content")
 
     @property
     def current_context(self):
         return self.marionette._send_message("getContext", key="value")
 
     @property
@@ -104,18 +109,18 @@ class TestImportScriptContent(WindowMana
         self.assert_defined("testFunc")
 
     def test_multiple_imports(self):
         self.marionette.import_script(self.script_file)
         self.marionette.import_script(self.another_script_file)
         self.assert_defined("testFunc")
         self.assert_defined("testAnotherFunc")
 
-    @skip_if_chrome
-    @skip_if_mobile  # New windows not supported in Fennec
+    @skip_if_chrome("Needs content scope")
+    @skip_if_mobile("New windows not supported in Fennec")
     def test_imports_apply_globally(self):
         self.marionette.navigate(
             self.marionette.absolute_url("test_windows.html"))
 
         def open_window_with_link():
             self.marionette.find_element(By.LINK_TEXT, "Open new window").click()
 
         new_window = self.open_window(trigger=open_window_with_link)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_modal_dialogs.py
@@ -152,17 +152,17 @@ class TestTabModals(MarionetteTestCase):
             """))
         self.marionette.navigate("about:blank")
         self.wait_for_alert()
         alert = self.marionette.switch_to_alert()
         self.assertTrue(alert.text.startswith("This page is asking you to confirm"))
         alert.accept()
         self.wait_for_condition(lambda mn: mn.get_url() == "about:blank")
 
-    @skip_if_e10s
+    @skip_if_e10s("Bug 1325044")
     def test_unrelated_command_when_alert_present(self):
         click_handler = self.marionette.find_element(By.ID, 'click-handler')
         text = self.marionette.find_element(By.ID, 'click-result').text
         self.assertEqual(text, '')
 
         self.marionette.find_element(By.ID, 'modal-alert').click()
         self.wait_for_alert()
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screen_orientation.py
@@ -18,69 +18,69 @@ class TestScreenOrientation(MarionetteTe
         self.is_mobile = self.marionette.session_capabilities.get("rotatable", False)
 
     def tearDown(self):
         if self.is_mobile:
             self.marionette.set_orientation(default_orientation)
             self.assertEqual(self.marionette.orientation, default_orientation, "invalid state")
         MarionetteTestCase.tearDown(self)
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_to_portrait_primary(self):
         self.marionette.set_orientation("portrait-primary")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "portrait-primary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_to_landscape_primary(self):
         self.marionette.set_orientation("landscape-primary")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "landscape-primary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_to_portrait_secondary(self):
         self.marionette.set_orientation("portrait-secondary")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "portrait-secondary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_to_landscape_secondary(self):
         self.marionette.set_orientation("landscape-secondary")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "landscape-secondary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_to_shorthand_portrait(self):
         # Set orientation to something other than portrait-primary first, since the default is
         # portrait-primary.
         self.marionette.set_orientation("landscape-primary")
         self.assertEqual(self.marionette.orientation, "landscape-primary", "invalid state")
 
         self.marionette.set_orientation("portrait")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "portrait-primary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_to_shorthand_landscape(self):
         self.marionette.set_orientation("landscape")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "landscape-primary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_orientation_with_mixed_casing(self):
         self.marionette.set_orientation("lAnDsCaPe")
         new_orientation = self.marionette.orientation
         self.assertEqual(new_orientation, "landscape-primary")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_invalid_orientation(self):
         with self.assertRaisesRegexp(errors.MarionetteException, unknown_orientation.format("cheese")):
             self.marionette.set_orientation("cheese")
 
-    @skip_if_desktop
+    @skip_if_desktop("Not supported in Firefox")
     def test_set_null_orientation(self):
         with self.assertRaisesRegexp(errors.MarionetteException, unknown_orientation.format("null")):
             self.marionette.set_orientation(None)
 
-    @skip_if_mobile
+    @skip_if_mobile("Specific test for Firefox")
     def test_unsupported_operation_on_desktop(self):
         with self.assertRaises(errors.UnsupportedOperationException):
             self.marionette.set_orientation("landscape-primary")
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_screenshot.py
@@ -3,21 +3,24 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import base64
 import hashlib
 import imghdr
 import struct
 import urllib
 
-from unittest import skip
-
 from marionette_driver import By
 from marionette_driver.errors import JavascriptException, NoSuchWindowException
-from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
+from marionette_harness import (
+    MarionetteTestCase,
+    skip,
+    skip_if_mobile,
+    WindowManagerMixin,
+)
 
 
 def inline(doc, mime="text/html;charset=utf-8"):
     return "data:{0},{1}".format(mime, urllib.quote(doc))
 
 
 box = inline("<body><div id='box'><p id='green' style='width: 50px; height: 50px; "
              "background: silver;'></p></div></body>")
@@ -144,17 +147,17 @@ class TestScreenCaptureChrome(WindowMana
 
     def test_capture_different_context(self):
         """Check that screenshots in content and chrome are different."""
         with self.marionette.using_context("content"):
             screenshot_content = self.marionette.screenshot()
         screenshot_chrome = self.marionette.screenshot()
         self.assertNotEqual(screenshot_content, screenshot_chrome)
 
-    @skip_if_mobile
+    @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_capture_element(self):
         dialog = self.open_dialog()
         self.marionette.switch_to_window(dialog)
 
         # Ensure we only capture the element
         el = self.marionette.find_element(By.ID, "test-list")
         screenshot_element = self.marionette.screenshot(element=el)
         self.assertEqual(self.scale(self.get_element_dimensions(el)),
@@ -207,17 +210,17 @@ class TestScreenCaptureChrome(WindowMana
         self.assertEqual(self.scale(self.viewport_dimensions),
                          self.get_image_dimensions(screenshot))
         self.assertNotEqual(self.scale(self.window_dimensions),
                             self.get_image_dimensions(screenshot))
 
         self.marionette.close_chrome_window()
         self.marionette.switch_to_window(self.start_window)
 
-    @skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1213875")
+    @skip("Bug 1213875")
     def test_capture_scroll_element_into_view(self):
         pass
 
     def test_capture_window_already_closed(self):
         dialog = self.open_dialog()
         self.marionette.switch_to_window(dialog)
         self.marionette.close_chrome_window()
 
@@ -232,17 +235,17 @@ class TestScreenCaptureChrome(WindowMana
 
         self.marionette.close_chrome_window()
         self.marionette.switch_to_window(self.start_window)
 
     def test_format_unknown(self):
         with self.assertRaises(ValueError):
             self.marionette.screenshot(format="cheese")
 
-    @skip_if_mobile
+    @skip_if_mobile("Fennec doesn't support other chrome windows")
     def test_highlight_elements(self):
         dialog = self.open_dialog()
         self.marionette.switch_to_window(dialog)
 
         # Highlighting the element itself shouldn't make the image larger
         element = self.marionette.find_element(By.ID, "test-list")
         screenshot_element = self.marionette.screenshot(element=element)
         screenshot_highlight = self.marionette.screenshot(element=element,
@@ -288,34 +291,34 @@ class TestScreenCaptureContent(WindowMan
         super(TestScreenCaptureContent, self).tearDown()
 
     @property
     def scroll_dimensions(self):
         return tuple(self.marionette.execute_script("""
             return [document.body.scrollWidth, document.body.scrollHeight]
             """))
 
-    @skip_if_mobile  # Needs application independent method to open a new tab
+    @skip_if_mobile("Needs application independent method to open a new tab")
     def test_capture_tab_already_closed(self):
         tab = self.open_tab()
         self.marionette.switch_to_window(tab)
         self.marionette.close()
 
         self.assertRaises(NoSuchWindowException, self.marionette.screenshot)
         self.marionette.switch_to_window(self.start_tab)
 
     def test_capture_element(self):
         self.marionette.navigate(box)
         el = self.marionette.find_element(By.TAG_NAME, "div")
         screenshot = self.marionette.screenshot(element=el)
         self.assert_png(screenshot)
         self.assertEqual(self.scale(self.get_element_dimensions(el)),
                          self.get_image_dimensions(screenshot))
 
-    @skip("https://bugzilla.mozilla.org/show_bug.cgi?id=1213875")
+    @skip("Bug 1213875")
     def test_capture_element_scrolled_into_view(self):
         self.marionette.navigate(long)
         el = self.marionette.find_element(By.TAG_NAME, "p")
         screenshot = self.marionette.screenshot(element=el)
         self.assert_png(screenshot)
         self.assertEqual(self.scale(self.get_element_dimensions(el)),
                          self.get_image_dimensions(screenshot))
         self.assertGreater(self.page_y_offset, 0)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_transport.py
@@ -28,24 +28,24 @@ class TestMessageSequencing(MarionetteTe
         self.marionette.client.last_id = new_id
 
     def send(self, name, params):
         self.last_id = self.last_id + 1
         cmd = Command(self.last_id, name, params)
         self.marionette.client.send(cmd)
         return self.last_id
 
-    @skip_unless_protocol(lambda level: level >= 3)
+    @skip_unless_protocol("Skip for level < 3", lambda level: level >= 3)
     def test_discard_older_messages(self):
         first = self.send(*get_current_url)
         second = self.send(*execute_script)
         resp = self.marionette.client.receive()
         self.assertEqual(second, resp.id)
 
-    @skip_unless_protocol(lambda level: level >= 3)
+    @skip_unless_protocol("Skip for level < 3", lambda level: level >= 3)
     def test_last_id_incremented(self):
         before = self.last_id
         self.send(*get_current_url)
         self.assertGreater(self.last_id, before)
 
 
 class MessageTestCase(MarionetteTestCase):
     def assert_attr(self, obj, attr):