Bug 1243729 - (m-b) Part II, Test on username selection dialog, r=MattN, a=lizzard
authorTimothy Guan-tin Chien <timdream@gmail.com>
Wed, 20 Apr 2016 12:49:38 +0800
changeset 325922 88c060c38873d2373efd49a2e58acc46c22ba532
parent 325921 b91e9c497db9007877d1c0b9661e486591af8d9d
child 325923 c678a3c4169ecdb9bb761e98978307b008659246
push id1128
push userjlund@mozilla.com
push dateWed, 01 Jun 2016 01:31:59 +0000
treeherdermozilla-release@fe0d30de989d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, lizzard
bugs1243729
milestone47.0a2
Bug 1243729 - (m-b) Part II, Test on username selection dialog, r=MattN, a=lizzard MozReview-Commit-ID: 3OKA17mVjMs
toolkit/components/passwordmgr/test/LoginTestUtils.jsm
toolkit/components/passwordmgr/test/browser/browser.ini
toolkit/components/passwordmgr/test/browser/browser_username_select_dialog.js
toolkit/components/passwordmgr/test/browser/head.js
toolkit/components/passwordmgr/test/subtst_notifications_change_p.html
--- a/toolkit/components/passwordmgr/test/LoginTestUtils.jsm
+++ b/toolkit/components/passwordmgr/test/LoginTestUtils.jsm
@@ -238,8 +238,20 @@ this.LoginTestUtils.testData = {
 
       new LoginInfo("chrome://example_extension", null, "Example Login One",
                     "the username", "the password one", "", ""),
       new LoginInfo("chrome://example_extension", null, "Example Login Two",
                     "the username", "the password two", "", ""),
     ];
   },
 };
+
+this.LoginTestUtils.recipes = {
+  getRecipeParent() {
+    let { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {});
+    if (!LoginManagerParent.recipeParentPromise) {
+      return null;
+    }
+    return LoginManagerParent.recipeParentPromise.then((recipeParent) => {
+      return recipeParent;
+    });
+  },
+};
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -1,17 +1,22 @@
 [DEFAULT]
 support-files =
+  ../formsubmit.sjs
+  ../subtst_notifications_change_p.html
   authenticate.sjs
   form_basic.html
+  head.js
   insecure_test.html
   insecure_test_subframe.html
   multiple_forms.html
   streamConverter_content.sjs
 
