Bug 1429759 - Allow Marionette tests to change the user profile. draft
authorHenrik Skupin <mail@hskupin.info>
Thu, 11 Jan 2018 15:21:11 +0100
changeset 737941 16df1d1c274c27861ac2e7251f3776ac4a61ee07
parent 723510 94472d41963d1ce2d76b18965f7b0064fc75fbb7
push id96800
push userbmo:hskupin@gmail.com
push dateThu, 25 Jan 2018 11:26:17 +0000
bugs1429759
milestone60.0a1
Bug 1429759 - Allow Marionette tests to change the user profile. If Marionette handles the instance of the application including the user profile, the tests should be able to change the user profile. This will enable tests to cover specific bugs in the application as caused by the profile name and path. To prevent dataloss changing the profile should only be allowed when the application is not running. MozReview-Commit-ID: JWQGV4JWP61
testing/marionette/client/marionette_driver/geckoinstance.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_profile_management.py
testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -14,16 +14,18 @@ from copy import deepcopy
 
 import mozversion
 
 from mozdevice import DMError
 from mozprofile import Profile
 from mozrunner import Runner, FennecEmulatorRunner
 from six import reraise
 
+from . import errors
+
 
 class GeckoInstance(object):
     required_prefs = {
         # Increase the APZ content response timeout in tests to 1 minute.
         # This is to accommodate the fact that test environments tends to be slower
         # than production environments (with the b2g emulator being the slowest of them
         # all), resulting in the production timeout value sometimes being exceeded
         # and causing false-positive test failures. See bug 1176798, bug 1177018,
@@ -122,37 +124,38 @@ class GeckoInstance(object):
         self.runner_class = Runner
         self.app_args = app_args or []
         self.runner = None
         self.symbols_path = symbols_path
         self.binary = bin
 
         self.marionette_host = host
         self.marionette_port = port
-        # Alternative to default temporary directory
-        self.workspace = workspace
         self.addons = addons
-        # Check if it is a Profile object or a path to profile
-        self.profile = None
-        if isinstance(profile, Profile):
-            self.profile = profile
-        else:
-            self.profile_path = profile
         self.prefs = prefs
         self.required_prefs = deepcopy(self.required_prefs)
         if prefs:
             self.required_prefs.update(prefs)
 
         self._gecko_log_option = gecko_log
         self._gecko_log = None
         self.verbose = verbose
         self.headless = headless
+
         # keep track of errors to decide whether instance is unresponsive
         self.unresponsive_count = 0
 
+        # Alternative to default temporary directory
+        self.workspace = workspace
+
+        # Don't use the 'profile' property here, because sub-classes could add
+        # further preferences and data, which would not be included in the new
+        # profile
+        self._profile = profile
+
     @property
     def gecko_log(self):
         if self._gecko_log:
             return self._gecko_log
 
         path = self._gecko_log_option
         if path != "-":
             if path is None:
@@ -163,52 +166,110 @@ class GeckoInstance(object):
 
             path = os.path.realpath(path)
             if os.access(path, os.F_OK):
                 os.remove(path)
 
         self._gecko_log = path
         return self._gecko_log
 
-    def _update_profile(self):
-        profile_args = {"preferences": deepcopy(self.required_prefs)}
-        profile_args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port
+    @property
+    def profile(self):
+        return self._profile
+
+    @profile.setter
+    def profile(self, value):
+        self._update_profile(value)
+
+    def _update_profile(self, profile=None, profile_name=None):
+        """Check if the profile has to be created, or replaced
+
+        :param profile: A Profile instance to be used.
+        :param name: Profile name to be used in the path.
+        """
+        if self.runner and self.runner.is_running():
+            raise errors.MarionetteException("The current profile can only be updated "
+                                             "when the instance is not running")
+
+        if isinstance(profile, Profile):
+            # Only replace the profile if it is not the current one
+            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):
+                profile_args["path_from"] = profile_path
+                profile_args["path_to"] = tempfile.mkdtemp(
+                    suffix=".{}".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)
+
+            # Otherwise create a new profile
+            else:
+                profile_args["profile"] = tempfile.mkdtemp(
+                    suffix=".{}".format(profile_name or "mozrunner"),
+                    dir=self.workspace)
+                profile = Profile(**profile_args)
+                profile.create_new = True
+
+        if isinstance(self.profile, Profile):
+            self.profile.cleanup()
+
+        self._profile = profile
+
+    def switch_profile(self, profile_name=None, clone_from=None):
+        """Switch the profile by using the given name, and optionally clone it.
+
+        Compared to :attr:`profile` this method allows to switch the profile
+        by giving control over the profile name as used for the new profile. It
+        also always creates a new blank profile, or as clone of an existent one.
+
+        :param profile_name: Optional, name of the profile, which will be used
+            as part of the profile path (folder name containing the profile).
+        :clone_from: Optional, if specified the new profile will be cloned
+            based on the given profile. This argument can be an instance of
+            ``mozprofile.Profile``, or the path of the profile.
+        """
+        if isinstance(clone_from, Profile):
+            clone_from = clone_from.profile
+
+        self._update_profile(clone_from, profile_name=profile_name)
+
+    @property
+    def profile_args(self):
+        args = {"preferences": deepcopy(self.required_prefs)}
+        args["preferences"]["marionette.defaultPrefs.port"] = self.marionette_port
+
         if self.prefs:
