Bug 1310805 - do not force-open root popup without clicking it in click_menus_in_sequence(). Click the proper widgets to open the popup. r=mkmelin
authoraceman <acelists@atlas.sk>
Wed, 21 Dec 2016 22:00:01 +0100
changeset 20891 31a0301ffb6abc35496db987a1b0b0a66a28e805
parent 20890 a844bb9580da7b60946d7592d772c7ef5b79da71
child 20892 e7ac7f2fd2ce84448a342244536069479181004f
push id12665
push useracelists@atlas.sk
push dateWed, 21 Dec 2016 21:01:22 +0000
treeherdercomm-central@31a0301ffb6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin
bugs1310805
Bug 1310805 - do not force-open root popup without clicking it in click_menus_in_sequence(). Click the proper widgets to open the popup. r=mkmelin
mail/test/mozmill/composition/test-address-widgets.js
mail/test/mozmill/composition/test-attachment-reminder.js
mail/test/mozmill/composition/test-drafts.js
mail/test/mozmill/composition/test-newmsg-compose-identity.js
mail/test/mozmill/composition/test-send-button.js
mail/test/mozmill/composition/test-signature-updating.js
mail/test/mozmill/content-policy/test-general-content-policy.js
mail/test/mozmill/folder-display/test-columns.js
mail/test/mozmill/folder-display/test-watch-ignore-thread.js
mail/test/mozmill/folder-tree-modes/test-mode-switching.js
mail/test/mozmill/message-header/test-phishing-bar.js
mail/test/mozmill/shared-modules/test-account-manager-helpers.js
mail/test/mozmill/shared-modules/test-compose-helpers.js
mail/test/mozmill/shared-modules/test-notificationbox-helpers.js
mail/test/mozmill/shared-modules/test-window-helpers.js
--- a/mail/test/mozmill/composition/test-address-widgets.js
+++ b/mail/test/mozmill/composition/test-address-widgets.js
@@ -120,26 +120,29 @@ function test_address_types() {
 
   // Now try the same accounts but choosing them in the From dropdown
   // inside compose window.
   be_in_folder(accountPOP3.incomingServer.rootFolder);
   cwc = open_compose_new_mail();
   check_nntp_address_types();
 
   let NNTPidentity = accountNNTP.defaultIdentity.key;
+  cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [ { identitykey: NNTPidentity } ]);
   check_nntp_address_types();
 
   // In a News account, choose "Newsgroup:" as the address type.
+  cwc.click(cwc.eid("addressCol1#1"));
   cwc.click_menus_in_sequence(cwc.e("addressCol1#1").menupopup,
                               [ { value: "addr_newsgroups" } ]);
   check_nntp_address_types();
 
   // And switch back to the POP3 account.
   let POP3identity = accountPOP3.defaultIdentity.key;
+  cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [ { identitykey: POP3identity } ]);
   check_nntp_address_types();
 
   close_compose_window(cwc);
 
   remove_NNTP_account();
 
   // Now the NNTP account is lost, so we should be back to mail only addressees.
--- a/mail/test/mozmill/composition/test-attachment-reminder.js
+++ b/mail/test/mozmill/composition/test-attachment-reminder.js
@@ -248,17 +248,17 @@ function test_attachment_reminder_aggres
  * Test that clicking "No, Send Now" in the attachment reminder alert
  * works.
  */
 function test_no_send_now_sends() {
   let cwc = open_compose_new_mail();
 
   setup_msg_contents(cwc, "test@example.org",
                      "will the 'No, Send Now' button work?",
-                     "Hello, i got your attachment!");
+                     "Hello, I got your attachment!");
 
   wait_for_reminder_state(cwc, true);
 
   // Click the send button again, this time choose "No, Send Now".
   plan_for_modal_dialog("commonDialog", click_no_send_now);
   cwc.click(cwc.eid("button-send"));
   wait_for_modal_dialog("commonDialog");
 
