Bug 835930 - move b2g unittest profile handling on top of mozbase instead of automation.py, r=jgriffin
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Thu, 28 Mar 2013 16:42:49 -0400
changeset 126985 947b9aa5e2d3961f8334f8656616c68634fd744c
parent 126984 ee67360ec6628de92c2ba6dfa45b19dde050ab2b
child 126986 8cc00926f48657510c1b2cca5366402e322fc112
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersjgriffin
bugs835930
milestone22.0a1
Bug 835930 - move b2g unittest profile handling on top of mozbase instead of automation.py, r=jgriffin
b2g/test/b2g-unittest-requirements.txt
build/automation.py.in
build/mobile/b2gautomation.py
testing/mochitest/runtests.py
testing/mochitest/runtestsb2g.py
testing/profiles/Makefile.in
testing/profiles/moz.build
testing/profiles/prefs_b2g_unittest.js
testing/profiles/prefs_general.js
testing/profiles/webapps_mochitest.json
toolkit/toolkit.mozbuild
--- a/b2g/test/b2g-unittest-requirements.txt
+++ b/b2g/test/b2g-unittest-requirements.txt
@@ -1,6 +1,8 @@
+ManifestDestiny==0.5.6
 mozprocess==0.9
+mozprofile==0.6
 mozrunner==5.15
 mozdevice==0.21
 mozcrash==0.5
 mozfile==0.3
 mozlog==1.1
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -447,16 +447,20 @@ class Automation(object):
     else:
       os.mkdir(profileDir)
 
     # Set up permissions database
     locations = self.readLocations()
     self.setupPermissionsDatabase(profileDir,
       {'allowXULXBL':[(l.host, 'noxul' not in l.options) for l in locations]});
 
