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 126623 947b9aa5e2d3961f8334f8656616c68634fd744c
parent 126622 ee67360ec6628de92c2ba6dfa45b19dde050ab2b
child 126624 8cc00926f48657510c1b2cca5366402e322fc112
push id25554
push userahalberstadt@mozilla.com
push dateThu, 28 Mar 2013 20:45:42 +0000
treeherdermozilla-inbound@947b9aa5e2d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgriffin
bugs835930
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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',