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 231193 fdbe5abe8675fdcbf32c5c7a8f41a15b9457761c
parent 231192 e1a7c0e78a9d54493403d672c2aec7fed134aecf
child 231194 ec936c510299f0c50b3bd33f18461827ece4b43d
push id11534
push usersmacleod@mozilla.com
push dateFri, 27 Feb 2015 19:29:47 +0000
treeherderfx-team@be473722fb84 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, paolo
bugs1093566
milestone39.0a1
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',
 ]