+    # NOTE: For refactoring purposes we are temporarily storing these prefs
+    #       in two locations. If you update a pref below, please also update
+    #       it in source/testing/profiles/prefs_general.js.
+    #       See bug 830430 for more details.
     part = """\
 user_pref("browser.console.showInPanel", true);
 user_pref("browser.dom.window.dump.enabled", true);
 user_pref("browser.firstrun.show.localepicker", false);
 user_pref("browser.firstrun.show.uidiscovery", false);
 user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.homepage
 user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
 user_pref("dom.allow_scripts_to_close_windows", true);
--- a/build/mobile/b2gautomation.py
+++ b/build/mobile/b2gautomation.py
@@ -326,17 +326,17 @@ class B2GRemoteAutomation(Automation):
             proc.daemon = True
             proc.start()
 
         def _save_stdout_proc(self, cmd, queue):
             self.stdout_proc = StdOutProc(cmd, queue)
             self.stdout_proc.run()
             if hasattr(self.stdout_proc, 'processOutput'):
                 self.stdout_proc.processOutput()
-            self.stdout_proc.waitForFinish()
+            self.stdout_proc.wait()
             self.stdout_proc = None
 
         @property
         def pid(self):
             # a dummy value to make the automation happy
             return 0
 
         @property
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -938,43 +938,46 @@ overlay chrome://webapprt/content/webapp
         shutil.copy2(abspath, options.profilePath)
       elif os.path.isdir(abspath):
         dest = os.path.join(options.profilePath, os.path.basename(abspath))
         shutil.copytree(abspath, dest)
       else:
         self.automation.log.warning("WARNING | runtests.py | Failed to copy %s to profile", abspath)
         continue
 
-  def installExtensionFromPath(self, options, path, extensionID = None):
-    extensionPath = self.getFullPath(path)
-
-    self.automation.log.info("INFO | runtests.py | Installing extension at %s to %s." %
-                            (extensionPath, options.profilePath))
-    self.automation.installExtension(extensionPath, options.profilePath,
-                                     extensionID)
-
-  def installExtensionsToProfile(self, options):
-    "Install special testing extensions, application distributed extensions, and specified on the command line ones to testing profile."
+  def getExtensionsToInstall(self, options):
+    "Return a list of extensions to install in the profile"
+    extensions = options.extensionsToInstall or []
     extensionDirs = [
       # Extensions distributed with the test harness.
       os.path.normpath(os.path.join(self.SCRIPT_DIRECTORY, "extensions")),
       # Extensions distributed with the application.
       os.path.join(options.app[ : options.app.rfind(os.sep)], "distribution", "extensions")
     ]
 
     for extensionDir in extensionDirs:
       if os.path.isdir(extensionDir):
         for dirEntry in os.listdir(extensionDir):
           if dirEntry not in options.extensionsToExclude:
             path = os.path.join(extensionDir, dirEntry)
             if os.path.isdir(path) or (os.path.isfile(path) and path.endswith(".xpi")):
-              self.installExtensionFromPath(options, path)
+              extensions.append(path)
+    return extensions
+
+  def installExtensionFromPath(self, options, path, extensionID = None):
+    extensionPath = self.getFullPath(path)
 
-    # Install custom extensions passed on the command line.
-    for path in options.extensionsToInstall:
+    self.automation.log.info("INFO | runtests.py | Installing extension at %s to %s." %
+                            (extensionPath, options.profilePath))
+    self.automation.installExtension(extensionPath, options.profilePath,
+                                     extensionID)
+
+  def installExtensionsToProfile(self, options):
+    "Install special testing extensions, application distributed extensions, and specified on the command line ones to testing profile."
+    for path in self.getExtensionsToInstall(options):
       self.installExtensionFromPath(options, path)
 
 def main():
   automation = Automation()
   mochitest = Mochitest(automation)
   parser = MochitestOptions(automation, mochitest.SCRIPT_DIRECTORY)
   options, args = parser.parse_args()
 
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -5,39 +5,58 @@
 import ConfigParser
 import os
 import shutil
 import sys
 import tempfile
 import threading
 import traceback
 
-sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))))
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+here = os.path.abspath(os.path.dirname(sys.argv[0]))
+sys.path.insert(0, here)
 
 from automation import Automation
 from b2gautomation import B2GRemoteAutomation, B2GDesktopAutomation
 from runtests import Mochitest
 from runtests import MochitestOptions
 from runtests import MochitestServer
 
-from mozdevice import DeviceManagerADB, DMError
 from marionette import Marionette
 
+from mozdevice import DeviceManagerADB, DMError
+from mozprofile import Profile, Preferences
 
-class B2GMochitestMixin(object):
+class B2GMochitest(Mochitest):
+    def __init__(self, automation, OOP=True, profile_data_dir=None,
+                    locations=os.path.join(here, 'server-locations.txt')):
+        Mochitest.__init__(self, automation)
+        self.OOP = OOP
+        self.locations = locations
+        self.preferences = []
+        self.webapps = None
 
-    def setupCommonOptions(self, options, OOP=True):
+        if profile_data_dir:
+            self.preferences = [os.path.join(profile_data_dir, f)
+                                 for f in os.listdir(profile_data_dir) if f.startswith('pref')]
+            self.webapps = [os.path.join(profile_data_dir, f)
+                             for f in os.listdir(profile_data_dir) if f.startswith('webapp')]
+
+    def setupCommonOptions(self, options):
         # set the testURL
         testURL = self.buildTestPath(options)
         if len(self.urlOpts) > 0:
             testURL += "?" + "&".join(self.urlOpts)
         self.automation.testURL = testURL
 
-        if OOP:
-            OOP_pref = "true"
+        if self.OOP:
             OOP_script = """
 let specialpowers = {};
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
 loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js", specialpowers);
 let specialPowersObserver = new specialpowers.SpecialPowersObserver();
 specialPowersObserver.init();
 
 let mm = container.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