@@ -273,16 +273,17 @@ function test_no_send_now_sends() {
  * Click the manual reminder in the menu.
  *
  * @param aCwc            A compose window controller.
  * @param aExpectedState  A boolean specifying what is the expected state
  *                        of the reminder menuitem after the click.
  */
 function click_manual_reminder(aCwc, aExpectedState) {
   wait_for_window_focused(aCwc.window);
+  aCwc.click(new elementslib.Elem(aCwc.get_menu_dropmarker(aCwc.e("button-attach"))));
   aCwc.click_menus_in_sequence(aCwc.e("button-attachPopup"),
                                [ {id: "button-attachPopup_remindLaterItem"} ]);
   wait_for_window_focused(aCwc.window);
   assert_manual_reminder_state(aCwc, aExpectedState);
 }
 
 /**
  * Bug 521128
@@ -606,17 +607,21 @@ function test_disabling_attachment_remin
   setup_msg_contents(cwc, "test@example.invalid", "Testing turning off the reminder",
                      "Some attachment keywords here...");
 
   // This one should have the manual reminder disabled.
   assert_manual_reminder_state(cwc, false);
   // There should be an attachment reminder.
   wait_for_reminder_state(cwc, true);
 
-  // Disable the reminder (not just dismiss).
+  // Disable the reminder (not just dismiss) using the menuitem
+  // in the notification bar menu-button.
+  let disableButton = get_notification_button(cwc, kBoxId, kNotificationId,
+                                              { popup: "reminderBarPopup" });
+  cwc.click(new elementslib.Elem(cwc.get_menu_dropmarker(disableButton)));
   cwc.click_menus_in_sequence(cwc.e("reminderBarPopup"),
                               [ {id: "disableReminder"} ]);
 
   wait_for_reminder_state(cwc, false);
 
   // Add more keywords.
   setup_msg_contents(cwc, "", "", "... and another file attached.");
   // Give the notification time to appear. It shouldn't.
@@ -633,16 +638,19 @@ function test_disabling_attachment_remin
   assert_automatic_reminder_state(cwc, false);
 
   // Add more keywords to trigger automatic reminder.
   setup_msg_contents(cwc, "", "", "I enclosed another file.");
   // Give the notification time to appear. It should now.
   wait_for_reminder_state(cwc, true);
 
   // Disable the reminder again.
+  disableButton = get_notification_button(cwc, kBoxId, kNotificationId,
+                                          { popup: "reminderBarPopup" });
+  cwc.click(new elementslib.Elem(cwc.get_menu_dropmarker(disableButton)));
   cwc.click_menus_in_sequence(cwc.e("reminderBarPopup"),
                               [ {id: "disableReminder"} ]);
   wait_for_reminder_state(cwc, false);
 
   // Now send the message.
   plan_for_window_close(cwc);
   cwc.window.goDoCommand("cmd_sendLater");
   wait_for_window_close();
--- a/mail/test/mozmill/composition/test-drafts.js
+++ b/mail/test/mozmill/composition/test-drafts.js
@@ -87,16 +87,17 @@ function internal_check_delivery_format(
   let cwc = open_compose_new_mail();
 
   setup_msg_contents(cwc, "test@example.invalid",
                      "Testing storing of the composition properties in the draft!",
                      "Hello!");
 
   // Select our wanted format.
   if (!mc.mozmillModule.isMac) {
+    cwc.click(cwc.eid("optionsMenu"));
     formatMenu = cwc.click_menus_in_sequence(cwc.e("optionsMenuPopup"),
                                              [ { id: "outputFormatMenu" },
                                                { id: "format_both" } ]);
   } else {
     // On OS X the main menu seems not accessible for clicking from mozmill.
     assert_true(cwc.e("outputFormatMenu").getAttribute("oncommand").startsWith("OutputFormatMenuSelect("));
     cwc.window.OutputFormatMenuSelect(cwc.e("format_both"));
   }
@@ -104,16 +105,17 @@ function internal_check_delivery_format(
   /**
    * Check if the right format is selected in the menu.
    *
    * @param aMenuItemId  The id of the menuitem expected to be selected.
    * @param aValue       A value of nsIMsgCompSendFormat contants of the expected selected format.
    */
   function assert_format_value(aMenuItemId, aValue) {
     if (!mc.mozmillModule.isMac) {
+      cwc.click(cwc.eid("optionsMenu"));
       let formatMenu = cwc.click_menus_in_sequence(cwc.e("optionsMenuPopup"),
                                                    [ { id: "outputFormatMenu" } ], true);
       let formatItem = cwc.e("outputFormatMenuPopup")
                           .querySelector("[name=output_format][checked=true]");
       assert_equals(formatItem.id, aMenuItemId);
       cwc.close_popup_sequence(formatMenu);
     } else {
       assert_equals(cwc.window.gSendFormat, aValue);
--- a/mail/test/mozmill/composition/test-newmsg-compose-identity.js
+++ b/mail/test/mozmill/composition/test-newmsg-compose-identity.js
@@ -111,18 +111,17 @@ function test_compose_from_composer() {
   plan_for_new_window("msgcompose");
   mainCompWin.keypress(null, "n", {shiftKey: false, accelKey: true});
   let newCompWin = wait_for_compose_window();
   checkCompIdentity(newCompWin, account.defaultIdentity.key);
   close_compose_window(newCompWin);
 
   // Switch to identity2 in the main compose window, new compose windows
   // starting from here should use the same identity as its "parent".
-  let identityList = mainCompWin.e("msgIdentity");
-  identityList.selectedIndex++;
+  mainCompWin.click(mainCompWin.eid("msgIdentity"));
   mainCompWin.click_menus_in_sequence(mainCompWin.e("msgIdentityPopup"),
                                       [ { identitykey: identityKey2 } ]);
   checkCompIdentity(mainCompWin, identityKey2);
 
   // Compose a second new message from the compose window.
   plan_for_new_window("msgcompose");
   mainCompWin.keypress(null, "n", {shiftKey: false, accelKey: true});
   let newCompWin2 = wait_for_compose_window();
@@ -144,16 +143,17 @@ function test_editing_identity() {
   let compWin = open_compose_new_mail();
   checkCompIdentity(compWin, account.defaultIdentity.key, identity1Email);
 
   // Input custom identity data into the From field.
   let customName = "custom";
   let customEmail = "custom@edited.invalid";
   let identityCustom = customName + " <" + customEmail + ">";
 
+  compWin.click(compWin.eid("msgIdentity"));
   compWin.click_menus_in_sequence(compWin.e("msgIdentityPopup"),
                                   [ { command: "cmd_customizeFromAddress" } ]);
 
   compWin.type(compWin.e("msgIdentityPopup").value, identityCustom);
 
   checkCompIdentity(compWin, account.defaultIdentity.key, identityCustom, identityCustom);
   close_compose_window(compWin);
 
--- a/mail/test/mozmill/composition/test-send-button.js
+++ b/mail/test/mozmill/composition/test-send-button.js
@@ -157,21 +157,23 @@ function test_send_enabled_prefilled_add
 
   let identityPicker = cwc.e("msgIdentity");
   assert_equals(identityPicker.selectedIndex, 0);
 
   // Switch to the second identity that has no CC. Send should be disabled.
   assert_true(account.identities.length >= 2);
   let identityWithoutCC = account.identities.queryElementAt(1, Ci.nsIMsgIdentity);
   assert_false(identityWithoutCC.doCc);
+  cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"),
                               [ { identitykey: identityWithoutCC.key } ]);
   check_send_commands_state(cwc, false);
 
   // Check the first identity again.
+  cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"),
                               [ { identitykey: identityWithCC.key } ]);
   check_send_commands_state(cwc, true);
 
   close_compose_window(cwc);
   identityWithCC.doCcList = "";
   identityWithCC.doCc = false;
 }
--- a/mail/test/mozmill/composition/test-signature-updating.js
+++ b/mail/test/mozmill/composition/test-signature-updating.js
@@ -87,16 +87,17 @@ function plaintextComposeWindowSwitchSig
     assert_equals(brNode.localName, "br");
     sigNode = brNode.nextSibling;
   }
 
   let expectedText = "Tinderbox is soo 90ies";
   assert_equals(sigNode.textContent, expectedText);
 
   // Now switch identities!
+  cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [ { identitykey: "id2" } ]);
 
   node = contentFrame.contentDocument.body.lastChild;
 
   // The last node is a BR - this allows users to put text after the
   // signature without it being styled like the signature.
   assert_equals(node.localName, "br");
   node = node.previousSibling;
