Bug 1444392 - Part 1 - Add test-only helpers to open and close the main menu. r=Gijs draft
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Tue, 03 Apr 2018 11:54:29 +0100
changeset 776589 b847de9b4565c82333363deaa4073e7539e15b11
parent 776477 d75d996016dcf325c2db2ed8a47af512d07ffacd
child 776590 db491b30c3149995e5681dfb3fa975233bac05db
push id104910
push userpaolo.mozmail@amadzone.org
push dateTue, 03 Apr 2018 10:55:58 +0000
reviewersGijs
bugs1444392
milestone61.0a1
Bug 1444392 - Part 1 - Add test-only helpers to open and close the main menu. r=Gijs MozReview-Commit-ID: 9kvUdw2TeHB
browser/components/customizableui/moz.build
browser/components/customizableui/test/CustomizableUITestUtils.jsm
browser/components/customizableui/test/browser_947914_button_paste.js
browser/components/customizableui/test/browser_967000_button_feeds.js
browser/components/customizableui/test/browser_editcontrols_update.js
browser/components/customizableui/test/browser_library_after_appMenu.js
browser/components/customizableui/test/browser_panelUINotifications.js
browser/components/customizableui/test/browser_panel_keyboard_navigation.js
browser/components/customizableui/test/browser_panel_toggle.js
browser/components/customizableui/test/head.js
--- a/browser/components/customizableui/moz.build
+++ b/browser/components/customizableui/moz.build
@@ -5,16 +5,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'content',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
+TESTING_JS_MODULES += [
+    'test/CustomizableUITestUtils.jsm',
+]
+
 EXTRA_JS_MODULES += [
     'CustomizableUI.jsm',
     'CustomizableWidgets.jsm',
     'CustomizeMode.jsm',
     'DragPositionManager.jsm',
     'PanelMultiView.jsm',
     'ScrollbarSampler.jsm',
     'SearchWidgetTracker.jsm',
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/CustomizableUITestUtils.jsm
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Shared functions generally available for tests involving PanelMultiView and
+ * the CustomizableUI elements in the browser window.
+ */
+
+var EXPORTED_SYMBOLS = ["CustomizableUITestUtils"];
+
+ChromeUtils.import("resource://testing-common/Assert.jsm");
+ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+class CustomizableUITestUtils {
+  /**
+   * Constructs an instance that operates with the specified browser window.
+   */
+  constructor(window) {
+    this.window = window;
+    this.document = window.document;
+    this.PanelUI = window.PanelUI;
+  }
+
+  /**
+   * Opens a closed PanelMultiView via the specified function while waiting for
+   * the main view with the specified ID to become fully interactive.
+   */
+  async openPanelMultiView(panel, mainView, openFn) {
+    if (panel.state == "open") {
+      // Some tests may intermittently leave the panel open. We report this, but
+      // don't fail so we don't introduce new intermittent test failures.
+      Assert.ok(true, "A previous test left the panel open. This should be" +
+                      " fixed, but we can still do a best-effort recovery and" +
+                      " assume that the requested view will be made visible.");
+      return openFn();
+    }
+
+    if (panel.state == "hiding") {
+      // There may still be tests that don't wait after invoking a command that
+      // causes the main menu panel to close. Depending on timing, the panel may
+      // or may not be fully closed when the following test runs. We handle this
+      // case gracefully so we don't risk introducing new intermittent test
+      // failures that may show up at a later time.
+      Assert.ok(true, "A previous test requested the panel to close but" +
+                      " didn't wait for the operation to complete. While" +
+                      " the test should be fixed, we can still continue.");
+    } else {
+      Assert.equal(panel.state, "closed", "The panel is closed to begin with.");
+    }
+
+    let promiseShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
+    await openFn();
+    await promiseShown;
+  }
+
+  /**
+   * Closes an open PanelMultiView via the specified function while waiting for
+   * the operation to complete.
+   */
+  async hidePanelMultiView(panel, closeFn) {
+    Assert.ok(panel.state == "open", "The panel is open to begin with.");
+
+    let promiseHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+    await closeFn();
+    await promiseHidden;
+  }
+
+  /**
+   * Opens the main menu and waits for it to become fully interactive.
+   */
+  async openMainMenu() {
+    await this.openPanelMultiView(this.PanelUI.panel, this.PanelUI.mainView,
+                                  () => this.PanelUI.show());
+  }
+
+  /**
+   * Closes the main menu and waits for the operation to complete.
+   */
+  async hideMainMenu() {
+    await this.hidePanelMultiView(this.PanelUI.panel,
+                                  () => this.PanelUI.hide());
+  }
+}
--- a/browser/components/customizableui/test/browser_947914_button_paste.js
+++ b/browser/components/customizableui/test/browser_947914_button_paste.js
@@ -24,22 +24,24 @@ add_task(async function() {
     ok(pasteButton, "Paste button exists in Panel Menu");
 
     // add text to clipboard
     let text = "Sample text for testing";
     clipboard.copyString(text);
 
     // test paste button by pasting text to URL bar
     gURLBar.focus();
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     info("Menu panel was opened");
 
     ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled");
     pasteButton.click();
 
     is(gURLBar.value, text, "Text pasted successfully");
+
+    await gCUITestUtils.hideMainMenu();
   });
 });
 
 registerCleanupFunction(function cleanup() {
   CustomizableUI.reset();
   Services.clipboard.emptyClipboard(globalClipboard);
 });
