Backed out changeset 2638daa60218 (bug 1316984) for a bad rebase.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 26 Dec 2016 22:52:35 -0500
changeset 353212 f9817e4e3e3c299837dcc2704ad1b907cfc0ca99
parent 353211 8d9fb8fa1f3db54852a586c145f2ed5afe00f27c
child 353213 5434909ba45c6df65a4e769ba80fe67ead95b785
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1316984
milestone52.0a2
backs out2638daa602187f2f8e4c9510c659ce0ca5251711
Backed out changeset 2638daa60218 (bug 1316984) for a bad rebase.
testing/firefox-ui/harness/firefox_ui_harness/testcases.py
testing/firefox-ui/tests/functional/locationbar/test_favicon_in_autocomplete.py
testing/firefox-ui/tests/functional/locationbar/test_suggest_bookmarks.py
testing/firefox-ui/tests/functional/private_browsing/test_about_private_browsing.py
testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
testing/firefox-ui/tests/functional/security/test_safe_browsing_warning_pages.py
testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
testing/firefox-ui/tests/functional/security/test_ssl_status_after_restart.py
testing/firefox-ui/tests/functional/security/test_submit_unencrypted_info_warning.py
testing/firefox-ui/tests/puppeteer/manifest.ini
testing/firefox-ui/tests/puppeteer/test_notifications.py
testing/firefox-ui/tests/puppeteer/test_prefs.py
testing/firefox-ui/tests/puppeteer/test_software_update.py
testing/firefox-ui/tests/puppeteer/test_windows.py
testing/marionette/puppeteer/firefox/docs/api/prefs.rst
testing/marionette/puppeteer/firefox/docs/index.rst
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/prefs.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/windows.py
--- a/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
+++ b/testing/firefox-ui/harness/firefox_ui_harness/testcases.py
@@ -4,16 +4,17 @@
 
 import os
 import pprint
 from datetime import datetime
 
 import mozfile
 
 from firefox_puppeteer import PuppeteerMixin
+from firefox_puppeteer.api.prefs import Preferences
 from firefox_puppeteer.api.software_update import SoftwareUpdate
 from firefox_puppeteer.ui.update_wizard import UpdateWizardDialog
 from marionette_driver import Wait
 from marionette_driver.errors import NoSuchWindowException
 from marionette_harness import MarionetteTestCase
 
 
 class UpdateTestCase(PuppeteerMixin, MarionetteTestCase):
