Bug 1093566 - Add modules for sharing mochitest-browser test code; r=Gijs r=paolo
authorSteven MacLeod <smacleod@mozilla.com>
Sun, 22 Feb 2015 23:11:22 -0500
changeset 231326 fdbe5abe8675fdcbf32c5c7a8f41a15b9457761c
parent 231325 e1a7c0e78a9d54493403d672c2aec7fed134aecf
child 231327 ec936c510299f0c50b3bd33f18461827ece4b43d
push id28349
push userkwierso@gmail.com
push dateMon, 02 Mar 2015 20:41:54 +0000
treeherdermozilla-central@eaaa45f32e9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, paolo
bugs1093566
milestone39.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 1093566 - Add modules for sharing mochitest-browser test code; r=Gijs r=paolo Currently code used by many mochitest-browser tests is scattered throughout the tree in various head.js files. Many similar or identical helper methods are repeated throughout these files. This commit introduces a BrowserTestUtils.jsm module and includes it in the mochitest scope; the idea being these frequently re-implemented methods can live in a central place. A TestUtils.jsm module has also been introduced to contain code useful to all types of tests.
testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
testing/mochitest/BrowserTestUtils/content/content-utils.js
testing/mochitest/BrowserTestUtils/moz.build
testing/mochitest/browser-test.js
testing/mochitest/jar.mn
testing/modules/TestUtils.jsm
testing/modules/moz.build
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -0,0 +1,84 @@
+/* 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/. */
+
+/*
+ * This module implements a number of utilities useful for browser tests.
+ *
+ * All asynchronous helper methods should return promises, rather than being
+ * callback based.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "BrowserTestUtils",
+];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+
+Cc["@mozilla.org/globalmessagemanager;1"]
+  .getService(Ci.nsIMessageListenerManager)
+  .loadFrameScript(
+    "chrome://mochikit/content/tests/BrowserTestUtils/content-utils.js", true);
+
+
+this.BrowserTestUtils = {
+  /**
+   * @param {xul:browser} browser
+   *        A xul:browser.
+   * @param {Boolean} includeSubFrames
+   *        A boolean indicating if loads from subframes should be included.
+   * @return {Promise}
+   *         A Promise which resolves when a load event is triggered
+   *         for browser.
+   */
+  browserLoaded(browser, includeSubFrames=false) {
+    return new Promise(resolve => {
+      browser.messageManager.addMessageListener("browser-test-utils:loadEvent",
+                                                 function onLoad(msg) {
+        if (!msg.data.subframe || includeSubFrames) {
+          browser.messageManager.removeMessageListener(
+            "browser-test-utils:loadEvent", onLoad);
+          resolve();
+        }
+      });
+    });
+  },
+
+  /**
+   * @param {Object} options
+   *        {
+   *          private: A boolean indicating if the window should be
+   *                   private
+   *        }
+   * @return {Promise}
+   *         Resolves with the new window once it is loaded.
+   */
+  openNewBrowserWindow(options) {
+    return new Promise(resolve => {
+      let argString = Cc["@mozilla.org/supports-string;1"].
+                      createInstance(Ci.nsISupportsString);
+      argString.data = "";
+      let features = "chrome,dialog=no,all";
+
+      if (options && options.private || false) {
+        features += ",private";
+      }
+
+      let win = Services.ww.openWindow(
+        null, Services.prefs.getCharPref("browser.chromeURL"), "_blank",
+        features, argString);
+
+      // Wait for browser-delayed-startup-finished notification, it indicates
+      // that the window has loaded completely and is ready to be used for
+      // testing.
+      TestUtils.topicObserved("browser-delayed-startup-finished", win).then(
+        () => resolve(win));
+    });
+  },
+};
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/BrowserTestUtils/content/content-utils.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+addEventListener("load", function(event) {
+  let subframe = event.target != content.document;
+  sendAsyncMessage("browser-test-utils:loadEvent",
+    {subframe: subframe, url: event.target.documentURI});
+}, true);
+
--- a/testing/mochitest/BrowserTestUtils/moz.build
+++ b/testing/mochitest/BrowserTestUtils/moz.build
@@ -1,9 +1,10 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # 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/.
 
 TESTING_JS_MODULES += [
+    'BrowserTestUtils.jsm',
     'ContentTask.jsm',
 ]
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -115,16 +115,18 @@ function Tester(aTests, aDumper, aCallba
   this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
   this.SimpleTest = simpleTestScope.SimpleTest;
 
   this.SimpleTest.harnessParameters = gConfig;
 
   this.MemoryStats = simpleTestScope.MemoryStats;
   this.Task = Task;
   this.ContentTask = Components.utils.import("resource://testing-common/ContentTask.jsm", null).ContentTask;
+  this.BrowserTestUtils = Components.utils.import("resource://testing-common/BrowserTestUtils.jsm", null).BrowserTestUtils;
+  this.TestUtils = Components.utils.import("resource://testing-common/TestUtils.jsm", null).TestUtils;
   this.Task.Debugging.maintainStack = true;
   this.Promise = Components.utils.import("resource://gre/modules/Promise.jsm", null).Promise;
   this.Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
 
   this.SimpleTestOriginal = {};
   SIMPLETEST_OVERRIDES.forEach(m => {
     this.SimpleTestOriginal[m] = this.SimpleTest[m];
   });
@@ -620,16 +622,18 @@ Tester.prototype = {
     let currentTest = this.currentTest;
 
     // Import utils in the test scope.
     this.currentTest.scope.EventUtils = this.EventUtils;
     this.currentTest.scope.SimpleTest = this.SimpleTest;
     this.currentTest.scope.gTestPath = this.currentTest.path;
     this.currentTest.scope.Task = this.Task;
     this.currentTest.scope.ContentTask = this.ContentTask;
+    this.currentTest.scope.BrowserTestUtils = this.BrowserTestUtils;
+    this.currentTest.scope.TestUtils = this.TestUtils;
     // Pass a custom report function for mochitest style reporting.
     this.currentTest.scope.Assert = new this.Assert(function(err, message, stack) {
       let res;
       if (err) {
         res = new testResult(false, err.message, err.stack, false, err.stack);
       } else {
         res = new testResult(true, message, "", false, stack);
       }
@@ -1016,16 +1020,18 @@ testScope.prototype = {
   __expectedMinAsserts: 0,
   __expectedMaxAsserts: 0,
   __expected: 'pass',
 
   EventUtils: {},
   SimpleTest: {},
   Task: null,
   ContentTask: null,
+  BrowserTestUtils: null,
+  TestUtils: null,
   Assert: null,
 
   /**
    * Add a test function which is a Task function.
    *
    * Task functions are functions fed into Task.jsm's Task.spawn(). They are
    * generators that emit promises.
    *
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -35,9 +35,10 @@ mochikit.jar:
   content/tests/SimpleTest/TestRunner.js (tests/SimpleTest/TestRunner.js)
   content/tests/SimpleTest/iframe-between-tests.html (tests/SimpleTest/iframe-between-tests.html)
   content/tests/SimpleTest/WindowSnapshot.js (tests/SimpleTest/WindowSnapshot.js)
   content/tests/SimpleTest/MockObjects.js (tests/SimpleTest/MockObjects.js)
   content/tests/SimpleTest/NativeKeyCodes.js (tests/SimpleTest/NativeKeyCodes.js)
   content/tests/SimpleTest/paint_listener.js (tests/SimpleTest/paint_listener.js)
   content/tests/SimpleTest/docshell_helpers.js (../../docshell/test/chrome/docshell_helpers.js)
   content/tests/BrowserTestUtils/content-task.js (BrowserTestUtils/content/content-task.js)
+  content/tests/BrowserTestUtils/content-utils.js (BrowserTestUtils/content/content-utils.js)
 
new file mode 100644
--- /dev/null
+++ b/testing/modules/TestUtils.jsm
@@ -0,0 +1,52 @@
+/* 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/. */
+
+/*
+ * This module implements a number of utilities useful for testing.
+ *
+ * Additions to this module should be generic and useful to testing multiple
+ * features. Utilities only useful to a sepcific testing framework should live
+ * in a module specific to that framework, such as BrowserTestUtils.jsm in the
+ * case of mochitest-browser-chrome.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "TestUtils",
+];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.TestUtils = {
+  executeSoon(callbackFn) {
+    Services.tm.mainThread.dispatch(callbackFn, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
+  /**
+   * Waits for the specified topic to be observed.
+   *
+   * @param {string} topic
+   *        The topic to observe.
+   * @param {*} subject
+   *        A value to check the notification subject against. Only a
+   *        notification with a matching subject will cause the promise to
+   *        resolve.
+   * @return {Promise}
+   *         Resolves with the data provided when the topic has been observed.
+   */
+  topicObserved(topic, subject=null) {
+    return new Promise(resolve => {
+      Services.obs.addObserver(function observer(observedSubject, topic, data) {
+        if (subject !== null && subject !== observedSubject) { return; }
+
+        Services.obs.removeObserver(observer, topic);
+        resolve(data);
+      }, topic, false);
+    });
+  },
+};
--- a/testing/modules/moz.build
+++ b/testing/modules/moz.build
@@ -7,9 +7,10 @@
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 
 TESTING_JS_MODULES += [
     'AppData.jsm',
     'AppInfo.jsm',
     'Assert.jsm',
     'StructuredLog.jsm',
+    'TestUtils.jsm',
 ]