--- a/browser/components/customizableui/test/browser_967000_button_feeds.js
+++ b/browser/components/customizableui/test/browser_967000_button_feeds.js
@@ -28,17 +28,17 @@ add_task(async function() {
 
   let panelHidePromise = promiseOverflowHidden(window);
   await document.getElementById("nav-bar").overflowable._panel.hidePopup();
   await panelHidePromise;
 
   newTab = gBrowser.selectedTab;
   await promiseTabLoadEvent(newTab, TEST_PAGE);
 
-  await PanelUI.show();
+  await gCUITestUtils.openMainMenu();
 
   await waitForCondition(() => !feedButton.hasAttribute("disabled"));
   ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled");
 
   feedButton.click();
   await promiseTabLoadEvent(newTab, TEST_FEED);
 
   is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened");
--- a/browser/components/customizableui/test/browser_editcontrols_update.js
+++ b/browser/components/customizableui/test/browser_editcontrols_update.js
@@ -47,42 +47,38 @@ function expectCommandUpdate(count, test
 add_task(async function test_init() {
   // Put something on the clipboard to verify that the paste button is properly enabled during the test.
   let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
   await new Promise(resolve => {
     SimpleTest.waitForClipboard("Sample", function() { clipboardHelper.copyString("Sample"); }, resolve);
   });
 
   // Open and close the panel first so that it is fully initialized.
-  await PanelUI.show();
-  let hiddenPromise = promisePanelHidden(window);
-  PanelUI.hide();
-  await hiddenPromise;
+  await gCUITestUtils.openMainMenu();
+  await gCUITestUtils.hideMainMenu();
 });
 
 // Test updating when the panel is open with the edit-controls on the panel.
 // Updates should occur.
 add_task(async function test_panelui_opened() {
   gURLBar.focus();
   gURLBar.value = "test";
 
-  await PanelUI.show();
+  await gCUITestUtils.openMainMenu();
 
   checkState(false, "Update when edit-controls is on panel and visible");
 
   let overridePromise = expectCommandUpdate(1);
   gURLBar.select();
   await overridePromise;
 
   checkState(true, "Update when edit-controls is on panel and selection changed");
 
   overridePromise = expectCommandUpdate(0);
-  let hiddenPromise = promisePanelHidden(window);
-  PanelUI.hide();
-  await hiddenPromise;
+  await gCUITestUtils.hideMainMenu();
   await overridePromise;
 
   // Check that updates do not occur after the panel has been closed.
   checkState(true, "Update when edit-controls is on panel and hidden");
 
   // Mac will update the enabled st1ate even when the panel is closed so that
   // main menubar shortcuts will work properly.
   overridePromise = expectCommandUpdate(isMac ? 1 : 0);
--- a/browser/components/customizableui/test/browser_library_after_appMenu.js
+++ b/browser/components/customizableui/test/browser_library_after_appMenu.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Checks that opening the Library view using the default toolbar button works
  * also while the view is displayed in the main menu.
  */
 add_task(async function test_library_after_appMenu() {
-  await PanelUI.show();
+  await gCUITestUtils.openMainMenu();
 
   // Show the Library view as a subview of the main menu.
   let libraryView = document.getElementById("appMenu-libraryView");
   let promise = BrowserTestUtils.waitForEvent(libraryView, "ViewShown");
   document.getElementById("appMenu-library-button").click();
   await promise;
 
   // Show the Library view as the main view of the Library panel.
--- a/browser/components/customizableui/test/browser_panelUINotifications.js
+++ b/browser/components/customizableui/test/browser_panelUINotifications.js
@@ -68,26 +68,26 @@ add_task(async function testSecondaryAct
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
 
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is hidden on PanelUI button.");
     let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
     is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
-    await PanelUI.hide();
+    await gCUITestUtils.hideMainMenu();
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is shown on PanelUI button.");
 
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
 
     AppMenuNotifications.removeNotification(/.*/);
   });
 });
 
 /**
@@ -119,17 +119,17 @@ add_task(async function testInteractionW
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
 
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is displaying on PanelUI button.");
 
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "Badge is hidden on PanelUI button.");
     let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
     is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(mainActionCalled, "Main action callback was called");
 
@@ -193,19 +193,19 @@ add_task(async function testMultipleBadg
     is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
     AppMenuNotifications.removeNotification(/^update-/);
     is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
     AppMenuNotifications.removeNotification(/^fxa-/);
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
 
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
-    PanelUI.hide();
+    await gCUITestUtils.hideMainMenu();
 
     AppMenuNotifications.showBadgeOnlyNotification("fxa-needs-authentication");
     AppMenuNotifications.showBadgeOnlyNotification("update-succeeded");
     AppMenuNotifications.removeNotification(/.*/);
     is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
   });
 });
 