@@ -48,17 +67,16 @@ mm.addMessageListener("SpecialPowers.Qui
 mm.addMessageListener("SPPermissionManager", specialPowersObserver);
 
 mm.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
 mm.loadFrameScript(CHILD_SCRIPT_API, true);
 mm.loadFrameScript(CHILD_SCRIPT, true);
 specialPowersObserver._isFrameScriptLoaded = true;
 """
         else:
-            OOP_pref = "false"
             OOP_script = ""
 
         # Execute this script on start up: loads special powers and sets
         # the test-container apps's iframe to the mochitest URL.
         self.automation.test_script = """
 const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js";
 const CHILD_SCRIPT_API = "chrome://specialpowers/content/specialpowersAPI.js";
 const CHILD_LOGGER_SCRIPT = "chrome://specialpowers/content/MozillaLogger.js";
@@ -67,28 +85,45 @@ let homescreen = document.getElementById
 let container = homescreen.contentWindow.document.getElementById('test-container');
 container.setAttribute('mozapp', 'http://mochi.test:8888/manifest.webapp');
 
 %s
 
 container.src = '%s';
 """ % (OOP_script, testURL)
 
-        # Set extra prefs for B2G.
-        f = open(os.path.join(options.profilePath, "user.js"), "a")
-        f.write("""
-user_pref("browser.homescreenURL","app://test-container.gaiamobile.org/index.html");
-user_pref("browser.manifestURL","app://test-container.gaiamobile.org/manifest.webapp");
-user_pref("dom.mozBrowserFramesEnabled", %s);
-user_pref("dom.ipc.tabs.disabled", false);
-user_pref("dom.ipc.browser_frames.oop_by_default", false);
-user_pref("dom.mozBrowserFramesWhitelist","app://test-container.gaiamobile.org,http://mochi.test:8888");
-user_pref("marionette.force-local", true);
-""" % OOP_pref)
-        f.close()
+    def buildProfile(self, options):
+        # preferences
+        prefs = {}
+        for path in self.preferences:
+            prefs.update(Preferences.read_prefs(path))
+
+        for v in options.extraPrefs:
+            thispref = v.split("=", 1)
+            if len(thispref) < 2:
+                print "Error: syntax error in --setpref=" + v
+                sys.exit(1)
+            prefs[thispref[0]] = thispref[1]
+
+        # interpolate the preferences
+        interpolation = { "server": "%s:%s" % (options.webServer, options.httpPort),
+                          "OOP": "true" if self.OOP else "false" }
+        prefs = json.loads(json.dumps(prefs) % interpolation)
+
+        self.profile = Profile(addons=self.getExtensionsToInstall(options),
+                               apps=self.webapps,
+                               locations=self.locations,
+                               preferences=prefs,
+                               proxy={"remote": options.webServer})
+
+        options.profilePath = self.profile.profile
+        # TODO bug 839108 - mozprofile should probably handle this
+        manifest = self.addChromeToProfile(options)
+        self.copyExtraFilesToProfile(options)
+        return manifest
 
 
 class B2GOptions(MochitestOptions):
 
     def __init__(self, automation, scriptdir, **kwargs):
         defaults = {}
         MochitestOptions.__init__(self, automation, scriptdir)
 
@@ -178,16 +213,21 @@ class B2GOptions(MochitestOptions):
                         type="string", dest="logcat_dir",
                         help="directory to store logcat dump files")
         defaults["logcat_dir"] = None
 
         self.add_option('--busybox', action='store',
                         type='string', dest='busybox',
                         help="Path to busybox binary to install on device")
         defaults['busybox'] = None
+        self.add_option('--profile-data-dir', action='store',
+                        type='string', dest='profile_data_dir',
+                        help="Path to a directory containing preference and other "
+                        "data to be installed into the profile")
+        defaults['profile_data_dir'] = os.path.join(here, 'profile_data')
 
         defaults["remoteTestRoot"] = "/data/local/tests"
         defaults["logFile"] = "mochitest.log"
         defaults["autorun"] = True
         defaults["closeWhenDone"] = True
         defaults["testPath"] = ""
         defaults["extensionsToExclude"] = ["specialpowers"]
 
@@ -282,25 +322,24 @@ class ProfileConfigParser(ConfigParser.R
                 if key == "__name__":
                     continue
                 if (value is not None) or (self._optcre == self.OPTCRE):
                     key = "=".join((key, str(value).replace('\n', '\n\t')))
                 fp.write("%s\n" % (key))
             fp.write("\n")
 
 
-class B2GMochitest(Mochitest, B2GMochitestMixin):
+class B2GDeviceMochitest(B2GMochitest):
 
     _automation = None
     _dm = None
-    localProfile = None
 
     def __init__(self, automation, devmgr, options):
         self._automation = automation
-        Mochitest.__init__(self, self._automation)
+        B2GMochitest.__init__(self, automation, OOP=True, profile_data_dir=options.profile_data_dir)
         self._dm = devmgr
         self.runSSLTunnel = False
         self.remoteProfile = options.remoteTestRoot + '/profile'
         self._automation.setRemoteProfile(self.remoteProfile)
         self.remoteLog = options.remoteLogFile
         self.localLog = None
         self.userJS = '/data/local/user.js'
         self.remoteMozillaPath = '/data/b2g/mozilla'
@@ -421,28 +460,16 @@ class B2GMochitest(Mochitest, B2GMochite
         options.xrePath = remoteXrePath
         options.utilityPath = remoteUtilityPath
         options.profilePath = remoteProfilePath
 
     def stopWebServer(self, options):
         if hasattr(self, 'server'):
             self.server.stop()
 
-    def buildProfile(self, options):
-        if self.localProfile:
-            options.profilePath = self.localProfile
-        manifest = Mochitest.buildProfile(self, options)
-        self.localProfile = options.profilePath
-
-        # Profile isn't actually copied to device until
-        # buildURLOptions is called.
-
-        options.profilePath = self.remoteProfile
-        return manifest
-
     def updateProfilesIni(self, profilePath):
         # update profiles.ini on the device to point to the test profile
         self.originalProfilesIni = tempfile.mktemp()
         self._dm.getFile(self.remoteProfilesIniPath, self.originalProfilesIni)
 
         config = ProfileConfigParser()
         config.read(self.originalProfilesIni)
         for section in config.sections():
@@ -461,17 +488,17 @@ class B2GMochitest(Mochitest, B2GMochite
             os.remove(newProfilesIni)
             os.remove(self.originalProfilesIni)
         except:
             pass
 
     def buildURLOptions(self, options, env):
         self.localLog = options.logFile
         options.logFile = self.remoteLog
-        options.profilePath = self.localProfile
+        options.profilePath = self.profile.profile
         retVal = Mochitest.buildURLOptions(self, options, env)
 
         self.setupCommonOptions(options)
 
         # Copy the profile to the device.
         self._dm._checkCmdAs(['shell', 'rm', '-r', self.remoteProfile])
         try:
             self._dm.pushDir(options.profilePath, self.remoteProfile)
@@ -498,21 +525,20 @@ class B2GMochitest(Mochitest, B2GMochite
             self.copyRemoteFile(self.userJS, '%s.orig' % self.userJS)
         self._dm.pushFile(os.path.join(options.profilePath, "user.js"), self.userJS)
         self.updateProfilesIni(self.remoteProfile)
         options.profilePath = self.remoteProfile
         options.logFile = self.localLog
         return retVal
 
 
-class B2GDesktopMochitest(Mochitest, B2GMochitestMixin):
+class B2GDesktopMochitest(B2GMochitest):
 
-    def __init__(self, automation):
-        #self._automation = automation
-        Mochitest.__init__(self, automation)
+    def __init__(self, automation, options):
+        B2GMochitest.__init__(self, automation, OOP=False, profile_data_dir=options.profile_data_dir)
 
     def runMarionetteScript(self, marionette, test_script):
         assert(marionette.wait_for_port())
         marionette.start_session()
         marionette.set_context(marionette.CONTEXT_CHROME)
         marionette.execute_script(test_script)
 
     def startTests(self):
@@ -522,40 +548,30 @@ class B2GDesktopMochitest(Mochitest, B2G
         thread = threading.Thread(target=self.runMarionetteScript,
                                   args=(self.automation.marionette,
                                         self.automation.test_script))
         thread.start()
 
     def buildURLOptions(self, options, env):
         retVal = Mochitest.buildURLOptions(self, options, env)
 
-        self.setupCommonOptions(options, OOP=False)
+        self.setupCommonOptions(options)
 
         # Copy the extensions to the B2G bundles dir.
         extensionDir = os.path.join(options.profilePath, 'extensions', 'staged')
         bundlesDir = os.path.join(os.path.dirname(options.app),
                                   'distribution', 'bundles')
 
         for filename in os.listdir(extensionDir):
             shutil.rmtree(os.path.join(bundlesDir, filename), True)
             shutil.copytree(os.path.join(extensionDir, filename),
                             os.path.join(bundlesDir, filename))
 
         return retVal
 
-    def buildProfile(self, options):
-        self.automation.initializeProfile(options.profilePath,
-                                          options.extraPrefs,
-                                          useServerLocations=True,
-                                          initialProfile=options.profile)
-        manifest = self.addChromeToProfile(options)
-        self.copyExtraFilesToProfile(options)
-        self.installExtensionsToProfile(options)
-        return manifest
-
 
 def run_remote_mochitests(automation, parser, options):
     # create our Marionette instance
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         automation.setEmulator(True)
         if options.noWindow:
@@ -592,17 +608,17 @@ def run_remote_mochitests(automation, pa
     automation.setDeviceManager(dm)
     options = parser.verifyRemoteOptions(options, automation)
     if (options == None):
         print "ERROR: Invalid options specified, use --help for a list of valid options"
         sys.exit(1)
 
     automation.setProduct("b2g")
 
-    mochitest = B2GMochitest(automation, dm, options)
+    mochitest = B2GDeviceMochitest(automation, dm, options)
 
     options = parser.verifyOptions(options, mochitest)
     if (options == None):
         sys.exit(1)
 
     logParent = os.path.dirname(options.remoteLogFile)
     dm.mkDir(logParent)
     automation.setRemoteLog(options.remoteLogFile)
@@ -632,17 +648,17 @@ def run_desktop_mochitests(parser, optio
     kwargs = {}
     if options.marionette:
         host, port = options.marionette.split(':')
         kwargs['host'] = host
         kwargs['port'] = int(port)
     marionette = Marionette.getMarionetteOrExit(**kwargs)
     automation.marionette = marionette
 
-    mochitest = B2GDesktopMochitest(automation)
+    mochitest = B2GDesktopMochitest(automation, options)
 
     # b2g desktop builds don't always have a b2g-bin file
     if options.app[-4:] == '-bin':
         options.app = options.app[:-4]
 
     options = MochitestOptions.verifyOptions(parser, options, mochitest)
     if options == None:
         sys.exit(1)
new file mode 100644
--- /dev/null
+++ b/testing/profiles/Makefile.in
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+MOCHITEST_PROFILE_FILES = \
+  prefs_general.js \
+  prefs_b2g_unittest.js \
+  webapps_mochitest.json \
+  $(NULL)
+
+_DEST_DIR = $(DEPTH)/_tests/testing/mochitest/profile_data
+libs:: $(MOCHITEST_PROFILE_FILES)
+	$(PYTHON) $(topsrcdir)/config/nsinstall.py $^ $(_DEST_DIR)
+
+stage-package: PKG_STAGE = $(DIST)/test-package-stage
+stage-package:
+	$(NSINSTALL) -D $(PKG_STAGE)/
+	@(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - $(MOCHITEST_PROFILE_FILES)) | (cd $(PKG_STAGE)/mochitest/profile_data && tar -xf -)
new file mode 100644
--- /dev/null
+++ b/testing/profiles/moz.build
@@ -0,0 +1,5 @@
+# vim: set filetype=python:
+# 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/.
+
new file mode 100644
--- /dev/null
+++ b/testing/profiles/prefs_b2g_unittest.js
@@ -0,0 +1,9 @@
+// Prefs specific to b2g mochitests
+
+user_pref("browser.homescreenURL","app://test-container.gaiamobile.org/index.html");
+user_pref("browser.manifestURL","app://test-container.gaiamobile.org/manifest.webapp");
+user_pref("dom.mozBrowserFramesEnabled", "%(OOP)s");
+user_pref("dom.ipc.tabs.disabled", false);
+user_pref("dom.ipc.browser_frames.oop_by_default", false);
+user_pref("dom.mozBrowserFramesWhitelist","app://test-container.gaiamobile.org,http://mochi.test:8888");
+user_pref("marionette.force-local", true);
new file mode 100644
--- /dev/null
+++ b/testing/profiles/prefs_general.js
@@ -0,0 +1,110 @@
+// Base preferences file used by most test harnesses
+
+user_pref("browser.console.showInPanel", true);
+user_pref("browser.dom.window.dump.enabled", true);
+user_pref("browser.firstrun.show.localepicker", false);
+user_pref("browser.firstrun.show.uidiscovery", false);
+user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.homepage
+user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
+user_pref("dom.allow_scripts_to_close_windows", true);
+user_pref("dom.disable_open_during_load", false);
+user_pref("dom.experimental_forms", true); // on for testing
+user_pref("dom.experimental_forms_range", true); // on for testing
+user_pref("dom.max_script_run_time", 0); // no slow script dialogs
+user_pref("hangmonitor.timeout", 0); // no hang monitor
+user_pref("dom.max_chrome_script_run_time", 0);
+user_pref("dom.popup_maximum", -1);
+user_pref("dom.send_after_paint_to_content", true);
+user_pref("dom.successive_dialog_time_limit", 0);
+user_pref("signed.applets.codebase_principal_support", true);
+user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref("shell.checkDefaultClient", false);
+user_pref("browser.warnOnQuit", false);
+user_pref("accessibility.typeaheadfind.autostart", false);
+user_pref("javascript.options.showInConsole", true);
+user_pref("devtools.errorconsole.enabled", true);
+user_pref("devtools.debugger.remote-port", 6023);
+user_pref("layout.debug.enable_data_xbl", true);
+user_pref("browser.EULA.override", true);
+user_pref("javascript.options.jit_hardening", true);
+user_pref("gfx.color_management.force_srgb", true);
+user_pref("network.manage-offline-status", false);
+user_pref("dom.min_background_timeout_value", 1000);
+user_pref("test.mousescroll", true);
+user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
+user_pref("network.http.prompt-temp-redirect", false);
+user_pref("media.cache_size", 100);
+user_pref("media.volume_scale", "0.01");
+user_pref("security.warn_viewing_mixed", false);
+user_pref("app.update.enabled", false);
+user_pref("app.update.staging.enabled", false);
+user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
+user_pref("dom.w3c_touch_events.enabled", 1);
+user_pref("dom.undo_manager.enabled", true);
+user_pref("dom.webcomponents.enabled", true);
+// Set a future policy version to avoid the telemetry prompt.
+user_pref("toolkit.telemetry.prompted", 999);
+user_pref("toolkit.telemetry.notifiedOptOut", 999);
+// Existing tests assume there is no font size inflation.
+user_pref("font.size.inflation.emPerLine", 0);
+user_pref("font.size.inflation.minTwips", 0);
+
+// Only load extensions from the application and user profile
+// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
+user_pref("extensions.enabledScopes", 5);
+// Disable metadata caching for installed add-ons by default
+user_pref("extensions.getAddons.cache.enabled", false);
+// Disable intalling any distribution add-ons
+user_pref("extensions.installDistroAddons", false);
+
+user_pref("extensions.testpilot.runStudies", false);
+user_pref("extensions.testpilot.alreadyCustomizedToolbar", true);
+
+user_pref("geo.wifi.uri", "http://%(server)s/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs");
+user_pref("geo.wifi.testing", true);
+user_pref("geo.ignore.location_filter", true);
+
+user_pref("camino.warn_when_closing", false); // Camino-only, harmless to others
+
+// Make url-classifier updates so rare that they won't affect tests
+user_pref("urlclassifier.updateinterval", 172800);
+// Point the url-classifier to the local testing server for fast failures
+user_pref("browser.safebrowsing.gethashURL", "http://%(server)s/safebrowsing-dummy/gethash");
+user_pref("browser.safebrowsing.keyURL", "http://%(server)s/safebrowsing-dummy/newkey");
+user_pref("browser.safebrowsing.updateURL", "http://%(server)s/safebrowsing-dummy/update");
+// Point update checks to the local testing server for fast failures
+user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL");
+user_pref("extensions.update.background.url", "http://%(server)s/extensions-dummy/updateBackgroundURL");
+user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL");
+user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL");
+// Turn off extension updates so they don't bother tests
+user_pref("extensions.update.enabled", false);
+// Make sure opening about:addons won't hit the network
+user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
+// Make sure AddonRepository won't hit the network
+user_pref("extensions.getAddons.maxResults", 0);
+user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
+user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
+user_pref("extensions.getAddons.search.browseURL", "http://%(server)s/extensions-dummy/repositoryBrowseURL");
+user_pref("extensions.getAddons.search.url", "http://%(server)s/extensions-dummy/repositorySearchURL");
+// Make sure that opening the plugins check page won't hit the network
+user_pref("plugins.update.url", "http://%(server)s/plugins-dummy/updateCheckURL");
+
+// Existing tests don't wait for the notification button security delay
+user_pref("security.notification_enable_delay", 0);
+
+// Make enablePrivilege continue to work for test code. :-(
+user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", true);
+
+// Get network events.
+user_pref("network.activity.blipIntervalMilliseconds", 250);
+
+// Don't allow the Data Reporting service to prompt for policy acceptance.
+user_pref("datareporting.policy.dataSubmissionPolicyBypassAcceptance", true);
+
+// Point Firefox Health Report at a local server. We don't care if it actually
+// works. It just can't hit the default production endpoint.
+user_pref("datareporting.healthreport.documentServerURI", "http://%(server)s/healthreport/");
+
+// Make sure CSS error reporting is enabled for tests
+user_pref("layout.css.report_errors", true);
new file mode 100644
--- /dev/null
+++ b/testing/profiles/webapps_mochitest.json
@@ -0,0 +1,98 @@
+[
+  {
+    "name": "http_example_org",
+    "csp": "",
+    "origin": "http://example.org",
+    "manifestURL": "http://example.org/manifest.webapp",
+    "description": "http://example.org App",
+    "appStatus": 1
+  },
+  {
+    "name": "https_example_com",
+    "csp": "",
+    "origin": "https://example.com",
+    "manifestURL": "https://example.com/manifest.webapp",
+    "description": "https://example.com App",
+    "appStatus": 1
+  },
+  {
+    "name": "http_test1_example_org",
+    "csp": "",
+    "origin": "http://test1.example.org",
+    "manifestURL": "http://test1.example.org/manifest.webapp",
+    "description": "http://test1.example.org App",
+    "appStatus": 1
+  },
+  {
+    "name": "http_test1_example_org_8000",
+    "csp": "",
+    "origin": "http://test1.example.org:8000",
+    "manifestURL": "http://test1.example.org:8000/manifest.webapp",
+    "description": "http://test1.example.org:8000 App",
+    "appStatus": 1
+  },
+  {
+    "name": "http_sub1_test1_example_org",
+    "csp": "",
+    "origin": "http://sub1.test1.example.org",
+    "manifestURL": "http://sub1.test1.example.org/manifest.webapp",
+    "description": "http://sub1.test1.example.org App",
+    "appStatus": 1
+  },
+  {
+    "name": "https_example_com_privileged",
+    "csp": "",
+    "origin": "https://example.com",
+    "manifestURL": "https://example.com/manifest_priv.webapp",
+    "description": "https://example.com Privileged App",
+    "appStatus": 2
+  },
+  {
+    "name": "https_example_com_certified",
+    "csp": "",
+    "origin": "https://example.com",
+    "manifestURL": "https://example.com/manifest_cert.webapp",
+    "description": "https://example.com Certified App",
+    "appStatus": 3
+  },
+  {
+    "name": "https_example_csp_certified",
+    "csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
+    "origin": "https://example.com",
+    "manifestURL": "https://example.com/manifest_csp_cert.webapp",
+    "description": "https://example.com Certified App with manifest policy",
+    "appStatus": 3
+  },
+  {
+    "name": "https_example_csp_installed",
+    "csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
+    "origin": "https://example.com",
+    "manifestURL": "https://example.com/manifest_csp_inst.webapp",
+    "description": "https://example.com Installed App with manifest policy",
+    "appStatus": 1
+  },
+  {
+    "name": "https_example_csp_privileged",
+    "csp": "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'",
+    "origin": "https://example.com",
+    "manifestURL": "https://example.com/manifest_csp_priv.webapp",
+    "description": "https://example.com Privileged App with manifest policy",
+    "appStatus": 2
+  },
+  {
+    "name": "https_a_domain_certified",
+    "csp": "",
+    "origin": "https://acertified.com",
+    "manifestURL": "https://acertified.com/manifest.webapp",
+    "description": "https://acertified.com Certified App",
+    "appStatus": 3
+  },
+  {
+    "name": "https_a_domain_privileged",
+    "csp": "",
+    "origin": "https://aprivileged.com",
+    "manifestURL": "https://aprivileged.com/manifest.webapp",
+    "description": "https://aprivileged.com Privileged App ",
+    "appStatus": 2
+  }
+]
--- a/toolkit/toolkit.mozbuild
+++ b/toolkit/toolkit.mozbuild
@@ -230,16 +230,17 @@ if CONFIG['ENABLE_MARIONETTE']:
     add_tier_dir('platform', 'testing/marionette')
 
 if CONFIG['ENABLE_TESTS']:
     add_tier_dir('platform', [
         'testing/mochitest',
         'testing/xpcshell',
         'testing/tools/screenshot',
         'testing/peptest',
+        'testing/profiles',
         'testing/mozbase',
         'testing/modules',
     ])
 
     if CONFIG['MOZ_WEBRTC'] and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
             add_tier_dir('platform', [
                 'media/webrtc/signaling/test',
                 'media/mtransport/test',