Bug 1285557 - Add a new WebExtension debug tests into about:debugging tests. r=ochameau,jryans draft
authorLuca Greco <lgreco@mozilla.com>
Mon, 25 Jul 2016 16:20:44 +0200
changeset 392961 b27e7eb0c81974c927b01c1b3923de5e5f201a72
parent 392960 9b8306225d09623d5fdba95cae332a3452ecb83a
child 392962 4f5e0949e374435b208a72d9f9721449118e9da1
push id24157
push userluca.greco@alcacoop.it
push dateTue, 26 Jul 2016 16:01:12 +0000
reviewersochameau, jryans
bugs1285557
milestone50.0a1
Bug 1285557 - Add a new WebExtension debug tests into about:debugging tests. r=ochameau,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 };
+}