@@ -247,29 +247,29 @@ add_task(async function testMultipleNonB
     is(doorhanger.id, "appMenu-update-restart-notification", "PanelUI is displaying the update-restart notification.");
 
     let secondaryActionButton = doc.getAnonymousElementByAttribute(doorhanger, "anonid", "secondarybutton");
     secondaryActionButton.click();
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-restart", "update-restart badge is displaying on PanelUI button.");
 
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-restart", "update-restart badge is hidden on PanelUI button.");
     let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
     is(menuItem.label, menuItem.getAttribute("label-update-restart"), "Showing correct label");
     is(menuItem.hidden, false, "update-restart menu item is showing.");
 
     menuItem.click();
     ok(updateRestartAction.called, "update-restart main action callback was called");
 
     is(PanelUI.notificationPanel.state, "closed", "update-manual doorhanger is closed.");
     is(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is displaying on PanelUI button.");
 
-    await PanelUI.show();
+    await gCUITestUtils.openMainMenu();
     isnot(PanelUI.menuButton.getAttribute("badge-status"), "update-manual", "update-manual badge is hidden on PanelUI button.");
     is(menuItem.label, menuItem.getAttribute("label-update-manual"), "Showing correct label");
     is(menuItem.hidden, false, "update-manual menu item is showing.");
 
     menuItem.click();
     ok(updateManualAction.called, "update-manual main action callback was called");
   });
 });