@@ -164,16 +165,17 @@ function HTMLComposeWindowSwitchSignatur
   assert_equals(node.className, "moz-signature");
   node = node.firstChild; // text node containing the signature divider
   if (suppressSigSep)
     assert_equals(node.nodeValue, "Tinderbox is soo 90ies");
   else
     assert_equals(node.nodeValue, "-- \nTinderbox is soo 90ies");
 
   // Now switch identities!
+  cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [ { identitykey: "id2" } ]);
 
   node = contentFrame.contentDocument.body.lastChild;
 
   // In html compose, the signature is inside the last node
   // with class="moz-signature".
   assert_equals(node.className, "moz-signature");
   node = node.firstChild; // text node containing the signature divider
--- a/mail/test/mozmill/content-policy/test-general-content-policy.js
+++ b/mail/test/mozmill/content-policy/test-general-content-policy.js
@@ -239,16 +239,19 @@ function allowRemoteContentAndCheck(test
   addMsgToFolderAndCheckContent(folder, test);
 
   plan_for_message_display(mc);
 
   // Click on the allow remote content button
   const kBoxId = "msgNotificationBar";
   const kNotificationValue = "remoteContent";
   wait_for_notification_to_show(mc, kBoxId, kNotificationValue);
+  let prefButton = get_notification_button(mc, kBoxId, kNotificationValue,
+                                           { popup: "remoteContentOptions" });
+  mc.click(new elib.Elem(prefButton));
   mc.click_menus_in_sequence(mc.e("remoteContentOptions"),
                              [{id: "remoteContentOptionAllowForMsg"}]);
   wait_for_notification_to_stop(mc, kBoxId, kNotificationValue);
 
   wait_for_message_display_completion(mc, true);
 
   if (!test.checkForAllowed(mc.window.content.document
                               .getElementById("testelement")))
--- a/mail/test/mozmill/folder-display/test-columns.js
+++ b/mail/test/mozmill/folder-display/test-columns.js
@@ -351,19 +351,17 @@ function invoke_column_picker_option(aAc
   //   |- hbox                item 0
   //   |- treecolpicker   <-- item 1 this is the one we want
   let threadCols = mc.window.document.getElementById("threadCols");
   let colPicker = mc.window.document.getAnonymousNodes(threadCols).item(1);
   let colPickerPopup = mc.window.document.getAnonymousElementByAttribute(
                          colPicker, "anonid", "popup");
 
   mc.click(new elib.Elem(colPicker));
-  wait_for_popup_to_open(colPickerPopup);
   mc.click_menus_in_sequence(colPickerPopup, aActions);
-  close_popup(mc, new elib.Elem(colPickerPopup));
 }
 
 
 /**
  * The column picker's "reset columns to default" option should set our state
  *  back to inbox state.
  */
 function test_reset_to_inbox() {
--- a/mail/test/mozmill/folder-display/test-watch-ignore-thread.js
+++ b/mail/test/mozmill/folder-display/test-watch-ignore-thread.js
@@ -30,16 +30,17 @@ function setupModule(module) {
   expand_all_threads();
 }
 
 /**
  * Click one of the menu items in the appmenu View | Messages menu.
  * @param aMenuId the id of the menu item to click.
  */
 function clickViewMessagesItem(aMenuId) {
+  mc.click(mc.eid("button-appmenu"));
   mc.click_menus_in_sequence(mc.e("appmenu-popup"),
     [
       {id: "appmenu_View"},
       {id: "appmenu_viewMessagesMenu"},
       {id: aMenuId}
     ]
   );
 }
--- a/mail/test/mozmill/folder-tree-modes/test-mode-switching.js
+++ b/mail/test/mozmill/folder-tree-modes/test-mode-switching.js
@@ -43,18 +43,20 @@ function setupModule(module) {
   favoriteFolder.flags |= Ci.nsMsgFolderFlags.Favorite;
 
   toggle_menu = mc.e("menu_compactFolderView");
   toggle_appmenu = mc.e("appmenu_compactFolderView");
 
   modeList_menu = mc.e("menu_FolderViewsPopup");
   modeList_appmenu = mc.e("appmenu_FolderViewsPopup");
 
-  view_menu = mc.e("menu_View_Popup");
-  view_appmenu = mc.e("appmenu-popup");
+  view_menu = mc.eid("menu_View");
+  view_menupopup = mc.e("menu_View_Popup");
+  appmenu = mc.eid("button-appmenu");
+  appmenupopup = mc.e("appmenu-popup");
 
   tree = mc.folderTreeView;
 
   select_no_folders();
 }
 
 /**
  * Check if both "Compact view" checkboxes in menu are of the expected state.
@@ -78,32 +80,35 @@ function assert_compact_state(aChecked, 
  */
 function assert_mode_selected(aMode) {
   assert_equals(tree.mode, aMode);
   let baseMode = tree.baseMode();
   assert_compact_state(baseMode != tree.mode);
   // We need to open the menu because only then the right mode is set in them.
   if (!mc.mozmillModule.isMac) {
     // On OS X the main menu seems not accessible for clicking from mozmill.
-    popuplist = mc.click_menus_in_sequence(view_menu, [ { id: modeList_menu.parentNode.id } ], true);
+    mc.click(view_menu);
+    popuplist = mc.click_menus_in_sequence(view_menupopup, [ { id: modeList_menu.parentNode.id } ], true);
     assert_true(modeList_menu.querySelector('[value="' + baseMode + '"]').hasAttribute("checked"));
     mc.close_popup_sequence(popuplist);
   }
-  popuplist = mc.click_menus_in_sequence(view_appmenu, [ { id: modeList_appmenu.parentNode.id } ], true);
+  mc.click(appmenu);
+  popuplist = mc.click_menus_in_sequence(appmenupopup, [ { id: modeList_appmenu.parentNode.id } ], true);
   assert_true(modeList_menu.querySelector('[value="' + baseMode + '"]').hasAttribute("checked"));
   mc.close_popup_sequence(popuplist);
 }
 
 /**
  * Toggle the folder mode by clicking in the menu.
  *
  * @param aMode  The base name of the mode to select.
  */
 function select_mode_in_menu(aMode) {
-  mc.click_menus_in_sequence(view_appmenu, [ { id: modeList_appmenu.parentNode.id },
+  mc.click(appmenu);
+  mc.click_menus_in_sequence(appmenupopup, [ { id: modeList_appmenu.parentNode.id },
                                              { value: aMode } ]);
 }
 
 /**
  * Toggle the Compact view option by clicking in the menu.
  */
 function toggle_compact_in_menu() {
   // For some reason, clicking the menuitem does not work by any means,
--- a/mail/test/mozmill/message-header/test-phishing-bar.js
+++ b/mail/test/mozmill/message-header/test-phishing-bar.js
@@ -52,16 +52,19 @@ function setupModule(module) {
 }
 
 /**
  * Make sure the notification shows, and goes away once the Ignore menuitem
  * is clicked.
  */
 function assert_ignore_works(aController) {
   wait_for_notification_to_show(aController, kBoxId, kNotificationValue);
+  let prefButton = get_notification_button(aController, kBoxId, kNotificationValue,
+                                           { popup: "phishingOptions" });
+  aController.click(new elementslib.Elem(prefButton));
   aController.click_menus_in_sequence(aController.e("phishingOptions"),
                                       [{id: "phishingOptionIgnore"}]);
   wait_for_notification_to_stop(aController, kBoxId, kNotificationValue);
 }
 
 /**
  * Test that when viewing a message, choosing ignore hides the the phishing
  * notification.
--- a/mail/test/mozmill/shared-modules/test-account-manager-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-account-manager-helpers.js
@@ -156,13 +156,14 @@ function remove_account(aAccount, aContr
     cdc.window.document.documentElement.acceptDialog();
     cdc.waitFor(() => !cdc.window.document.documentElement.getButton("accept").disabled,
                 "Timeout waiting for finish of account removal",
                 5000, 100);
     cdc.window.document.documentElement.acceptDialog();
   });
 
   // Use the Remove item in the Account actions menu.
+  aController.click(aController.eid("accountActionsButton"));
   aController.click_menus_in_sequence(aController.e("accountActionsDropdown"),
                                       [ {id: "accountActionsDropdownRemove"} ]);
   wh.wait_for_modal_dialog("removeAccountDialog");
   // TODO: For unknown reason this also closes the whole account manager.
 }
--- a/mail/test/mozmill/shared-modules/test-compose-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-compose-helpers.js
@@ -307,16 +307,17 @@ function clear_recipient(aController, aR
  * Change recipient type in compose widget.
  *
  * @param aController    Compose window controller.
  * @param aType          The recipient type, e.g. "addr_to".
  * @param aRecipientRow  The compose widget row containing recipient to remove.
  */
 function toggle_recipient_type(aController, aType, aRecipientRow = 1) {
   let addrType = aController.window.awGetPopupElement(aRecipientRow);
+  aController.click(new elib.Elem(addrType));
   aController.click_menus_in_sequence(addrType.menupopup, [ { value: aType } ]);
 }
 
 /**
  * Create and return an nsIMsgAttachment for the passed URL.
  * @param aUrl the URL for this attachment (either a file URL or a web URL)
  * @param aSize (optional) the file size of this attachment, in bytes
  */
--- a/mail/test/mozmill/shared-modules/test-notificationbox-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-notificationbox-helpers.js
@@ -8,16 +8,17 @@ var RELATIVE_ROOT = "../shared-modules";
 var MODULE_REQUIRES = [];
 
 function installInto(module) {
   module.check_notification_displayed = check_notification_displayed;
   module.assert_notification_displayed = assert_notification_displayed;
   module.close_notification = close_notification;
   module.wait_for_notification_to_stop = wait_for_notification_to_stop;
   module.wait_for_notification_to_show = wait_for_notification_to_show;
+  module.get_notification_button = get_notification_button;
 }
 
 /**
  * A helper function for determining whether or not a notification with
  * a particular value is being displayed.
  *
  * @param aController    the controller of the window to check
  * @param aBoxId         the id of the notification box
@@ -96,25 +97,76 @@ function wait_for_notification_to_stop(a
                       "Timed out waiting for notification with value " +
                       aValue + " to stop.");
 }
 
 /**
  * A helper function that waits for a notification with value aValue
  * to show in the window.
  *
- * @param aController the controller for the compose window that we want
+ * @param aController the controller for the window that we want
  *                    the notification to appear in
  * @param aBoxId the id of the notification box
  * @param aValue the value of the notification to wait for
  */
 function wait_for_notification_to_show(aController, aBoxId, aValue) {
   let nb = aController.window.document.getElementById(aBoxId);
   if (!nb)
     throw new Error("Couldn't find a notification box for id=" + aBoxId);
 
   function nbReady() {
     return (nb.getNotificationWithValue(aValue) != null) && !nb._animating;
   }
   aController.waitFor(nbReady,
                       "Timed out waiting for notification with value " +
                       aValue + " to show.");
 }
+
+
+/**
+ * Gets a button in a notification, as those do not have IDs.
+ *
+ * @param aController The controller for the window
+ *                    that has the notification.
+ * @param aBoxId      The id of the notification box.
+ * @param aValue      The value of the notification to find.
+ * @param aMatch      Attributes of the button to find.
+ *                    An object with key:value pairs,
+ *                    similar to click_menus_in_sequence().
+ */
+function get_notification_button(aController, aBoxId, aValue, aMatch) {
+  let nb = aController.window.document.getElementById(aBoxId);
+  if (!nb)
+    throw new Error("Couldn't find a notification box for id=" + aBoxId);
+
+  let notification = nb.getNotificationWithValue(aValue);
+  let buttons = notification.querySelectorAll("button");
+  for (let button of buttons) {
+    let matchedAll = true;
+    for (let name in aMatch) {
+      let value = aMatch[name];
+      let matched = false;
+      if (name == "popup") {
+        if (button.getAttribute("type") == "menu-button" ||
+            button.getAttribute("type") == "menu") {
+          // The button contains a menupopup as the first child.
+          matched = (button.firstChild &&
+                     (button.firstChild.tagName == "menupopup") &&
+                     (button.firstChild.id == value));
+        } else {
+          // The "popup" attribute is not on the button itself but in its
+          // buttonInfo member.
+          matched = (("buttonInfo" in button) && (button.buttonInfo.popup == value));
+        }
+      } else if (button.hasAttribute(name) && button.getAttribute(name) == value) {
+        matched = true;
+      }
+      if (!matched) {
+        matchedAll = false;
+        break;
+      }
+    }
+    if (matchedAll)
+      return button;
+  }
+
+  throw new Error("Couldn't find the requested button on a notification");
+}
--- a/mail/test/mozmill/shared-modules/test-window-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-window-helpers.js
@@ -937,30 +937,29 @@ var AugmentEverybodyWith = {
                               dis.click(aWhatToClick);
                             }, 1000);
     },
 
     /**
      * Dynamically-built/XBL-defined menus can be hard to work with, this makes it
      *  easier.
      *
-     * @param aRootPopup The base popup.  We will open it if it is not open or
-     *     wait for it to open if it is in the process.
-     * @param aActions A list of objects where each object has a single
+     * @param aRootPopup  The base popup. The caller is expected to activate it
+     *     (by clicking/rightclicking the right widget). We will only wait for it
+     *     to open if it is in the process.
+     * @param aActions  A list of objects where each object has a single
      *     attribute with a single value.  We pick the menu option whose DOM
      *     node has an attribute with that name and value.  We click whatever we
      *     find.  We throw if we don't find what you were asking for.
      * @param aKeepOpen  If set to true the popups are not closed after last click.
      *
      * @return  An array of popup elements that were left open. It will be
      *          an empty array if aKeepOpen was set to false.
      */
     click_menus_in_sequence: function _click_menus(aRootPopup, aActions, aKeepOpen) {
-      if (aRootPopup.state == "closed")
-        aRootPopup.openPopup(null, "", 0, 0, true, true);
       if (aRootPopup.state != "open") { // handle "showing"
         utils.waitFor(() => aRootPopup.state == "open",
                       "Popup never opened! id=" + aRootPopup.id +
                       ", state=" + aRootPopup.state);
       }
       // These popups sadly do not close themselves, so we need to keep track
       // of them so we can make sure they end up closed.
       let closeStack = [aRootPopup];
@@ -1033,24 +1032,38 @@ var AugmentEverybodyWith = {
      *
      * @param aCloseStack  An array of menupopup elements that are to be closed.
      *                     The elements are processed from the end of the array
      *                     to the front (a stack).
      */
     close_popup_sequence: function _close_popup_sequence(aCloseStack) {
       while (aCloseStack.length) {
         let curPopup = aCloseStack.pop();
-        this.keypress(new elib.Elem(curPopup), "VK_ESCAPE", {});
+        if (curPopup.state == "open")
+          this.keypress(new elib.Elem(curPopup), "VK_ESCAPE", {});
         utils.waitFor(function() { return curPopup.state == "closed"; },
                       "Popup did not close! id=" + curPopup.id +
                       ", state=" +  curPopup.state, 5000, 50);
       }
     },
 
     /**
+     * Get dropmarker arrow element from 
+     *
+     * @param aNode  An element containing a dropmarker, e.g. menulist or menu-button
+     */
+    get_menu_dropmarker: function(aNode) {
+      let children = aNode.ownerDocument.getAnonymousNodes(aNode);
+      for (let node of children) {
+        if (node.tagName == "xul:dropmarker")
+          return node;
+      }
+    },
+
+    /**
      * mark_action helper method that produces something that can be concat()ed
      *  onto a list being passed to mark_action in order to describe the focus
      *  state of the window.  For now this will be a variable-length list but
      *  could be changed to a single object in the future.
      */
     describeFocus: function() {
       let arr = [
         "in window:",
@@ -1091,17 +1104,17 @@ var AugmentEverybodyWith = {
  * border but are no longer so (bug 595652), so we need these wrappers to
  * perform the operations at the center when aLeft or aTop aren't passed in.
  */
 var MOUSE_OPS_TO_WRAP = [
   "click", "doubleClick", "mouseDown", "mouseOut", "mouseOver", "mouseUp",
   "middleClick", "rightClick",
 ];
 
-for (let [, mouseOp] in Iterator(MOUSE_OPS_TO_WRAP)) {
+for (let mouseOp of MOUSE_OPS_TO_WRAP) {
   let thisMouseOp = mouseOp;
   let wrapperFunc = function (aElem, aLeft, aTop) {
     let el = aElem.getNode();
     let rect = el.getBoundingClientRect();
     if (aLeft === undefined)
       aLeft = rect.width / 2;
     if (aTop === undefined)
       aTop = rect.height / 2;