+[browser_username_select_dialog.js]
+skip-if = e10s # bug 1263760
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_filldoorhanger.js]
 [browser_hasInsecureLoginForms.js]
 [browser_hasInsecureLoginForms_streamConverter.js]
 [browser_notifications.js]
 skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
 [browser_passwordmgr_editing.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_username_select_dialog.js
@@ -0,0 +1,144 @@
+/*
+ * Test username selection dialog, on password update from a p-only form,
+ * when there are multiple saved logins on the domain.
+ */
+
+// Copied from prompt_common.js. TODO: share the code.
+function getSelectDialogDoc() {
+  // Trudge through all the open windows, until we find the one
+  // that has selectDialog.xul loaded.
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+           getService(Ci.nsIWindowMediator);
+  //var enumerator = wm.getEnumerator("navigator:browser");
+  var enumerator = wm.getXULWindowEnumerator(null);
+
+  while (enumerator.hasMoreElements()) {
+    var win = enumerator.getNext();
+    var windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+    var containedDocShells = windowDocShell.getDocShellEnumerator(
+                                      Ci.nsIDocShellTreeItem.typeChrome,
+                                      Ci.nsIDocShell.ENUMERATE_FORWARDS);
+    while (containedDocShells.hasMoreElements()) {
+        // Get the corresponding document for this docshell
+        var childDocShell = containedDocShells.getNext();
+        // We don't want it if it's not done loading.
+        if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+          continue;
+        var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+                                    .contentViewer
+                                    .DOMDocument;
+
+        if (childDoc.location.href == "chrome://global/content/selectDialog.xul")
+          return childDoc;
+    }
+  }
+
+  return null;
+}
+
+let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                                             Ci.nsILoginInfo, "init");
+let login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                             "notifyu1", "notifyp1", "user", "pass");
+let login1B = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
+                              "notifyu1B", "notifyp1B", "user", "pass");
+
+add_task(function* test_changeUPLoginOnPUpdateForm_accept() {
+  info("Select an u+p login from multiple logins, on password update form, and accept.");
+  Services.logins.addLogin(login1);
+  Services.logins.addLogin(login1B);
+
+  yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return getSelectDialogDoc();
+    }, "Wait for selection dialog to be accessible.");
+
+    let doc = getSelectDialogDoc();
+    let dialog = doc.getElementsByTagName("dialog")[0];
+    let listbox = doc.getElementById("list");
+
+    is(listbox.selectedIndex, 0, "Checking selected index");
+    is(listbox.itemCount, 2, "Checking selected length");
+    ['notifyu1', 'notifyu1B'].forEach((username, i) => {
+      is(listbox.getItemAtIndex(i).label, username, "Check username selection on dialog");
+    });
+
+    dialog.acceptDialog();
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return !getSelectDialogDoc();
+    }, "Wait for selection dialog to disappear.");
+  });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 2, "Should have 2 logins");
+
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "pass2", "Check the password changed");
+  is(login.timesUsed, 2, "Check times used");
+
+  login = SpecialPowers.wrap(logins[1]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1B", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
+  // cleanup
+  login1.password = "pass2";
+  Services.logins.removeLogin(login1);
+  login1.password = "notifyp1";
+
+  Services.logins.removeLogin(login1B);
+});
+
+add_task(function* test_changeUPLoginOnPUpdateForm_cancel() {
+  info("Select an u+p login from multiple logins, on password update form, and cancel.");
+  Services.logins.addLogin(login1);
+  Services.logins.addLogin(login1B);
+
+  yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
+    is(fieldValues.username, "null", "Checking submitted username");
+    is(fieldValues.password, "pass2", "Checking submitted password");
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return getSelectDialogDoc();
+    }, "Wait for selection dialog to be accessible.");
+
+    let doc = getSelectDialogDoc();
+    let dialog = doc.getElementsByTagName("dialog")[0];
+    let listbox = doc.getElementById("list");
+
+    is(listbox.selectedIndex, 0, "Checking selected index");
+    is(listbox.itemCount, 2, "Checking selected length");
+    ['notifyu1', 'notifyu1B'].forEach((username, i) => {
+      is(listbox.getItemAtIndex(i).label, username, "Check username selection on dialog");
+    });
+
+    dialog.cancelDialog();
+
+    yield ContentTaskUtils.waitForCondition(() => {
+      return !getSelectDialogDoc();
+    }, "Wait for selection dialog to disappear.");
+  });
+
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 2, "Should have 2 logins");
+
+  let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1", "Check the username unchanged");
+  is(login.password, "notifyp1", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
+  login = SpecialPowers.wrap(logins[1]).QueryInterface(Ci.nsILoginMetaInfo);
+  is(login.username, "notifyu1B", "Check the username unchanged");
+  is(login.password, "notifyp1B", "Check the password unchanged");
+  is(login.timesUsed, 1, "Check times used");
+
+  // cleanup
+  Services.logins.removeLogin(login1);
+  Services.logins.removeLogin(login1B);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/head.js
@@ -0,0 +1,119 @@
+const DIRECTORY_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
+
+Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
+Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
+
+registerCleanupFunction(function* cleanup_removeAllLoginsAndResetRecipes() {
+  Services.logins.removeAllLogins();
+  let recipeParent = LoginTestUtils.recipes.getRecipeParent();
+  if (!recipeParent) {
+    // No need to reset the recipes if the recipe module wasn't even loaded.
+    return;
+  }
+  yield recipeParent.then(recipeParent => recipeParent.reset());
+});
+
+/**
+ * Loads a test page in `DIRECTORY_URL` which automatically submits to formsubmit.sjs and returns a
+ * promise resolving with the field values when the optional `aTaskFn` is done.
+ *
+ * @param {String} aPageFile - test page file name which auto-submits to formsubmit.sjs
+ * @param {Function} aTaskFn - task which can be run before the tab closes.
+ * @param {String} [aOrigin="http://mochi.test:8888"] - origin of the server to
+ *                                                      use to load `aPageFile`.
+ */
+function testSubmittingLoginForm(aPageFile, aTaskFn, aOrigin = "http://mochi.test:8888") {
+  return BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: aOrigin + DIRECTORY_PATH + aPageFile,
+  }, function*(browser) {
+    ok(true, "loaded " + aPageFile);
+    let fieldValues = yield ContentTask.spawn(browser, undefined, function*() {
+      yield ContentTaskUtils.waitForCondition(() => {
+        return content.location.pathname.endsWith("/formsubmit.sjs") &&
+          content.document.readyState == "complete";
+      }, "Wait for form submission load (formsubmit.sjs)");
+      let username = content.document.getElementById("user").textContent;
+      let password = content.document.getElementById("pass").textContent;
+      return {
+        username,
+        password,
+      };
+    });
+    ok(true, "form submission loaded");
+    if (aTaskFn) {
+      yield* aTaskFn(fieldValues);
+    }
+    return fieldValues;
+  });
+}
+
+function checkOnlyLoginWasUsedTwice({ justChanged }) {
+  // Check to make sure we updated the timestamps and use count on the
+  // existing login that was submitted for the test.
+  let logins = Services.logins.getAllLogins();
+  is(logins.length, 1, "Should only have 1 login");
+  ok(logins[0] instanceof Ci.nsILoginMetaInfo, "metainfo QI");
+  is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
+  ok(logins[0].timeCreated < logins[0].timeLastUsed, "timeLastUsed bumped");
+  if (justChanged) {
+    is(logins[0].timeLastUsed, logins[0].timePasswordChanged, "timeLastUsed == timePasswordChanged");
+  } else {
+    is(logins[0].timeCreated, logins[0].timePasswordChanged, "timeChanged not updated");
+  }
+}
+
+// Begin popup notification (doorhanger) functions //
+
+const REMEMBER_BUTTON = 0;
+const NEVER_BUTTON = 1;
+
+const CHANGE_BUTTON = 0;
+const DONT_CHANGE_BUTTON = 1;
+
+/**
+ * Checks if we have a password capture popup notification
+ * of the right type and with the right label.
+ *
+ * @param {String} aKind The desired `passwordNotificationType`
+ * @return the found password popup notification.
+ */
+function getCaptureDoorhanger(aKind) {
+  ok(true, "Looking for " + aKind + " popup notification");
+  let notification = PopupNotifications.getNotification("password");
+  if (notification) {
+    is(notification.options.passwordNotificationType, aKind, "Notification type matches.");
+    if (aKind == "password-change") {
+      is(notification.mainAction.label, "Update", "Main action label matches update doorhanger.");
+    } else if (aKind == "password-save") {
+      is(notification.mainAction.label, "Remember", "Main action label matches save doorhanger.");
+    }
+  }
+  return notification;
+}
+
+/**
+ * Clicks the specified popup notification button.
+ *
+ * @param {Element} aPopup Popup Notification element
+ * @param {Number} aButtonIndex Number indicating which button to click.
+ *                              See the constants in this file.
+ */
+function clickDoorhangerButton(aPopup, aButtonIndex) {
+  ok(true, "Looking for action at index " + aButtonIndex);
+
+  let notifications = aPopup.owner.panel.childNodes;
+  ok(notifications.length > 0, "at least one notification displayed");
+  ok(true, notifications.length + " notification(s)");
+  let notification = notifications[0];
+
+  if (aButtonIndex == 0) {
+    ok(true, "Triggering main action");
+    notification.button.doCommand();
+  } else if (aButtonIndex <= aPopup.secondaryActions.length) {
+    ok(true, "Triggering secondary action " + aButtonIndex);
+    notification.childNodes[aButtonIndex].doCommand();
+  }
+}
+
+// End popup notification (doorhanger) functions //
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/subtst_notifications_change_p.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Subtest for Login Manager notifications</title>
+</head>
+<body>
+<h2>Change password</h2>
+<form id="form" action="formsubmit.sjs">
+  <input id="pass_current" name="pass_current" type="password" value="notifyp1">
+  <input id="pass" name="pass" type="password">
+  <input id="pass_confirm" name="pass_confirm" type="password">
+  <button type='submit'>Submit</button>
+</form>
+
+<script>
+function submitForm() {
+  passField.value = "pass2";
+  passConfirmField.value = "pass2";
+
+  form.submit();
+}
+
+window.onload = submitForm;
+var form      = document.getElementById("form");
+var userField = document.getElementById("user");
+var passField = document.getElementById("pass");
+var passConfirmField = document.getElementById("pass_confirm");
+
+</script>
+</body>
+</html>