Bug 1285557 - Add a new WebExtension debug tests into about:debugging tests. r=ochameau, r=jryans
authorLuca Greco <lgreco@mozilla.com>
Mon, 25 Jul 2016 16:20:44 +0200
changeset 347143 2ccdff083889a48189e6eed2a5230570f88df48b
parent 347142 a6679fb98d015526fbdfd8ff4e97c8ce90c57988
child 347144 dea8a60d4ba975e16c5baeec97ee5e8148eeacaf
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau, jryans
bugs1285557
milestone50.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 1285557 - Add a new WebExtension debug tests into about:debugging tests. r=ochameau, r=jryans MozReview-Commit-ID: Ew1DpyJ940R
devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json
devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js
devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json
devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html
devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
devtools/client/aboutdebugging/test/browser.ini
devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
devtools/client/aboutdebugging/test/browser_addons_install.js
devtools/client/aboutdebugging/test/browser_addons_reload.js
devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
devtools/client/aboutdebugging/test/head.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json
@@ -0,0 +1,10 @@
+{
+  "manifest_version": 2,
+  "name": "test-devtools-webextension-nobg",
+  "version": "1.0",
+  "applications": {
+    "gecko": {
+      "id": "test-devtools-webextension-nobg@mozilla.org"
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* global browser */
+
+"use strict";
+
+document.body.innerText = "Background Page Body Test Content";
+
+// This function are called from the webconsole test:
+// browser_addons_debug_webextension.js
+
+function myWebExtensionAddonFunction() {  // eslint-disable-line no-unused-vars
+  console.log("Background page function called", browser.runtime.getManifest());
+}
+
+function myWebExtensionShowPopup() {  // eslint-disable-line no-unused-vars
+  console.log("readyForOpenPopup");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json
@@ -0,0 +1,17 @@
+{
+  "manifest_version": 2,
+  "name": "test-devtools-webextension",
+  "version": "1.0",
+  "applications": {
+    "gecko": {
+      "id": "test-devtools-webextension@mozilla.org"
+    }
+  },
+  "background": {
+    "scripts": ["bg.js"]
+  },
+  "browser_action": {
+    "default_title": "WebExtension Popup Debugging",
+    "default_popup": "popup.html"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <script src="popup.js"></script>
+  </head>
+  <body>
+    Background Page Body Test Content
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* global browser */
+
+"use strict";
+
+// This function is called from the webconsole test:
+// browser_addons_debug_webextension.js
+function myWebExtensionPopupAddonFunction() {  // eslint-disable-line no-unused-vars
+  console.log("Popup page function called", browser.runtime.getManifest());
+}
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -2,23 +2,29 @@
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   addons/unpacked/bootstrap.js
   addons/unpacked/install.rdf
   addons/bad/manifest.json
   addons/bug1273184.xpi
+  addons/test-devtools-webextension/*
+  addons/test-devtools-webextension-nobg/*
   service-workers/empty-sw.html
   service-workers/empty-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
   !/devtools/client/framework/test/shared-head.js
 
 [browser_addons_debug_bootstrapped.js]
+[browser_addons_debug_webextension.js]
+[browser_addons_debug_webextension_inspector.js]
+[browser_addons_debug_webextension_nobg.js]
+[browser_addons_debug_webextension_popup.js]
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
 [browser_addons_reload.js]
 [browser_addons_toggle_debug.js]
 [browser_page_not_found.js]
 [browser_service_workers.js]
 [browser_service_workers_not_compatible.js]
 [browser_service_workers_push.js]
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
@@ -21,18 +21,21 @@ add_task(function* () {
       // Enable Browser toolbox test script execution via env variable
       ["devtools.browser-toolbox.allow-unsafe-script", true],
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
-  yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
-                     "test-devtools");
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: ADDON_NAME,
+  });
 
   // Retrieve the DEBUG button for the addon
   let names = [...document.querySelectorAll("#addons .target-name")];
   let name = names.filter(element => element.textContent === ADDON_NAME)[0];
   ok(name, "Found the addon in the list");
   let targetElement = name.parentNode.parentNode;
   let debugBtn = targetElement.querySelector(".debug-button");
   ok(debugBtn, "Found its debug button");
@@ -70,11 +73,11 @@ add_task(function* () {
   debugBtn.click();
 
   yield onCustomMessage;
   ok(true, "Received the notification message from the bootstrap.js function");
 
   yield onToolboxClose;
   ok(true, "Addon toolbox closed");
 
-  yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   yield closeAboutDebugging(tab);
 });
copy from devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
copy to devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -1,80 +1,74 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
-const ADDON_ID = "test-devtools@mozilla.org";
-const ADDON_NAME = "test-devtools";
+const ADDON_ID = "test-devtools-webextension@mozilla.org";
+const ADDON_NAME = "test-devtools-webextension";
+const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
 
-const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+const {
+  BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
 
-add_task(function* () {
-  yield new Promise(resolve => {
-    let options = {"set": [
-      // Force enabling of addons debugging
-      ["devtools.chrome.enabled", true],
-      ["devtools.debugger.remote-enabled", true],
-      // Disable security prompt
-      ["devtools.debugger.prompt-connection", false],
-      // Enable Browser toolbox test script execution via env variable
-      ["devtools.browser-toolbox.allow-unsafe-script", true],
-    ]};
-    SpecialPowers.pushPrefEnv(options, resolve);
-  });
-
-  let { tab, document } = yield openAboutDebugging("addons");
-  yield waitForInitialAddonList(document);
-  yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
-                     "test-devtools");
-
-  // Retrieve the DEBUG button for the addon
-  let names = [...document.querySelectorAll("#addons .target-name")];
-  let name = names.filter(element => element.textContent === ADDON_NAME)[0];
-  ok(name, "Found the addon in the list");
-  let targetElement = name.parentNode.parentNode;
-  let debugBtn = targetElement.querySelector(".debug-button");
-  ok(debugBtn, "Found its debug button");
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - when the debug button is clicked on a webextension, the opened toolbox
+ *   has a working webconsole with the background page as default target;
+ */
+add_task(function* testWebExtensionsToolboxWebConsole() {
+  let {
+    tab, document, debugBtn,
+  } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
 
   // Wait for a notification sent by a script evaluated the test addon via
   // the web console.
   let onCustomMessage = new Promise(done => {
-    Services.obs.addObserver(function listener() {
-      Services.obs.removeObserver(listener, "addon-console-works");
-      done();
-    }, "addon-console-works", false);
+    Services.obs.addObserver(function listener(message, topic) {
+      let apiMessage = message.wrappedJSObject;
+      if (!apiMessage.originAttributes ||
+          apiMessage.originAttributes.addonId != ADDON_ID) {
+        return;
+      }
+      Services.obs.removeObserver(listener, "console-api-log-event");
+      done(apiMessage.arguments);
+    }, "console-api-log-event", false);
   });
 
   // Be careful, this JS function is going to be executed in the addon toolbox,
   // which lives in another process. So do not try to use any scope variable!
   let env = Cc["@mozilla.org/process/environment;1"]
               .getService(Ci.nsIEnvironment);
   let testScript = function () {
     /* eslint-disable no-undef */
     toolbox.selectTool("webconsole")
       .then(console => {
         let { jsterm } = console.hud;
-        return jsterm.execute("myBootstrapAddonFunction()");
+        return jsterm.execute("myWebExtensionAddonFunction()");
       })
       .then(() => toolbox.destroy());
     /* eslint-enable no-undef */
   };
   env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
   registerCleanupFunction(() => {
     env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
   });
 
   let onToolboxClose = BrowserToolboxProcess.once("close");
 
   debugBtn.click();
 
-  yield onCustomMessage;
-  ok(true, "Received the notification message from the bootstrap.js function");
+  let args = yield onCustomMessage;
+  ok(true, "Received console message from the background page function as expected");
+  is(args[0], "Background page function called", "Got the expected console message");
+  is(args[1] && args[1].name, ADDON_NAME,
+     "Got the expected manifest from WebExtension API");
 
   yield onToolboxClose;
   ok(true, "Addon toolbox closed");
 
-  yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   yield closeAboutDebugging(tab);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_ID = "test-devtools-webextension@mozilla.org";
+const ADDON_NAME = "test-devtools-webextension";
+const ADDON_PATH = "addons/test-devtools-webextension/manifest.json";
+
+const {
+  BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - the webextension developer toolbox has a working Inspector panel, with the
+ *   background page as default target;
+ */
+add_task(function* testWebExtensionsToolboxInspector() {
+  let {
+    tab, document, debugBtn,
+  } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_PATH);
+
+  // Be careful, this JS function is going to be executed in the addon toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  let env = Cc["@mozilla.org/process/environment;1"]
+        .getService(Ci.nsIEnvironment);
+  let testScript = function () {
+    /* eslint-disable no-undef */
+    toolbox.selectTool("inspector")
+      .then(inspector => {
+        return inspector.walker.querySelector(inspector.walker.rootNode, "body");
+      })
+      .then((nodeActor) => {
+        if (!nodeActor) {
+          throw new Error("nodeActor not found");
+        }
+
+        dump("Got a nodeActor\n");
+
+        if (!(nodeActor.inlineTextChild)) {
+          throw new Error("inlineTextChild not found");
+        }
+
+        dump("Got a nodeActor with an inline text child\n");
+
+        let expectedValue = "Background Page Body Test Content";
+        let actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+        if (String(actualValue).trim() !== String(expectedValue).trim()) {
+          throw new Error(
+            `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+          );
+        }
+
+        dump("Got the expected inline text content in the selected node\n");
+        return Promise.resolve();
+      })
+      .then(() => toolbox.destroy())
+      .catch((error) => {
+        dump("Error while running code in the browser toolbox process:\n");
+        dump(error + "\n");
+        dump("stack:\n" + error.stack + "\n");
+      });
+    /* eslint-enable no-undef */
+  };
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+  });
+
+  let onToolboxClose = BrowserToolboxProcess.once("close");
+  debugBtn.click();
+  yield onToolboxClose;
+
+  ok(true, "Addon toolbox closed");
+
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+  yield closeAboutDebugging(tab);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
+const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
+const ADDON_NOBG_PATH = "addons/test-devtools-webextension-nobg/manifest.json";
+
+const {
+  BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - the webextension developer toolbox is connected to a fallback page when the
+ *   background page is not available (and in the fallback page document body contains
+ *   the expected message, which warns the user that the current page is not a real
+ *   webextension context);
+ */
+add_task(function* testWebExtensionsToolboxNoBackgroundPage() {
+  let {
+    tab, document, debugBtn,
+  } = yield setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, ADDON_NOBG_PATH);
+
+  // Be careful, this JS function is going to be executed in the addon toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  let env = Cc["@mozilla.org/process/environment;1"]
+        .getService(Ci.nsIEnvironment);
+  let testScript = function () {
+    /* eslint-disable no-undef */
+    toolbox.selectTool("inspector")
+      .then(inspector => {
+        return inspector.walker.querySelector(inspector.walker.rootNode, "body");
+      })
+      .then((nodeActor) => {
+        if (!nodeActor) {
+          throw new Error("nodeActor not found");
+        }
+
+        dump("Got a nodeActor\n");
+
+        if (!(nodeActor.inlineTextChild)) {
+          throw new Error("inlineTextChild not found");
+        }
+
+        dump("Got a nodeActor with an inline text child\n");
+
+        let expectedValue = "Your addon does not have any document opened yet.";
+        let actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+        if (actualValue !== expectedValue) {
+          throw new Error(
+            `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+          );
+        }
+
+        dump("Got the expected inline text content in the selected node\n");
+        return Promise.resolve();
+      })
+      .then(() => toolbox.destroy())
+      .catch((error) => {
+        dump("Error while running code in the browser toolbox process:\n");
+        dump(error + "\n");
+        dump("stack:\n" + error.stack + "\n");
+      });
+    /* eslint-enable no-undef */
+  };
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+  });
+
+  let onToolboxClose = BrowserToolboxProcess.once("close");
+  debugBtn.click();
+  yield onToolboxClose;
+
+  ok(true, "Addon toolbox closed");
+
+  yield uninstallAddon({document, id: ADDON_NOBG_ID, name: ADDON_NOBG_NAME});
+  yield closeAboutDebugging(tab);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -0,0 +1,180 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
+requestLongerTimeout(2);
+
+const ADDON_ID = "test-devtools-webextension@mozilla.org";
+const ADDON_NAME = "test-devtools-webextension";
+const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
+
+const {
+  BrowserToolboxProcess
+} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+
+/**
+ * This test file ensures that the webextension addon developer toolbox:
+ * - when the debug button is clicked on a webextension, the opened toolbox
+ *   has a working webconsole with the background page as default target;
+ * - the webextension developer toolbox has a working Inspector panel, with the
+ *   background page as default target;
+ * - the webextension developer toolbox is connected to a fallback page when the
+ *   background page is not available (and in the fallback page document body contains
+ *   the expected message, which warns the user that the current page is not a real
+ *   webextension context);
+ * - the webextension developer toolbox has a frame list menu and the noautohide toolbar
+ *   toggle button, and they can be used to switch the current target to the extension
+ *   popup page.
+ */
+
+/**
+ * Returns the widget id for an extension with the passed id.
+ */
+function makeWidgetId(id) {
+  id = id.toLowerCase();
+  return id.replace(/[^a-z0-9_-]/g, "_");
+}
+
+add_task(function* testWebExtensionsToolboxSwitchToPopup() {
+  let {
+    tab, document, debugBtn,
+  } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
+
+  let onReadyForOpenPopup = new Promise(done => {
+    Services.obs.addObserver(function listener(message, topic) {
+      let apiMessage = message.wrappedJSObject;
+      if (!apiMessage.originAttributes ||
+          apiMessage.originAttributes.addonId != ADDON_ID) {
+        return;
+      }
+
+      if (apiMessage.arguments[0] == "readyForOpenPopup") {
+        Services.obs.removeObserver(listener, "console-api-log-event");
+        done();
+      }
+    }, "console-api-log-event", false);
+  });
+
+  // Be careful, this JS function is going to be executed in the addon toolbox,
+  // which lives in another process. So do not try to use any scope variable!
+  let env = Cc["@mozilla.org/process/environment;1"]
+        .getService(Ci.nsIEnvironment);
+  let testScript = function () {
+    /* eslint-disable no-undef */
+
+    let jsterm;
+
+    toolbox.selectTool("webconsole")
+      .then(console => {
+        dump(`Clicking the noautohide button\n`);
+        toolbox.doc.getElementById("command-button-noautohide").click();
+        dump(`Clicked the noautohide button\n`);
+
+        let waitForFrameListUpdate = new Promise((done) => {
+          toolbox.target.once("frame-update", () => {
+            done(console);
+          });
+        });
+
+        jsterm = console.hud.jsterm;
+        jsterm.execute("myWebExtensionShowPopup()");
+
+        // Wait the initial frame update (which list the background page).
+        return waitForFrameListUpdate;
+      })
+      .then((console) => {
+        // Wait the new frame update (once the extension popup has been opened).
+        return new Promise((done) => {
+          toolbox.target.once("frame-update", done);
+        });
+      })
+      .then(() => {
+        dump(`Clicking the frame list button\n`);
+        let btn = toolbox.doc.getElementById("command-button-frames");
+        let menu = toolbox.showFramesMenu({target: btn});
+        dump(`Clicked the frame list button\n`);
+        return menu.once("open").then(() => {
+          return menu;
+        });
+      })
+      .then(frameMenu => {
+        let frames = frameMenu.items;
+
+        if (frames.length != 2) {
+          throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
+        }
+
+        let popupFrameBtn = frames.filter((frame) => {
+          return frame.label.endsWith("popup.html");
+        }).pop();
+
+        if (!popupFrameBtn) {
+          throw Error("Extension Popup frame not found in the listed frames");
+        }
+
+        let waitForNavigated = toolbox.target.once("navigate");
+
+        popupFrameBtn.click();
+
+        return waitForNavigated;
+      })
+      .then(() => {
+        return jsterm.execute("myWebExtensionPopupAddonFunction()");
+      })
+      .then(() => toolbox.destroy())
+      .catch((error) => {
+        dump("Error while running code in the browser toolbox process:\n");
+        dump(error + "\n");
+        dump("stack:\n" + error.stack + "\n");
+      });
+    /* eslint-enable no-undef */
+  };
+  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+  registerCleanupFunction(() => {
+    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+  });
+
+  // Wait for a notification sent by a script evaluated the test addon via
+  // the web console.
+  let onPopupCustomMessage = new Promise(done => {
+    Services.obs.addObserver(function listener(message, topic) {
+      let apiMessage = message.wrappedJSObject;
+      if (!apiMessage.originAttributes ||
+          apiMessage.originAttributes.addonId != ADDON_ID) {
+        return;
+      }
+
+      if (apiMessage.arguments[0] == "Popup page function called") {
+        Services.obs.removeObserver(listener, "console-api-log-event");
+        done(apiMessage.arguments);
+      }
+    }, "console-api-log-event", false);
+  });
+
+  let onToolboxClose = BrowserToolboxProcess.once("close");
+
+  debugBtn.click();
+
+  yield onReadyForOpenPopup;
+
+  let browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
+  let browserActionEl = window.document.getElementById(browserActionId);
+
+  ok(browserActionEl, "Got the browserAction button from the browser UI");
+  browserActionEl.click();
+  info("Clicked on the browserAction button");
+
+  let args = yield onPopupCustomMessage;
+  ok(true, "Received console message from the popup page function as expected");
+  is(args[0], "Popup page function called", "Got the expected console message");
+  is(args[1] && args[1].name, ADDON_NAME,
+     "Got the expected manifest from WebExtension API");
+
+  yield onToolboxClose;
+
+  ok(true, "Addon toolbox closed");
+
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
@@ -45,26 +45,29 @@ function* testCheckboxState(testData) {
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
 
   info("Install a test addon.");
-  yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
-                     "test-devtools");
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: ADDON_NAME,
+  });
 
   info("Test checkbox checked state.");
   let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
   is(addonDebugCheckbox.checked, testData.expected,
     "Addons debugging checkbox should be in expected state.");
 
   info("Test debug buttons disabled state.");
   let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
   ok(debugButtons.every(b => b.disabled != testData.expected),
     "Debug buttons should be in the expected state");
 
   info("Uninstall test addon installed earlier.");
-  yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
 
   yield closeAboutDebugging(tab);
 }
--- a/devtools/client/aboutdebugging/test/browser_addons_install.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_install.js
@@ -5,21 +5,24 @@
 const ADDON_ID = "test-devtools@mozilla.org";
 const ADDON_NAME = "test-devtools";
 
 add_task(function* () {
   let { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
 
   // Install this add-on, and verify that it appears in the about:debugging UI
-  yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
-                     "test-devtools");
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: ADDON_NAME,
+  });
 
   // Install the add-on, and verify that it disappears in the about:debugging UI
-  yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
 
   yield closeAboutDebugging(tab);
 });
 
 add_task(function* () {
   let { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
 
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -104,18 +104,21 @@ class TempWebExt {
   remove() {
     return this.tmpDir.remove(true);
   }
 }
 
 add_task(function* reloadButtonReloadsAddon() {
   const { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
-  yield installAddon(document, "addons/unpacked/install.rdf",
-                     ADDON_NAME, ADDON_NAME);
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: ADDON_NAME,
+  });
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
   is(reloadButton.disabled, false, "Reload button should not be disabled");
   is(reloadButton.title, "", "Reload button should not have a tooltip");
   const onInstalled = promiseAddonEvent("onInstalled");
 
   const onBootstrapInstallCalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
--- a/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
@@ -18,18 +18,21 @@ add_task(function* () {
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { tab, document } = yield openAboutDebugging("addons");
   yield waitForInitialAddonList(document);
 
   info("Install a test addon.");
-  yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
-                     "test-devtools");
+  yield installAddon({
+    document,
+    path: "addons/unpacked/install.rdf",
+    name: ADDON_NAME,
+  });
 
   let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
   ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
 
   info("Check all debug buttons are disabled.");
   let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
   ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
 
@@ -51,12 +54,12 @@ add_task(function* () {
   addonDebugCheckbox.click();
   yield onAddonsMutation;
 
   info("Check all debug buttons are disabled again.");
   debugButtons = [...document.querySelectorAll("#addons .debug-button")];
   ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
 
   info("Uninstall addon installed earlier.");
-  yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
+  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
 
   yield closeAboutDebugging(tab);
 });
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -1,27 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-env browser */
 /* exported openAboutDebugging, changeAboutDebuggingHash, closeAboutDebugging,
    installAddon, uninstallAddon, waitForMutation, assertHasTarget,
    getServiceWorkerList, getTabList, openPanel, waitForInitialAddonList,
    waitForServiceWorkerRegistered, unregisterServiceWorker,
-   waitForDelayedStartupFinished */
+   waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension */
 /* import-globals-from ../../framework/test/shared-head.js */
 
 "use strict";
 
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+const { Management } = Cu.import("resource://gre/modules/Extension.jsm", {});
+
 DevToolsUtils.testing = true;
 
 registerCleanupFunction(() => {
   DevToolsUtils.testing = false;
 });
 
 function* openAboutDebugging(page, win) {
   info("opening about:debugging");
@@ -143,55 +145,70 @@ function getServiceWorkerList(document) 
  * @param  {DOMDocument}  document   #tabs section container document
  * @return {DOMNode}                 target list or container element
  */
 function getTabList(document) {
   return document.querySelector("#tabs .target-list") ||
     document.querySelector("#tabs.targets");
 }
 
-function* installAddon(document, path, name, evt) {
+function* installAddon({document, path, name, isWebExtension}) {
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(null);
   let file = getSupportsFile(path);
   MockFilePicker.returnFiles = [file.file];
 
   let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
-  // Wait for a message sent by the addon's bootstrap.js file
-  let onAddonInstalled = new Promise(done => {
-    Services.obs.addObserver(function listener() {
-      Services.obs.removeObserver(listener, evt);
+  let onAddonInstalled;
+
+  if (isWebExtension) {
+    onAddonInstalled = new Promise(done => {
+      Management.on("startup", function listener(event, extension) {
+        if (extension.name != name) {
+          return;
+        }
 
-      done();
-    }, evt, false);
-  });
+        Management.off("startup", listener);
+        done();
+      });
+    });
+  } else {
+    // Wait for a "test-devtools" message sent by the addon's bootstrap.js file
+    onAddonInstalled = new Promise(done => {
+      Services.obs.addObserver(function listener() {
+        Services.obs.removeObserver(listener, "test-devtools");
+
+        done();
+      }, "test-devtools", false);
+    });
+  }
   // Trigger the file picker by clicking on the button
   document.getElementById("load-addon-from-file").click();
 
   yield onAddonInstalled;
   ok(true, "Addon installed and running its bootstrap.js file");
 
   // Check that the addon appears in the UI
   yield addonListMutation;
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(name),
     "The addon name appears in the list of addons: " + names);
 }
 
-function* uninstallAddon(document, addonId, addonName) {
+function* uninstallAddon({document, id, name}) {
   let addonList = getAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   // Now uninstall this addon
   yield new Promise(done => {
-    AddonManager.getAddonByID(addonId, addon => {
+    AddonManager.getAddonByID(id, addon => {
       let listener = {
         onUninstalled: function (uninstalledAddon) {
           if (uninstalledAddon != addon) {
             return;
           }
           AddonManager.removeAddonListener(listener);
 
           done();
@@ -201,17 +218,17 @@ function* uninstallAddon(document, addon
       addon.uninstall();
     });
   });
 
   // Ensure that the UI removes the addon from the list
   yield addonListMutation;
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
-  ok(!names.includes(addonName),
+  ok(!names.includes(name),
     "After uninstall, the addon name disappears from the list of addons: "
     + names);
 }
 
 /**
  * Returns a promise that will resolve when the add-on list has been updated.
  *
  * @param {Node} document
@@ -307,8 +324,46 @@ function waitForDelayedStartupFinished(w
     Services.obs.addObserver(function observer(subject, topic) {
       if (win == subject) {
         Services.obs.removeObserver(observer, topic);
         resolve();
       }
     }, "browser-delayed-startup-finished", false);
   });
 }
+
+/**
+ * open the about:debugging page and install an addon
+ */
+function* setupTestAboutDebuggingWebExtension(name, path) {
+  yield new Promise(resolve => {
+    let options = {"set": [
+      // Force enabling of addons debugging
+      ["devtools.chrome.enabled", true],
+      ["devtools.debugger.remote-enabled", true],
+      // Disable security prompt
+      ["devtools.debugger.prompt-connection", false],
+      // Enable Browser toolbox test script execution via env variable
+      ["devtools.browser-toolbox.allow-unsafe-script", true],
+    ]};
+    SpecialPowers.pushPrefEnv(options, resolve);
+  });
+
+  let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+
+  yield installAddon({
+    document,
+    path,
+    name,
+    isWebExtension: true,
+  });
+
+  // Retrieve the DEBUG button for the addon
+  let names = [...document.querySelectorAll("#addons .target-name")];
+  let nameEl = names.filter(element => element.textContent === name)[0];
+  ok(name, "Found the addon in the list");
+  let targetElement = nameEl.parentNode.parentNode;
+  let debugBtn = targetElement.querySelector(".debug-button");
+  ok(debugBtn, "Found its debug button");
+
+  return { tab, document, debugBtn };
+}