--- a/browser/components/customizableui/test/browser_panel_keyboard_navigation.js
+++ b/browser/components/customizableui/test/browser_panel_keyboard_navigation.js
@@ -3,19 +3,17 @@
 /**
  * Test keyboard navigation in the app menu panel.
  */
 
 const {PanelView} = ChromeUtils.import("resource:///modules/PanelMultiView.jsm", {});
 const kHelpButtonId = "appMenu-help-button";
 
 add_task(async function testUpDownKeys() {
-  let promise = promisePanelShown(window);
-  PanelUI.show();
-  await promise;
+  await gCUITestUtils.openMainMenu();
 
   let buttons = PanelView.forNode(PanelUI.mainView).getNavigableElements();
 
   for (let button of buttons) {
     if (button.disabled)
       continue;
     EventUtils.synthesizeKey("KEY_ArrowDown");
     Assert.equal(document.commandDispatcher.focusedElement, button,
@@ -30,35 +28,31 @@ add_task(async function testUpDownKeys()
     let button = buttons[i];
     if (button.disabled)
       continue;
     EventUtils.synthesizeKey("KEY_ArrowUp");
     Assert.equal(document.commandDispatcher.focusedElement, button,
       "The first button should be focused after navigating upward");
   }
 
-  promise = promisePanelHidden(window);
-  PanelUI.hide();
-  await promise;
+  await gCUITestUtils.hideMainMenu();
 });
 
 add_task(async function testEnterKeyBehaviors() {
-  let promise = promisePanelShown(window);
-  PanelUI.show();
-  await promise;
+  await gCUITestUtils.openMainMenu();
 
   let buttons = PanelView.forNode(PanelUI.mainView).getNavigableElements();
 
   // Navigate to the 'Help' button, which points to a subview.
   EventUtils.synthesizeKey("KEY_ArrowUp");
   let focusedElement = document.commandDispatcher.focusedElement;
   Assert.equal(focusedElement, buttons[buttons.length - 1],
     "The last button should be focused after navigating upward");
 
-  promise = BrowserTestUtils.waitForEvent(PanelUI.helpView, "ViewShown");
+  let promise = BrowserTestUtils.waitForEvent(PanelUI.helpView, "ViewShown");
   // Make sure the Help button is in focus.
   while (!focusedElement || !focusedElement.id || focusedElement.id != kHelpButtonId) {
     EventUtils.synthesizeKey("KEY_ArrowUp");
     focusedElement = document.commandDispatcher.focusedElement;
   }
   EventUtils.synthesizeKey("KEY_Enter");
   await promise;
 
@@ -93,62 +87,55 @@ add_task(async function testEnterKeyBeha
   while (!focusedElement || !focusedElement.id || focusedElement.id != kFindButtonId) {
     EventUtils.synthesizeKey("KEY_ArrowUp");
     focusedElement = document.commandDispatcher.focusedElement;
   }
   let findBarPromise = gBrowser.isFindBarInitialized() ?
     null : BrowserTestUtils.waitForEvent(gBrowser.selectedTab, "TabFindInitialized");
   Assert.equal(focusedElement.id, kFindButtonId, "Find button should be selected");
 
-  promise = promisePanelHidden(window);
-  EventUtils.synthesizeKey("KEY_Enter");
-  await promise;
+  await gCUITestUtils.hidePanelMultiView(PanelUI.panel,
+    () => EventUtils.synthesizeKey("KEY_Enter"));
 
   await findBarPromise;
   Assert.ok(!gFindBar.hidden, "Findbar should have opened");
   gFindBar.close();
 });
 
 add_task(async function testLeftRightKeys() {
-  let promise = promisePanelShown(window);
-  PanelUI.show();
-  await promise;
+  await gCUITestUtils.openMainMenu();
 
   // Navigate to the 'Help' button, which points to a subview.
   let focusedElement = document.commandDispatcher.focusedElement;
   while (!focusedElement || !focusedElement.id || focusedElement.id != kHelpButtonId) {
     EventUtils.synthesizeKey("KEY_ArrowUp");
     focusedElement = document.commandDispatcher.focusedElement;
   }
   Assert.equal(focusedElement.id, kHelpButtonId, "The last button should be focused after navigating upward");
 
   // Hitting ArrowRight on a button that points to a subview should navigate us
   // there.
-  promise = BrowserTestUtils.waitForEvent(PanelUI.helpView, "ViewShown");
+  let promise = BrowserTestUtils.waitForEvent(PanelUI.helpView, "ViewShown");
   EventUtils.synthesizeKey("KEY_ArrowRight");
   await promise;
 
   // Hitting ArrowLeft should navigate us back.
   promise = BrowserTestUtils.waitForEvent(PanelUI.mainView, "ViewShown");
   EventUtils.synthesizeKey("KEY_ArrowLeft");
   await promise;
 
   focusedElement = document.commandDispatcher.focusedElement;
   Assert.equal(focusedElement.id, kHelpButtonId,
                "Help button should be focused again now that we're back in the main view");
 
-  promise = promisePanelHidden(window);
-  PanelUI.hide();
-  await promise;
+  await gCUITestUtils.hideMainMenu();
 });
 
 add_task(async function testTabKey() {
-  let promise = promisePanelShown(window);
-  PanelUI.show();
-  await promise;
+  await gCUITestUtils.openMainMenu();
 
   let buttons = PanelView.forNode(PanelUI.mainView).getNavigableElements();
 
   for (let button of buttons) {
     if (button.disabled)
       continue;
     EventUtils.synthesizeKey("KEY_Tab");
     Assert.equal(document.commandDispatcher.focusedElement, button,
@@ -167,25 +154,21 @@ add_task(async function testTabKey() {
     Assert.equal(document.commandDispatcher.focusedElement, button,
       "The correct button should be focused after shift + tabbing");
   }
 
   EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
   Assert.equal(document.commandDispatcher.focusedElement, buttons[buttons.length - 1],
     "Pressing shift + tab should cycle around and select the last button again");
 
-  promise = promisePanelHidden(window);
-  PanelUI.hide();
-  await promise;
+  await gCUITestUtils.hideMainMenu();
 });
 
 add_task(async function testInterleavedTabAndArrowKeys() {
-  let promise = promisePanelShown(window);
-  PanelUI.show();
-  await promise;
+  await gCUITestUtils.openMainMenu();
 
   let buttons = PanelView.forNode(PanelUI.mainView).getNavigableElements();
   let tab = false;
 
   for (let button of buttons) {
     if (button.disabled)
       continue;
     if (tab) {
@@ -194,25 +177,21 @@ add_task(async function testInterleavedT
       EventUtils.synthesizeKey("KEY_ArrowDown");
     }
     tab = !tab;
   }
 
   Assert.equal(document.commandDispatcher.focusedElement, buttons[buttons.length - 1],
     "The last button should be focused after a mix of Tab and ArrowDown");
 
-  promise = promisePanelHidden(window);
-  PanelUI.hide();
-  await promise;
+  await gCUITestUtils.hideMainMenu();
 });
 
 add_task(async function testSpaceDownAfterTabNavigation() {
-  let promise = promisePanelShown(window);
-  PanelUI.show();
-  await promise;
+  await gCUITestUtils.openMainMenu();
 
   let buttons = PanelView.forNode(PanelUI.mainView).getNavigableElements();
   let button;
 
   for (button of buttons) {
     if (button.disabled)
       continue;
     EventUtils.synthesizeKey("KEY_Tab");
@@ -221,16 +200,14 @@ add_task(async function testSpaceDownAft
     }
   }
 
   Assert.equal(document.commandDispatcher.focusedElement, button,
                "Help button should be focused after tabbing to it.");
 
   // Pressing down space on a button that points to a subview should navigate us
   // there, before keyup.
-  promise = BrowserTestUtils.waitForEvent(PanelUI.helpView, "ViewShown");
+  let promise = BrowserTestUtils.waitForEvent(PanelUI.helpView, "ViewShown");
   EventUtils.synthesizeKey(" ", {type: "keydown"});
   await promise;
 
-  promise = promisePanelHidden(window);
-  PanelUI.hide();
-  await promise;
+  await gCUITestUtils.hideMainMenu();
 });
--- a/browser/components/customizableui/test/browser_panel_toggle.js
+++ b/browser/components/customizableui/test/browser_panel_toggle.js
@@ -5,39 +5,33 @@
 "use strict";
 
 /**
  * Test opening and closing the menu panel UI.
  */
 
 // Show and hide the menu panel programmatically without an event (like UITour.jsm would)
 add_task(async function() {
-  let shownPromise = promisePanelShown(window);
-  PanelUI.show();
-  await shownPromise;
+  await gCUITestUtils.openMainMenu();
 
   is(PanelUI.panel.getAttribute("panelopen"), "true", "Check that panel has panelopen attribute");
   is(PanelUI.panel.state, "open", "Check that panel state is 'open'");
 
-  let hiddenPromise = promisePanelHidden(window);
-  PanelUI.hide();
-  await hiddenPromise;
+  await gCUITestUtils.hideMainMenu();
 
   ok(!PanelUI.panel.hasAttribute("panelopen"), "Check that panel doesn't have the panelopen attribute");
   is(PanelUI.panel.state, "closed", "Check that panel state is 'closed'");
 });
 
 // Toggle the menu panel open and closed
 add_task(async function() {
-  let shownPromise = promisePanelShown(window);
-  PanelUI.toggle({type: "command"});
-  await shownPromise;
+  await gCUITestUtils.openPanelMultiView(PanelUI.panel, PanelUI.mainView,
+    () => PanelUI.toggle({type: "command"}));
 
   is(PanelUI.panel.getAttribute("panelopen"), "true", "Check that panel has panelopen attribute");
   is(PanelUI.panel.state, "open", "Check that panel state is 'open'");
 
-  let hiddenPromise = promisePanelHidden(window);
-  PanelUI.toggle({type: "command"});
-  await hiddenPromise;
+  await gCUITestUtils.hidePanelMultiView(PanelUI.panel,
+    () => PanelUI.toggle({type: "command"}));
 
   ok(!PanelUI.panel.hasAttribute("panelopen"), "Check that panel doesn't have the panelopen attribute");
   is(PanelUI.panel.state, "closed", "Check that panel state is 'closed'");
 });
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -4,21 +4,27 @@
 
 "use strict";
 
 // Avoid leaks by using tmp for imports...
 var tmp = {};
 ChromeUtils.import("resource://gre/modules/Promise.jsm", tmp);
 ChromeUtils.import("resource:///modules/CustomizableUI.jsm", tmp);
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm", tmp);
-var {Promise, CustomizableUI, AppConstants} = tmp;
+ChromeUtils.import("resource://testing-common/CustomizableUITestUtils.jsm", tmp);
+var {Promise, CustomizableUI, AppConstants, CustomizableUITestUtils} = tmp;
 
 var EventUtils = {};
 Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
+/**
+ * Instance of CustomizableUITestUtils for the current browser window.
+ */
+var gCUITestUtils = new CustomizableUITestUtils(window);
+
 Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
 registerCleanupFunction(() => Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck"));
 
 var {synthesizeDragStart, synthesizeDrop} = EventUtils;
 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const kTabEventFailureTimeoutInMs = 20000;
 
@@ -249,21 +255,16 @@ function promiseWindowClosed(win) {
   return new Promise(resolve => {
     win.addEventListener("unload", function() {
       resolve();
     }, {once: true});
     win.close();
   });
 }
 
-function promisePanelShown(win) {
-  let panelEl = win.PanelUI.panel;
-  return promisePanelElementShown(win, panelEl);
-}
-
 function promiseOverflowShown(win) {
   let panelEl = win.document.getElementById("widget-overflow");
   return promisePanelElementShown(win, panelEl);
 }
 
 function promisePanelElementShown(win, aPanel) {
   return new Promise((resolve, reject) => {
     let timeoutId = win.setTimeout(() => {
@@ -273,21 +274,16 @@ function promisePanelElementShown(win, a
       aPanel.removeEventListener("popupshown", onPanelOpen);
       win.clearTimeout(timeoutId);
       resolve();
     }
     aPanel.addEventListener("popupshown", onPanelOpen);
   });
 }
 
-function promisePanelHidden(win) {
-  let panelEl = win.PanelUI.panel;
-  return promisePanelElementHidden(win, panelEl);
-}
-
 function promiseOverflowHidden(win) {
   let panelEl = win.PanelUI.overflowPanel;
   return promisePanelElementHidden(win, panelEl);
 }
 
 function promisePanelElementHidden(win, aPanel) {
   return new Promise((resolve, reject) => {
     let timeoutId = win.setTimeout(() => {