-            profile_args["preferences"].update(self.prefs)
+            args["preferences"].update(self.prefs)
+
         if self.verbose:
             level = "TRACE" if self.verbose >= 2 else "DEBUG"
-            profile_args["preferences"]["marionette.logging"] = level
+            args["preferences"]["marionette.logging"] = level
+
         if "-jsdebugger" in self.app_args:
-            profile_args["preferences"].update({
+            args["preferences"].update({
                 "devtools.browsertoolbox.panel": "jsdebugger",
                 "devtools.debugger.remote-enabled": True,
                 "devtools.chrome.enabled": True,
                 "devtools.debugger.prompt-connection": False,
                 "marionette.debugging.clicktostart": True,
             })
-        if self.addons:
-            profile_args["addons"] = self.addons
 
-        if hasattr(self, "profile_path") and self.profile is None:
-            if not self.profile_path:
-                if self.workspace:
-                    profile_args["profile"] = tempfile.mkdtemp(
-                        suffix=".mozrunner-{:.0f}".format(time.time()),
-                        dir=self.workspace)
-                self.profile = Profile(**profile_args)
-            else:
-                profile_args["path_from"] = self.profile_path
-                profile_name = "{}-{:.0f}".format(
-                    os.path.basename(self.profile_path),
-                    time.time()
-                )
-                if self.workspace:
-                    profile_args["path_to"] = os.path.join(self.workspace,
-                                                           profile_name)
-                self.profile = Profile.clone(**profile_args)
+        if self.addons:
+            args["addons"] = self.addons
+
+        return args
 
     @classmethod
     def create(cls, app=None, *args, **kwargs):
         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]
 
@@ -216,17 +277,17 @@ class GeckoInstance(object):
         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)
 
         return instance_class(*args, **kwargs)
 
     def start(self):
-        self._update_profile()
+        self._update_profile(self.profile)
         self.runner = self.runner_class(**self._get_runner_args())
         self.runner.start()
 
     def _get_runner_args(self):
         process_args = {
             "processOutputLine": [NullOutput()],
         }
 