@@ -182,17 +183,18 @@ class UpdateTestCase(PuppeteerMixin, Mar
         :param timeout: How long to wait for the download to finish. Optional, default to 360s.
         """
 
         def download_via_update_wizard(dialog):
             """ Download the update via the old update wizard dialog.
 
             :param dialog: Instance of :class:`UpdateWizardDialog`.
             """
-            self.marionette.set_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE, dialog.window_type)
+            prefs = Preferences(self.marionette)
+            prefs.set_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE, dialog.window_type)
 
             try:
                 # If updates have already been found, proceed to download
                 if dialog.wizard.selected_panel in [dialog.wizard.updates_found_basic,
                                                     dialog.wizard.error_patching,
                                                     ]:
                     dialog.select_next_page()
 
--- a/testing/firefox-ui/tests/functional/locationbar/test_favicon_in_autocomplete.py
+++ b/testing/firefox-ui/tests/functional/locationbar/test_favicon_in_autocomplete.py
@@ -11,18 +11,18 @@ class TestFaviconInAutocomplete(Puppetee
 
     PREF_SUGGEST_SEARCHES = 'browser.urlbar.suggest.searches'
     PREF_SUGGEST_BOOKMARK = 'browser.urlbar.suggest.bookmark'
 
     def setUp(self):
         super(TestFaviconInAutocomplete, self).setUp()
 
         # Disable suggestions for searches and bookmarks to get results only for history
-        self.marionette.set_pref(self.PREF_SUGGEST_SEARCHES, False)
-        self.marionette.set_pref(self.PREF_SUGGEST_BOOKMARK, False)
+        self.puppeteer.prefs.set_pref(self.PREF_SUGGEST_SEARCHES, False)
+        self.puppeteer.prefs.set_pref(self.PREF_SUGGEST_BOOKMARK, False)
 
         self.puppeteer.places.remove_all_history()
 
         self.test_urls = [self.marionette.absolute_url('layout/mozilla.html')]
 
         self.test_string = 'mozilla'
         self.test_favicon = 'mozilla_favicon.ico'
 
--- a/testing/firefox-ui/tests/functional/locationbar/test_suggest_bookmarks.py
+++ b/testing/firefox-ui/tests/functional/locationbar/test_suggest_bookmarks.py
@@ -17,17 +17,17 @@ class TestStarInAutocomplete(PuppeteerMi
 
     def setUp(self):
         super(TestStarInAutocomplete, self).setUp()
 
         self.bookmark_panel = None
         self.test_urls = [self.marionette.absolute_url('layout/mozilla_grants.html')]
 
         # Disable search suggestions to only get results for history and bookmarks
-        self.marionette.set_pref(self.PREF_SUGGEST_SEARCHES, False)
+        self.puppeteer.prefs.set_pref(self.PREF_SUGGEST_SEARCHES, False)
 
         with self.marionette.using_context('content'):
             self.marionette.navigate('about:blank')
 
         self.puppeteer.places.remove_all_history()
 
     def tearDown(self):
         # Close the autocomplete results
--- a/testing/firefox-ui/tests/functional/private_browsing/test_about_private_browsing.py
+++ b/testing/firefox-ui/tests/functional/private_browsing/test_about_private_browsing.py
@@ -10,17 +10,17 @@ from marionette_harness import Marionett
 
 class TestAboutPrivateBrowsing(PuppeteerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestAboutPrivateBrowsing, self).setUp()
 
         # Use a fake local support URL
         support_url = 'about:blank?'
-        self.marionette.set_pref('app.support.baseURL', support_url)
+        self.puppeteer.prefs.set_pref('app.support.baseURL', support_url)
 
         self.pb_url = support_url + 'private-browsing'
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('app.support.baseURL')
         finally:
             super(TestAboutPrivateBrowsing, self).tearDown()
--- a/testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
+++ b/testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
@@ -32,18 +32,18 @@ class TestSafeBrowsingNotificationBar(Pu
             # Malware URL object
             {
                 'button_property': 'safebrowsing.notAnAttackButton.label',
                 'report_page': 'stopbadware.org',
                 'unsafe_page': 'https://www.itisatrap.org/firefox/its-an-attack.html'
             }
         ]
 
-        self.marionette.set_pref('browser.safebrowsing.phishing.enabled', True)
-        self.marionette.set_pref('browser.safebrowsing.malware.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.phishing.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.malware.enabled', True)
 
         # Give the browser a little time, because SafeBrowsing.jsm takes a while
         # between start up and adding the example urls to the db.
         # hg.mozilla.org/mozilla-central/file/46aebcd9481e/browser/base/content/browser.js#l1194
         time.sleep(3)
 
         # TODO: Bug 1139544: While we don't have a reliable way to close the safe browsing
         # notification bar when a test fails, run this test in a new tab.
--- a/testing/firefox-ui/tests/functional/security/test_safe_browsing_warning_pages.py
+++ b/testing/firefox-ui/tests/functional/security/test_safe_browsing_warning_pages.py
@@ -18,18 +18,18 @@ class TestSafeBrowsingWarningPages(Puppe
             # Unwanted software URL
             'https://www.itisatrap.org/firefox/unwanted.html',
             # Phishing URL
             'https://www.itisatrap.org/firefox/its-a-trap.html',
             # Malware URL
             'https://www.itisatrap.org/firefox/its-an-attack.html'
         ]
 
-        self.marionette.set_pref('browser.safebrowsing.phishing.enabled', True)
-        self.marionette.set_pref('browser.safebrowsing.malware.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.phishing.enabled', True)
+        self.puppeteer.prefs.set_pref('browser.safebrowsing.malware.enabled', True)
 
         # Give the browser a little time, because SafeBrowsing.jsm takes a
         # while between start up and adding the example urls to the db.
         # hg.mozilla.org/mozilla-central/file/46aebcd9481e/browser/base/content/browser.js#l1194
         time.sleep(3)
 
         # TODO: Bug 1139544: While we don't have a reliable way to close the safe browsing
         # notification bar when a test fails, run this test in a new tab.
--- a/testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
+++ b/testing/firefox-ui/tests/functional/security/test_ssl_disabled_error_page.py
@@ -17,18 +17,18 @@ class TestSSLDisabledErrorPage(Puppeteer
 
         self.url = 'https://tlsv1-0.mozqa.com'
 
         self.puppeteer.utils.sanitize({"sessions": True})
 
         # Disable SSL 3.0, TLS 1.0 and TLS 1.1 for secure connections
         # by forcing the use of TLS 1.2
         # see: http://kb.mozillazine.org/Security.tls.version.*#Possible_values_and_their_effects
-        self.marionette.set_pref('security.tls.version.min', 3)
-        self.marionette.set_pref('security.tls.version.max', 3)
+        self.puppeteer.prefs.set_pref('security.tls.version.min', 3)
+        self.puppeteer.prefs.set_pref('security.tls.version.max', 3)
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('security.tls.version.min')
             self.marionette.clear_pref('security.tls.version.max')
         finally:
             super(TestSSLDisabledErrorPage, self).tearDown()
 
--- 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
@@ -26,17 +26,17 @@ class TestSSLStatusAfterRestart(Puppetee
             {
                 'url': 'https://ssl-ov.mozqa.com/',
                 'identity': '',
                 'type': 'secure'
             }
         )
 
         # Set browser to restore previous session
-        self.marionette.set_pref('browser.startup.page', 3)
+        self.puppeteer.prefs.set_pref('browser.startup.page', 3)
 
         self.locationbar = self.browser.navbar.locationbar
         self.identity_popup = self.locationbar.identity_popup
 
     def tearDown(self):
         try:
             self.puppeteer.windows.close_all([self.browser])
             self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
--- a/testing/firefox-ui/tests/functional/security/test_submit_unencrypted_info_warning.py
+++ b/testing/firefox-ui/tests/functional/security/test_submit_unencrypted_info_warning.py
@@ -12,17 +12,17 @@ from marionette_harness import Marionett
 class TestSubmitUnencryptedInfoWarning(PuppeteerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestSubmitUnencryptedInfoWarning, self).setUp()
 
         self.url = 'https://ssl-dv.mozqa.com/data/firefox/security/unencryptedsearch.html'
         self.test_string = 'mozilla'
 
-        self.marionette.set_pref('security.warn_submit_insecure', True)
+        self.puppeteer.prefs.set_pref('security.warn_submit_insecure', True)
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('security.warn_submit_insecure')
         finally:
             super(TestSubmitUnencryptedInfoWarning, self).tearDown()
 
     def test_submit_unencrypted_info_warning(self):
--- a/testing/firefox-ui/tests/puppeteer/manifest.ini
+++ b/testing/firefox-ui/tests/puppeteer/manifest.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 tags = local
 
 # API tests
 [test_appinfo.py]
 [test_l10n.py]
 [test_places.py]
+[test_prefs.py]
 [test_security.py]
 tags = remote
 [test_software_update.py]
 tags = remote
 [test_utils.py]
 
 # UI tests
 [test_about_window.py]
--- a/testing/firefox-ui/tests/puppeteer/test_notifications.py
+++ b/testing/firefox-ui/tests/puppeteer/test_notifications.py
@@ -12,17 +12,17 @@ from marionette_driver.errors import Tim
 from marionette_harness import MarionetteTestCase
 
 
 class TestNotifications(PuppeteerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestNotifications, self).setUp()
 
-        self.marionette.set_pref('extensions.install.requireSecureOrigin', False)
+        self.puppeteer.prefs.set_pref('extensions.install.requireSecureOrigin', False)
 
         self.addons_url = self.marionette.absolute_url('addons/extensions/')
         self.puppeteer.utils.permissions.add(self.marionette.baseurl, 'install')
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('extensions.install.requireSecureOrigin')
             self.marionette.clear_pref('xpinstall.signatures.required')
@@ -64,17 +64,17 @@ class TestNotifications(PuppeteerMixin, 
         """Trigger a notification with an origin"""
         self.trigger_addon_notification('restartless_addon_signed.xpi')
         self.assertIn(self.browser.notification.origin, self.marionette.baseurl)
         self.assertIsNotNone(self.browser.notification.label)
 
     def test_addon_install_failed_notification(self):
         """Trigger add-on blocked notification using an unsigned add-on"""
         # Ensure that installing unsigned extensions will fail
-        self.marionette.set_pref('xpinstall.signatures.required', True)
+        self.puppeteer.prefs.set_pref('xpinstall.signatures.required', True)
 
         self.trigger_addon_notification(
             'restartless_addon_unsigned.xpi',
             notification=AddOnInstallFailedNotification)
 
     def trigger_addon_notification(self, addon, notification=AddOnInstallConfirmationNotification):
         with self.marionette.using_context('content'):
             self.marionette.navigate(self.addons_url)
new file mode 100644
--- /dev/null
+++ b/testing/firefox-ui/tests/puppeteer/test_prefs.py
@@ -0,0 +1,165 @@
+# 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 firefox_puppeteer import PuppeteerMixin
+from marionette_harness import MarionetteTestCase
+
+
+class testPreferences(PuppeteerMixin, MarionetteTestCase):
+
+    def setUp(self):
+        super(testPreferences, self).setUp()
+
+        self.new_pref = 'marionette.unittest.set_pref'
+        self.unknown_pref = 'marionette.unittest.unknown'
+
+        self.bool_pref = 'browser.tabs.loadBookmarksInBackground'
+        self.int_pref = 'browser.tabs.maxOpenBeforeWarn'
+        # Consider using new test preferences
+        # See Bug 1303863 Comment #32
+        self.string_pref = 'browser.startup.homepage'
+
+    def tearDown(self):
+        try:
+            self.marionette.clear_pref('marionette.unittest.set_pref')
+            self.marionette.clear_pref('marionette.unittest.unknown')
+            self.marionette.clear_pref('browser.tabs.loadBookmarksInBackground')
+            self.marionette.clear_pref('browser.tabs.maxOpenBeforeWarn')
+            self.marionette.clear_pref('browser.startup.homepage')
+        finally:
+            super(testPreferences, self).tearDown()
+
+    def test_get_pref(self):
+        # check correct types
+        self.assertTrue(isinstance(self.puppeteer.prefs.get_pref(self.bool_pref),
+                                   bool))
+        self.assertTrue(isinstance(self.puppeteer.prefs.get_pref(self.int_pref),
+                                   int))
+        self.assertTrue(isinstance(self.puppeteer.prefs.get_pref(self.string_pref),
+                                   basestring))
+
+        # unknown
+        self.assertIsNone(self.puppeteer.prefs.get_pref(self.unknown_pref))
+
+        # default branch
+        orig_value = self.puppeteer.prefs.get_pref(self.int_pref)
+        self.puppeteer.prefs.set_pref(self.int_pref, 99999)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 99999)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref, True), orig_value)
+
+        # complex value
+        properties_file = 'chrome://branding/locale/browserconfig.properties'
+        self.assertEqual(self.puppeteer.prefs.get_pref('browser.startup.homepage'),
+                         properties_file)
+
+        value = self.puppeteer.prefs.get_pref('browser.startup.homepage',
+                                              interface='nsIPrefLocalizedString')
+        self.assertNotEqual(value, properties_file)
+
+    def test_set_pref_casted_values(self):
+        # basestring as boolean
+        self.puppeteer.prefs.set_pref(self.bool_pref, '')
+        self.assertFalse(self.puppeteer.prefs.get_pref(self.bool_pref))
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.bool_pref)
+
+        self.puppeteer.prefs.set_pref(self.bool_pref, 'unittest')
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.bool_pref))
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.bool_pref)
+
+        # int as boolean
+        self.puppeteer.prefs.set_pref(self.bool_pref, 0)
+        self.assertFalse(self.puppeteer.prefs.get_pref(self.bool_pref))
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.bool_pref)
+
+        self.puppeteer.prefs.set_pref(self.bool_pref, 5)
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.bool_pref))
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.bool_pref)
+
+        # boolean as int
+        self.puppeteer.prefs.set_pref(self.int_pref, False)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 0)
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.int_pref)
+
+        self.puppeteer.prefs.set_pref(self.int_pref, True)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 1)
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.int_pref)
+
+        # int as string
+        self.puppeteer.prefs.set_pref(self.string_pref, 54)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), '54')
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.string_pref)
+
+    def test_set_pref_invalid(self):
+        self.assertRaises(AssertionError,
+                          self.puppeteer.prefs.set_pref, self.new_pref, None)
+
+    def test_set_pref_new_preference(self):
+        self.puppeteer.prefs.set_pref(self.new_pref, True)
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.new_pref))
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.new_pref)
+
+        self.puppeteer.prefs.set_pref(self.new_pref, 5)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.new_pref), 5)
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.new_pref)
+
+        self.puppeteer.prefs.set_pref(self.new_pref, 'test')
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.new_pref), 'test')
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.new_pref)
+
+    def test_set_pref_new_values(self):
+        self.puppeteer.prefs.set_pref(self.bool_pref, True)
+        self.assertTrue(self.puppeteer.prefs.get_pref(self.bool_pref))
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.bool_pref)
+
+        self.puppeteer.prefs.set_pref(self.int_pref, 99999)
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.int_pref), 99999)
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.int_pref)
+
+        self.puppeteer.prefs.set_pref(self.string_pref, 'test_string')
+        self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), 'test_string')
+        # Remove when all self.marionette methods are implemented
+        # Please see Bug 1293588
+        self.marionette.clear_pref(self.string_pref)
+
+    def test_set_pref_default_branch(self):
+        orig_value = self.puppeteer.prefs.get_pref(self.string_pref, default_branch=True)
+
+        try:
+            self.puppeteer.prefs.set_pref(self.string_pref, 'default', default_branch=True)
+            self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), 'default')
+
+            self.puppeteer.prefs.set_pref(self.string_pref, 'user')
+            self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), 'user')
+            self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref, default_branch=True),
+                             'default')
+
+            self.marionette.clear_pref(self.string_pref)
+            self.assertEqual(self.puppeteer.prefs.get_pref(self.string_pref), 'default')
+
+        finally:
+            self.puppeteer.prefs.set_pref(self.string_pref, orig_value, default_branch=True)
--- a/testing/firefox-ui/tests/puppeteer/test_software_update.py
+++ b/testing/firefox-ui/tests/puppeteer/test_software_update.py
@@ -68,17 +68,17 @@ class TestSoftwareUpdate(PuppeteerMixin,
     def test_os_version(self):
         self.assertTrue(self.software_update.os_version)
 
     def test_staging_directory(self):
         self.assertTrue(self.software_update.staging_directory)
 
     def test_set_update_channel(self):
         self.software_update.update_channel = 'new_channel'
-        self.assertEqual(self.marionette.get_pref('app.update.channel', default_branch=True),
+        self.assertEqual(self.puppeteer.prefs.get_pref('app.update.channel', default_branch=True),
                          'new_channel')
 
 
 class TestMARChannels(PuppeteerMixin, MarionetteTestCase):
 
     def setUp(self):
         super(TestMARChannels, self).setUp()
 
--- a/testing/firefox-ui/tests/puppeteer/test_windows.py
+++ b/testing/firefox-ui/tests/puppeteer/test_windows.py
@@ -24,17 +24,17 @@ class BaseWindowTestCase(PuppeteerMixin,
         the timeout gets hit, which results in the parent killing
         the content process manually, which generates a crash report,
         which causes these tests to orange. We side-step this by
         setting dom.ipc.tabs.shutdownTimeoutSecs to 0, which disables
         the shutdown timer.
         """
         super(BaseWindowTestCase, self).setUp()
 
-        self.marionette.set_pref('dom.ipc.tabs.shutdownTimeoutSecs', 0)
+        self.puppeteer.prefs.set_pref('dom.ipc.tabs.shutdownTimeoutSecs', 0)
 
     def tearDown(self):
         try:
             self.marionette.clear_pref('dom.ipc.tabs.shutdownTimeoutSecs')
         finally:
             super(BaseWindowTestCase, self).tearDown()
 
 
new file mode 100644
--- /dev/null
+++ b/testing/marionette/puppeteer/firefox/docs/api/prefs.rst
@@ -0,0 +1,18 @@
+.. py:currentmodule:: firefox_puppeteer.api.prefs
+
+Preferences
+===========
+
+The Preferences class is a wrapper around the nsIPrefBranch_ interface in
+Firefox and allows you to interact with the preferences system. It only
+includes the most commonly used methods of that interface, whereby it also
+enhances the logic in terms of e.g. restoring the original value of modified
+preferences.
+
+.. _nsIPrefBranch: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrefBranch
+
+Preferences
+-----------
+
+.. autoclass:: Preferences
+   :members:
--- a/testing/marionette/puppeteer/firefox/docs/index.rst
+++ b/testing/marionette/puppeteer/firefox/docs/index.rst
@@ -67,16 +67,17 @@ future. Each library is available from a
    ui/browser/toolbars
    ui/browser/window
    ui/update_wizard/dialog
    ui/windows
    api/appinfo
    api/keys
    api/l10n
    api/places
+   api/prefs
    api/security
    api/software_update
    api/utils
 
 
 Indices and tables
 ==================
 
new file mode 100644
--- /dev/null
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/prefs.py
@@ -0,0 +1,177 @@
+# 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 firefox_puppeteer.base import BaseLib
+
+
+class Preferences(BaseLib):
+    archive = {}
+
+    def get_pref(self, pref_name, default_branch=False, interface=None):
+        """Retrieves the value of a preference.
+
+        To retrieve the value of a preference its name has to be specified. By
+        default it will be read from the `user` branch, and returns the current
+        value. If the default value is wanted instead, ensure to flag that by
+        passing `True` via default_branch.
+
+        It is also possible to retrieve the value of complex preferences, which
+        do not represent an atomic type like `basestring`, `int`, or `boolean`.
+        Specify the interface of the represented XPCOM object via `interface`.
+
+        :param pref_name: The preference name
+        :param default_branch: Optional, flag to use the default branch,
+         default to `False`
+        :param interface: Optional, interface of the complex preference,
+         default to `None`. Possible values are: `nsILocalFile`,
+         `nsISupportsString`, and `nsIPrefLocalizedString`
+
+        :returns: The preference value.
+        """
+        assert pref_name is not None
+
+        with self.marionette.using_context('chrome'):
+            return self.marionette.execute_script("""
+              Components.utils.import("resource://gre/modules/Services.jsm");
+
+              let pref_name = arguments[0];
+              let default_branch = arguments[1];
+              let interface = arguments[2];
+
+              let prefBranch;
+              if (default_branch) {
+                prefBranch = Services.prefs.getDefaultBranch("");
+              }
+              else {
+                prefBranch = Services.prefs;
+              }
+
+              // If an interface has been set, handle it differently
+              if (interface !== null) {
+                return prefBranch.getComplexValue(pref_name,
+                                                  Components.interfaces[interface]).data;
+              }
+
+              let type = prefBranch.getPrefType(pref_name);
+
+              switch (type) {
+                case prefBranch.PREF_STRING:
+                  return prefBranch.getCharPref(pref_name);
+                case prefBranch.PREF_BOOL:
+                  return prefBranch.getBoolPref(pref_name);
+                case prefBranch.PREF_INT:
+                  return prefBranch.getIntPref(pref_name);
+                case prefBranch.PREF_INVALID:
+                  return null;
+              }
+            """, script_args=[pref_name, default_branch, interface])
+
+    def reset_pref(self, pref_name):
+        """Resets a user set preference.
+
+        Every modification of a preference will turn its status into a user-set
+        preference. To restore the default value of the preference, call this
+        function once. Further calls have no effect as long as the value hasn't
+        changed again.
+
+        In case the preference is not present on the default branch, it will be
+        completely removed.
+
+        :param pref_name: The preference to reset
+
+        :returns: `True` if a user preference has been removed
+        """
+        assert pref_name is not None
+
+        with self.marionette.using_context('chrome'):
+            return self.marionette.execute_script("""
+              Components.utils.import("resource://gre/modules/Services.jsm");
+              let prefBranch = Services.prefs;
+
+              let pref_name = arguments[0];
+
+              if (prefBranch.prefHasUserValue(pref_name)) {
+                prefBranch.clearUserPref(pref_name);
+                return true;
+              }
+              else {
+                return false;
+              }
+            """, script_args=[pref_name])
+
+    def set_pref(self, pref_name, value, default_branch=False):
+        """Sets a preference to a specified value.
+
+        To set the value of a preference its name has to be specified.
+
+        The first time a new value for a preference is set, its value will be
+        automatically archived. It allows to restore the original value by
+        calling :func:`self.marionette.clear_pref`.
+
+        :param pref_name: The preference to set
+        :param value: The value to set the preference to
+        :param default_branch: Optional, flag to use the default branch,
+         default to `False`
+
+        """
+        assert pref_name is not None
+        assert value is not None
+
+        with self.marionette.using_context('chrome'):
+            # Backup original value only once
+            if pref_name not in self.archive:
+                self.archive[pref_name] = self.get_pref(pref_name)
+
+            retval = self.marionette.execute_script("""
+              Components.utils.import("resource://gre/modules/Services.jsm");
+
+              let pref_name = arguments[0];
+              let value = arguments[1];
+              let default_branch = arguments[2];
+
+              let prefBranch;
+              if (default_branch) {
+                prefBranch = Services.prefs.getDefaultBranch("");
+              }
+              else {
+                prefBranch = Services.prefs;
+              }
+
+              let type = prefBranch.getPrefType(pref_name);
+
+              // If the pref does not exist yet, get the type from the value
+              if (type == prefBranch.PREF_INVALID) {
+                switch (typeof value) {
+                  case "boolean":
+                    type = prefBranch.PREF_BOOL;
+                    break;
+                  case "number":
+                    type = prefBranch.PREF_INT;
+                    break;
+                  case "string":
+                    type = prefBranch.PREF_STRING;
+                    break;
+                  default:
+                    type = prefBranch.PREF_INVALID;
+                }
+              }
+
+              switch (type) {
+                case prefBranch.PREF_BOOL:
+                  prefBranch.setBoolPref(pref_name, value);
+                  break;
+                case prefBranch.PREF_STRING:
+                  prefBranch.setCharPref(pref_name, value);
+                  break;
+                case prefBranch.PREF_INT:
+                  prefBranch.setIntPref(pref_name, value);
+                  break;
+                default:
+                  return false;
+              }
+
+              return true;
+            """, script_args=[pref_name, value, default_branch])
+
+        assert retval
--- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/software_update.py
@@ -4,16 +4,17 @@
 
 import ConfigParser
 import os
 
 import mozinfo
 
 from firefox_puppeteer.base import BaseLib
 from firefox_puppeteer.api.appinfo import AppInfo
+from firefox_puppeteer.api.prefs import Preferences
 
 
 class ActiveUpdate(BaseLib):
 
     def __getattr__(self, attr):
         value = self.marionette.execute_script("""
           let ums = Components.classes['@mozilla.org/updates/update-manager;1']
                     .getService(Components.interfaces.nsIUpdateManager);
@@ -157,22 +158,24 @@ class MARChannels(BaseLib):
 
 
 class SoftwareUpdate(BaseLib):
     """The SoftwareUpdate API adds support for an easy access to the update process."""
     PREF_APP_DISTRIBUTION = 'distribution.id'
     PREF_APP_DISTRIBUTION_VERSION = 'distribution.version'
     PREF_APP_UPDATE_CHANNEL = 'app.update.channel'
     PREF_APP_UPDATE_URL = 'app.update.url'
+    PREF_APP_UPDATE_URL_OVERRIDE = 'app.update.url.override'
     PREF_DISABLED_ADDONS = 'extensions.disabledAddons'
 
     def __init__(self, marionette):
         BaseLib.__init__(self, marionette)
 
         self.app_info = AppInfo(marionette)
+        self.prefs = Preferences(marionette)
 
         self._mar_channels = MARChannels(marionette)
         self._active_update = ActiveUpdate(marionette)
 
     @property
     def ABI(self):
         """Get the customized ABI for the update service.
 
@@ -209,22 +212,22 @@ class SoftwareUpdate(BaseLib):
         """)
 
     @property
     def build_info(self):
         """Return information of the current build version
 
         :returns: A dictionary of build information
         """
-        update_url = self.get_formatted_update_url(True)
+        update_url = self.get_update_url(True)
 
         return {
             'buildid': self.app_info.appBuildID,
             'channel': self.update_channel,
-            'disabled_addons': self.marionette.get_pref(self.PREF_DISABLED_ADDONS),
+            'disabled_addons': self.prefs.get_pref(self.PREF_DISABLED_ADDONS),
             'locale': self.app_info.locale,
             'mar_channels': self.mar_channels.channels,
             'update_url': update_url,
             'update_snippet': self.get_update_snippet(update_url),
             'user_agent': self.app_info.user_agent,
             'version': self.app_info.version
         }
 
@@ -309,44 +312,26 @@ class SoftwareUpdate(BaseLib):
           let aus = Components.classes['@mozilla.org/updates/update-service;1']
                     .getService(Components.interfaces.nsIApplicationUpdateService);
           return aus.getUpdatesDirectory().path;
         """)
 
     @property
     def update_channel(self):
         """Return the currently used update channel."""
-        return self.marionette.get_pref(self.PREF_APP_UPDATE_CHANNEL,
-                                        default_branch=True)
+        return self.prefs.get_pref(self.PREF_APP_UPDATE_CHANNEL, default_branch=True)
 
     @update_channel.setter
     def update_channel(self, channel):
         """Set the update channel to be used for update checks.
 
         :param channel: New update channel to use
 
         """
-        self.marionette.set_pref(self.PREF_APP_UPDATE_CHANNEL, channel,
-                                 default_branch=True)
-
-    @property
-    def update_url(self):
-        """Return the update URL used for update checks."""
-        return self.marionette.get_pref(self.PREF_APP_UPDATE_URL,
-                                        default_branch=True)
-
-    @update_url.setter
-    def update_url(self, url):
-        """Set the update URL to be used for update checks.
-
-        :param url: New update URL to use
-
-        """
-        self.marionette.set_pref(self.PREF_APP_UPDATE_URL, url,
-                                 default_branch=True)
+        self.prefs.set_pref(self.PREF_APP_UPDATE_CHANNEL, channel, default_branch=True)
 
     @property
     def update_type(self):
         """Returns the type of the active update."""
         return self.active_update.type
 
     def force_fallback(self):
         """Update the update.status file and set the status to 'failed:6'"""
@@ -363,28 +348,32 @@ class SoftwareUpdate(BaseLib):
             import urllib2
             response = urllib2.urlopen(update_url)
             snippet = response.read()
         except Exception:
             pass
 
         return snippet
 
-    def get_formatted_update_url(self, force=False):
-        """Retrieve the formatted AUS update URL the update snippet is retrieved from.
+    def get_update_url(self, force=False):
+        """Retrieve the AUS update URL the update snippet is retrieved from.
 
         :param force: Boolean flag to force an update check
 
         :returns: The URL of the update snippet
         """
+        url = self.prefs.get_pref(self.PREF_APP_UPDATE_URL_OVERRIDE)
+        if not url:
+            url = self.prefs.get_pref(self.PREF_APP_UPDATE_URL)
+
         # Format the URL by replacing placeholders
         url = self.marionette.execute_script("""
           Components.utils.import("resource://gre/modules/UpdateUtils.jsm")
           return UpdateUtils.formatUpdateURL(arguments[0]);
-        """, script_args=[self.update_url])
+        """, script_args=[url])
 
         if force:
             if '?' in url:
                 url += '&'
             else:
                 url += '?'
             url += 'force=1'
 
--- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
@@ -46,18 +46,17 @@ class BrowserWindow(BaseWindow):
         self._tabbar = None
 
     @property
     def default_homepage(self):
         """The default homepage as used by the current locale.
 
         :returns: The default homepage for the current locale.
         """
-        return self.marionette.get_pref('browser.startup.homepage',
-                                        value_type='nsIPrefLocalizedString')
+        return self._prefs.get_pref('browser.startup.homepage', interface='nsIPrefLocalizedString')
 
     @property
     def is_private(self):
         """Returns True if this is a Private Browsing window."""
         self.switch_to()
 
         with self.marionette.using_context('chrome'):
             return self.marionette.execute_script("""
--- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/windows.py
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/windows.py
@@ -6,16 +6,17 @@ from marionette_driver import By, Wait
 from marionette_driver.errors import NoSuchWindowException
 from marionette_driver.keys import Keys
 
 import firefox_puppeteer.errors as errors
 
 from firefox_puppeteer.api.l10n import L10n
 from firefox_puppeteer.base import BaseLib
 from firefox_puppeteer.decorators import use_class_as_property
+from firefox_puppeteer.api.prefs import Preferences
 
 
 class Windows(BaseLib):
 
     # Used for registering the different windows with this class to avoid
     # circular dependencies with BaseWindow
     windows_map = {}
 
@@ -211,16 +212,17 @@ class BaseWindow(BaseLib):
     # l10n class attributes will be set by each window class individually
     dtds = []
     properties = []
 
     def __init__(self, marionette, window_handle):
         super(BaseWindow, self).__init__(marionette)
 
         self._l10n = L10n(self.marionette)
+        self._prefs = Preferences(self.marionette)
         self._windows = Windows(self.marionette)
 
         if window_handle not in self.marionette.chrome_window_handles:
             raise errors.UnknownWindowError('Window with handle "%s" does not exist' %
                                             window_handle)
         self._handle = window_handle
 
     def __eq__(self, other):