@@ -266,33 +327,34 @@ class GeckoInstance(object):
 
         :param clean: If True, also perform runner cleanup.
         """
         if self.runner:
             self.runner.stop()
             if clean:
                 self.runner.cleanup()
 
-        if clean and self.profile:
-            self.profile.cleanup()
+        if clean:
+            if isinstance(self.profile, Profile):
+                self.profile.cleanup()
             self.profile = None
 
     def restart(self, prefs=None, clean=True):
         """
         Close then start the managed Gecko process.
 
         :param prefs: Dictionary of preference names and values.
         :param clean: If True, reset the profile before starting.
         """
-        self.close(clean=clean)
-
         if prefs:
             self.prefs = prefs
         else:
             self.prefs = None
+
+        self.close(clean=clean)
         self.start()
 
 
 class FennecInstance(GeckoInstance):
     fennec_prefs = {
         # Enable output of dump()
         "browser.dom.window.dump.enabled": True,
 
@@ -345,17 +407,17 @@ class FennecInstance(GeckoInstance):
         if self._package_name is None:
             self._package_name = "org.mozilla.fennec"
             user = os.getenv("USER")
             if user:
                 self._package_name += "_" + user
         return self._package_name
 
     def start(self):
-        self._update_profile()
+        self._update_profile(self.profile)
         self.runner = self.runner_class(**self._get_runner_args())
         try:
             if self.connect_to_running_emulator:
                 self.runner.device.connect()
             self.runner.start()
         except Exception as e:
             exc, val, tb = sys.exc_info()
             message = "Error possibly due to runner or device args: {}"
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs.py
@@ -1,32 +1,67 @@
 # 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
 
+from marionette_driver import geckoinstance
 from marionette_driver.errors import JavascriptException
 
-from marionette_harness import MarionetteTestCase
+from marionette_harness import (
+    MarionetteTestCase,
+    run_if_manage_instance,
+    skip_if_desktop,
+    skip_if_mobile
+)
 
 
 class TestPreferences(MarionetteTestCase):
     prefs = {
         "bool": "marionette.test.bool",
         "int": "marionette.test.int",
         "string": "marionette.test.string",
     }
 
     def tearDown(self):
         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():
+            self.assertEqual(self.marionette.get_pref(key), value,
+                             "Preference {} hasn't been set to {}".format(key, value))
+
+    @skip_if_mobile("Only runnable with Firefox")
+    def test_desktop_instance_preferences(self):
+        required_prefs = geckoinstance.DesktopInstance.desktop_prefs
+
+        for key, value in required_prefs.iteritems():
+            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))
+
+    @skip_if_desktop("Only runnable with Fennec")
+    def test_fennec_instance_preferences(self):
+        required_prefs = geckoinstance.FennecInstance.required_prefs
+
+        for key, value in required_prefs.iteritems():
+            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"]))
 
         self.marionette.set_pref(self.prefs["bool"], True)
         self.assertTrue(self.marionette.get_pref(self.prefs["bool"]))
 
         self.marionette.clear_pref(self.prefs["bool"])
         self.assertIsNone(self.marionette.get_pref(self.prefs["bool"]))
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_prefs_enforce.py
@@ -0,0 +1,42 @@
+# 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
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestEnforcePreferences(MarionetteTestCase):
+
+    def setUp(self):
+        super(TestEnforcePreferences, self).setUp()
+
+        self.marionette.enforce_gecko_prefs({
+            "marionette.test.bool": True,
+            "marionette.test.int": 3,
+            "marionette.test.string": "testing",
+        })
+        self.marionette.set_context("chrome")
+
+    def tearDown(self):
+        self.marionette.quit(clean=True)
+
+        super(TestEnforcePreferences, self).tearDown()
+
+    def test_preferences_are_set(self):
+        self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
+        self.assertEqual(self.marionette.get_pref("marionette.test.string"), "testing")
+        self.assertEqual(self.marionette.get_pref("marionette.test.int"), 3)
+
+    def test_change_preference(self):
+        self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
+
+        self.marionette.enforce_gecko_prefs({"marionette.test.bool": False})
+
+        self.assertFalse(self.marionette.get_pref("marionette.test.bool"))
+
+    def test_restart_with_clean_profile(self):
+        self.marionette.restart(clean=True)
+
+        self.assertEqual(self.marionette.get_pref("marionette.test.bool"), None)
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_profile_management.py
@@ -1,36 +1,235 @@
 # 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 os
+import shutil
+import tempfile
+
+import mozprofile
+
+from marionette_driver import errors
 from marionette_harness import MarionetteTestCase
 
 
-class TestProfileManagement(MarionetteTestCase):
+class BaseProfileManagement(MarionetteTestCase):
+
+    def setUp(self):
+        super(BaseProfileManagement, self).setUp()
+
+        self.orig_profile_path = self.profile_path
+
+    def tearDown(self):
+        shutil.rmtree(self.orig_profile_path, ignore_errors=True)
+
+        self.marionette.profile = None
+
+        super(BaseProfileManagement, self).tearDown()
+
+    @property
+    def profile(self):
+        return self.marionette.instance.profile
+
+    @property
+    def profile_path(self):
+        return self.marionette.instance.profile.profile
+
+
+class WorkspaceProfileManagement(BaseProfileManagement):
+
+    def setUp(self):
+        super(WorkspaceProfileManagement, self).setUp()
+
+        # Set a new workspace for the instance, which will be used
+        # the next time a new profile is requested by a test.
+        self.workspace = tempfile.mkdtemp()
+        self.marionette.instance.workspace = self.workspace
+
+    def tearDown(self):
+        self.marionette.instance.workspace = None
+
+        shutil.rmtree(self.workspace, ignore_errors=True)
+
+        super(WorkspaceProfileManagement, self).tearDown()
+
+
+class ExternalProfileMixin(object):
 
     def setUp(self):
-        MarionetteTestCase.setUp(self)
-        self.marionette.enforce_gecko_prefs(
-            {"marionette.test.bool": True,
-             "marionette.test.string": "testing",
-             "marionette.test.int": 3
-             })
-        self.marionette.set_context("chrome")
+        super(ExternalProfileMixin, self).setUp()
+
+        # Create external profile
+        tmp_dir = tempfile.mkdtemp(suffix="external")
+        shutil.rmtree(tmp_dir, ignore_errors=True)
+
+        self.external_profile = mozprofile.Profile(profile=tmp_dir)
+        # Prevent profile from being removed during cleanup
+        self.external_profile.create_new = False
+
+    def tearDown(self):
+        shutil.rmtree(self.external_profile.profile, ignore_errors=True)
+
+        super(ExternalProfileMixin, self).tearDown()
+
+
+class TestQuitRestartWithoutWorkspace(BaseProfileManagement):
+
+    def test_quit_keeps_same_profile(self):
+        self.marionette.quit()
+        self.marionette.start_session()
+
+        self.assertEqual(self.profile_path, self.orig_profile_path)
+        self.assertTrue(os.path.exists(self.orig_profile_path))
+
+    def test_quit_clean_creates_new_profile(self):
+        self.marionette.quit(clean=True)
+        self.marionette.start_session()
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+    def test_restart_keeps_same_profile(self):
+        self.marionette.restart()
 
-    def test_preferences_are_set(self):
-        self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
-        self.assertEqual(self.marionette.get_pref("marionette.test.string"), "testing")
-        self.assertEqual(self.marionette.get_pref("marionette.test.int"), 3)
+        self.assertEqual(self.profile_path, self.orig_profile_path)
+        self.assertTrue(os.path.exists(self.orig_profile_path))
+
+    def test_restart_clean_creates_new_profile(self):
+        self.marionette.restart(clean=True)
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+
+class TestQuitRestartWithWorkspace(WorkspaceProfileManagement):
+
+    def test_quit_keeps_same_profile(self):
+        self.marionette.quit()
+        self.marionette.start_session()
 
-    def test_change_preference(self):
-        self.assertTrue(self.marionette.get_pref("marionette.test.bool"))
+        self.assertEqual(self.profile_path, self.orig_profile_path)
+        self.assertNotIn(self.workspace, self.profile_path)
+        self.assertTrue(os.path.exists(self.orig_profile_path))
+
+    def test_quit_clean_creates_new_profile(self):
+        self.marionette.quit(clean=True)
+        self.marionette.start_session()
 
-        self.marionette.enforce_gecko_prefs({"marionette.test.bool": False})
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertIn(self.workspace, self.profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
 
-        self.assertFalse(self.marionette.get_pref("marionette.test.bool"))
+    def test_restart_keeps_same_profile(self):
+        self.marionette.restart()
 
-    def test_clean_profile(self):
+        self.assertEqual(self.profile_path, self.orig_profile_path)
+        self.assertNotIn(self.workspace, self.profile_path)
+        self.assertTrue(os.path.exists(self.orig_profile_path))
+
+    def test_restart_clean_creates_new_profile(self):
         self.marionette.restart(clean=True)
 
-        self.assertEqual(self.marionette.get_pref("marionette.test.bool"), None)
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertIn(self.workspace, self.profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+
+class TestSwitchProfileFailures(BaseProfileManagement):
+
+    def test_raise_for_switching_profile_while_instance_is_running(self):
+        with self.assertRaisesRegexp(errors.MarionetteException, "instance is not running"):
+            self.marionette.instance.switch_profile()
+
+
+class TestSwitchProfileWithoutWorkspace(ExternalProfileMixin, BaseProfileManagement):
+
+    def setUp(self):
+        super(TestSwitchProfileWithoutWorkspace, self).setUp()
+
+        self.marionette.quit()
+
+    def test_do_not_call_cleanup_of_profile_for_path_only(self):
+        # If a path to a profile has been given (eg. via the --profile command
+        # line argument) and the profile hasn't been created yet, switching the
+        # profile should not try to call `cleanup()` on a string.
+        self.marionette.instance._profile = self.external_profile.profile
+        self.marionette.instance.switch_profile()
+
+    def test_new_random_profile_name(self):
+        self.marionette.instance.switch_profile()
+        self.marionette.start_session()
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+    def test_new_named_profile(self):
+        self.marionette.instance.switch_profile("foobar")
+        self.marionette.start_session()
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertIn("foobar", self.profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+    def test_clone_existing_profile(self):
+        self.marionette.instance.switch_profile(clone_from=self.external_profile)
+        self.marionette.start_session()
+
+        self.assertIn(os.path.basename(self.external_profile.profile), self.profile_path)
+        self.assertTrue(os.path.exists(self.external_profile.profile))
+
+    def test_replace_with_current_profile(self):
+        self.marionette.instance.profile = self.profile
+        self.marionette.start_session()
+
+        self.assertEqual(self.profile_path, self.orig_profile_path)
+        self.assertTrue(os.path.exists(self.orig_profile_path))
+
+    def test_replace_with_external_profile(self):
+        self.marionette.instance.profile = self.external_profile
+        self.marionette.start_session()
+
+        self.assertEqual(self.profile_path, self.external_profile.profile)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+        # Set a new profile and ensure the external profile has not been deleted
+        self.marionette.quit()
+        self.marionette.instance.profile = None
+
+        self.assertNotEqual(self.profile_path, self.external_profile.profile)
+        self.assertTrue(os.path.exists(self.external_profile.profile))
+
+
+class TestSwitchProfileWithWorkspace(ExternalProfileMixin, WorkspaceProfileManagement):
+
+    def setUp(self):
+        super(TestSwitchProfileWithWorkspace, self).setUp()
+
+        self.marionette.quit()
+
+    def test_new_random_profile_name(self):
+        self.marionette.instance.switch_profile()
+        self.marionette.start_session()
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertIn(self.workspace, self.profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+    def test_new_named_profile(self):
+        self.marionette.instance.switch_profile("foobar")
+        self.marionette.start_session()
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertIn("foobar", self.profile_path)
+        self.assertIn(self.workspace, self.profile_path)
+        self.assertFalse(os.path.exists(self.orig_profile_path))
+
+    def test_clone_existing_profile(self):
+        self.marionette.instance.switch_profile(clone_from=self.external_profile)
+        self.marionette.start_session()
+
+        self.assertNotEqual(self.profile_path, self.orig_profile_path)
+        self.assertIn(self.workspace, self.profile_path)
+        self.assertIn(os.path.basename(self.external_profile.profile), self.profile_path)
+        self.assertTrue(os.path.exists(self.external_profile.profile))
--- a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -1,11 +1,11 @@
 [test_marionette.py]
 [test_cli_arguments.py]
-skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921
 [test_geckoinstance.py]
 [test_data_driven.py]
 [test_session.py]
 [test_capabilities.py]
 [test_proxy.py]
 [test_accessibility.py]
 [test_expectedfail.py]
 expected = fail
@@ -88,38 +88,40 @@ skip-if = appname == 'fennec'
 [test_getactiveframe_oop.py]
 skip-if = true # Bug 925688
 [test_screen_orientation.py]
 [test_errors.py]
 
 [test_execute_isolate.py]
 [test_click_scrolling.py]
 [test_profile_management.py]
-skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921
 [test_quit_restart.py]
-skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921 and bug 1322993
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921
 [test_with_using_context.py]
 
 [test_modal_dialogs.py]
 skip-if = appname == 'fennec' # Bug 1325738
 [test_key_actions.py]
 [test_mouse_action.py]
 skip-if = appname == 'fennec'
 [test_teardown_context_preserved.py]
 [test_file_upload.py]
 skip-if = appname == 'fennec' || os == "win" # http://bugs.python.org/issue14574
 
 [test_execute_sandboxes.py]
 [test_prefs.py]
+[test_prefs_enforce.py]
+skip-if = manage_instance == false || appname == 'fennec' # Bug 1298921
 
 [test_shadow_dom.py]
 skip-if = true # Bug 1293844, bug 1387678
 
 [test_chrome.py]
 skip-if = appname == 'fennec'
 
 [test_addons.py]
 skip-if = appname == 'fennec' # Bug 1330598
 
 [test_select.py]
 [test_crash.py]
-skip-if = asan || manage_instance == false || appname == 'fennec' || (os == 'mac' && !debug) # Bug 1298921, bug 1322993, bug 1376773
+skip-if = asan || manage_instance == false || appname == 'fennec' || (os == 'mac' && !debug) # Bug 1298921, bug 1376773
 [test_localization.py]