Bug 1188719 - Tests for the username fill login context menu. r=MattN a=sylvestre
authorBernardo P. Rittmeyer <bernardo@rittme.com>
Wed, 14 Oct 2015 17:24:18 -0400
changeset 289561 645e5fe8f354
parent 289560 b0aa8d67c934
child 289562 82cc6b6e6eaa
push id5188
push usermozilla@noorenberghe.ca
push date2015-10-15 17:59 +0000
treeherdermozilla-beta@645e5fe8f354 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, sylvestre
bugs1188719
milestone42.0
Bug 1188719 - Tests for the username fill login context menu. r=MattN a=sylvestre
browser/base/content/test/general/test_contextmenu.html
toolkit/components/passwordmgr/test/browser/browser_context_menu.js
toolkit/components/passwordmgr/test/browser/multiple_forms.html
--- a/browser/base/content/test/general/test_contextmenu.html
+++ b/browser/base/content/test/general/test_contextmenu.html
@@ -75,17 +75,17 @@ function runTest(testNum) {
   ok(true, "Starting test #" + testNum);
 
   var inspectItems = [];
   if (SpecialPowers.getBoolPref("devtools.inspector.enabled")) {
     inspectItems = ["---", null,
                     "context-inspect", true];
   }
 
-  var passwordFillItems = [
+  var loginFillItems = [
     "---", null,
     "fill-login", null,
       [
         "fill-login-no-logins", false,
         "---", null,
         "fill-login-saved-passwords", true
       ], null,
   ];
@@ -647,17 +647,18 @@ function runTest(testNum) {
                           "context-copy",        true,
                           "context-paste",       null, // ignore clipboard state
                           "context-delete",      true,
                           "---",                 null,
                           "context-selectall",   true,
                           "context-searchselect",true,
                           "---",                 null,
                           "spell-check-enabled", true
-                         ].concat(inspectItems));
+                         ].concat(loginFillItems)
+                          .concat(inspectItems));
         closeContextMenu();
         selectInputText(select_inputtext_password); // Select text prior to opening context menu.
         openContextMenuFor(select_inputtext_password); // Invoke context menu for next test.
     },
 
     function () {
         // Context menu for selected text in input[type="password"]
         checkContextMenu(["context-undo",        false,
@@ -670,17 +671,17 @@ function runTest(testNum) {
                           "context-selectall",   true,
                           "---",                 null,
                           "spell-check-enabled", true,
                           //spell checker is shown on input[type="password"] on this testcase
                           "spell-dictionaries",  true,
                               ["spell-check-dictionary-en-US", true,
                                "---",                          null,
                                "spell-add-dictionaries",       true], null
-                         ].concat(passwordFillItems)
+                         ].concat(loginFillItems)
                           .concat(inspectItems));
         closeContextMenu();
         subwindow.getSelection().removeAllRanges();
         openContextMenuFor(plugin);
     },
 
     function () {
         // Context menu for click-to-play blocked plugin
--- a/toolkit/components/passwordmgr/test/browser/browser_context_menu.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_context_menu.js
@@ -3,16 +3,17 @@
  */
 
 "use strict";
 
 Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
 
 // The hostname for the test URIs.
 const TEST_HOSTNAME = "https://example.com";
+const MULTIPLE_FORMS_PAGE_PATH = "/browser/toolkit/components/passwordmgr/test/browser/multiple_forms.html";
 
 /**
  * Initialize logins needed for the tests and disable autofill
  * for login forms for easier testing of manual fill.
  */
 add_task(function* test_initialize() {
   Services.prefs.setBoolPref("signon.autofillForms", false);
   registerCleanupFunction(() => {
@@ -23,23 +24,45 @@ add_task(function* test_initialize() {
     Services.logins.addLogin(login);
   }
 });
 
 /**
  * Check if the context menu is populated with the right
  * menuitems for the target password input field.
  */
-add_task(function* test_context_menu_populate() {
+add_task(function* test_context_menu_populate_password() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
+  }, function* (browser) {
+    let passwordInput = browser.contentWindow.document.getElementById("test-password-1");
+
+    yield openPasswordContextMenu(browser, passwordInput);
+
+    // Check the content of the password manager popup
+    let popupMenu = document.getElementById("fill-login-popup");
+    checkMenu(popupMenu);
+
+    let contextMenu = document.getElementById("contentAreaContextMenu");
+    contextMenu.hidePopup();
+  });
+});
+
+/**
+ * Check if the context menu is populated with the right menuitems
+ * for the target username field with a password field present.
+ */
+add_task(function* test_context_menu_populate_username_with_password() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: TEST_HOSTNAME + "/browser/toolkit/components/" +
          "passwordmgr/test/browser/multiple_forms.html",
   }, function* (browser) {
-    let passwordInput = browser.contentWindow.document.getElementById("test-password-1");
+    let passwordInput = browser.contentWindow.document.getElementById("test-username-2");
 
     yield openPasswordContextMenu(browser, passwordInput);
 
     // Check the content of the password manager popup
     let popupMenu = document.getElementById("fill-login-popup");
     checkMenu(popupMenu);
 
     let contextMenu = document.getElementById("contentAreaContextMenu");
@@ -47,109 +70,125 @@ add_task(function* test_context_menu_pop
   });
 });
 
 /**
  * Check if the password field is correctly filled when one
  * login menuitem is clicked.
  */
 add_task(function* test_context_menu_password_fill() {
-  // Set of element ids to check.
-  let testSet = [
-    {
-      passwordInput: "test-password-1",
-      unchangedFields: null,
-    },
-    {
-      passwordInput: "test-password-2",
-      unchangedFields: ["test-username-2"],
-    },
-    {
-      passwordInput: "test-password-3",
-      unchangedFields: ["test-username-3"],
-    },
-    {
-      passwordInput: "test-password-4",
-      unchangedFields: ["test-username-4"],
-    },
-    {
-      passwordInput: "test-password-5",
-      unchangedFields: ["test-username-5", "test-password2-5"],
-    },
-    {
-      passwordInput: "test-password2-5",
-      unchangedFields: ["test-username-5", "test-password-5"],
-    },
-    {
-      passwordInput: "test-password-6",
-      unchangedFields: ["test-username-6", "test-password2-6"],
-    },
-    {
-      passwordInput: "test-password2-6",
-      unchangedFields: ["test-username-6", "test-password-6"],
-    },
-    {
-      passwordInput: "test-password-7",
-      unchangedFields: null,
-    },
-  ];
-
   yield BrowserTestUtils.withNewTab({
     gBrowser,
-    url: TEST_HOSTNAME + "/browser/toolkit/components/" +
-         "passwordmgr/test/browser/multiple_forms.html",
+    url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
   }, function* (browser) {
-    for (let testCase of testSet) {
-      let passwordInput = browser.contentWindow.document.getElementById(testCase.passwordInput);
+
+    let testForms = browser.contentWindow.document.getElementsByClassName("test-form");
+    for (let form of testForms) {
+      let usernameInputList = form.querySelectorAll("input[type='password']");
+      info("Testing form: " + form.getAttribute("description"));
 
-      yield openPasswordContextMenu(browser, passwordInput);
+      for (let passwordField of usernameInputList) {
+        info("Testing password field: " + passwordField.id);
+
+        let contextMenu = document.getElementById("contentAreaContextMenu");
+        let menuItemStatus = form.getAttribute("menuitemStatus");
+
+        // Synthesize a right mouse click over the username input element.
+        yield openPasswordContextMenu(browser, passwordField, ()=> {
+          let popupHeader = document.getElementById("fill-login");
 
-      let popupMenu = document.getElementById("fill-login-popup");
+          // If the password field is disabled or read-only, we want to see
+          // the disabled Fill Password popup header.
+          if (passwordField.disabled || passwordField.readOnly) {
+            Assert.ok(!popupHeader.hidden, "Popup menu is not hidden.");
+            Assert.ok(popupHeader.disabled, "Popup menu is disabled.");
+            contextMenu.hidePopup();
+            return false;
+          }
+          return true;
+        });
 
-      // Store the values of fields that should remain unchanged.
-      let unchangedFieldsValues = null;
-      if (testCase.unchangedFields) {
-        unchangedFieldsValues = [];
-        for (let fieldId of testCase.unchangedFields) {
-          unchangedFieldsValues[fieldId] = browser.contentWindow.document.getElementById(fieldId).value;
+        if (contextMenu.state != "open") {
+          continue;
         }
+
+        // The only field affected by the password fill
+        // should be the target password field itself.
+        let unchangedFields = form.querySelectorAll('input:not(#' + passwordField.id + ')');
+        yield assertContextMenuFill(form, null, passwordField, unchangedFields);
+        contextMenu.hidePopup();
       }
+    }
+  });
+});
 
-      // Execute the default command of the first login menuitem found at the context menu.
-      let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
-      firstLoginItem.doCommand();
+/**
+ * Check if the form is correctly filled when one
+ * username context menu login menuitem is clicked.
+ */
+add_task(function* test_context_menu_username_login_fill() {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
+  }, function* (browser) {
 
-      yield BrowserTestUtils.waitForEvent(passwordInput, "input", "Password input value changed");
+    let testForms = browser.contentWindow.document.getElementsByClassName("test-form");
+    for (let form of testForms) {
+      let usernameInputList = form.querySelectorAll("input[type='text']");
+      info("Testing form: " + form.getAttribute("description"));
 
-      // Find the used login by it's username (Use only unique usernames in this test).
-      let login = getLoginFromUsername(firstLoginItem.label);
+      for (let usernameField of usernameInputList) {
+        info("Testing username field: " + usernameField.id);
+
+        // We always want to check if the first password field is filled,
+        // since this is the current behavior from the _fillForm function.
+        let passwordField = form.querySelector("input[type='password']");
+
+        let contextMenu = document.getElementById("contentAreaContextMenu");
+        let menuItemStatus = form.getAttribute("menuitemStatus");
 
-      Assert.equal(login.password, passwordInput.value, "Password filled and correct.");
+        // Synthesize a right mouse click over the username input element.
+        yield openPasswordContextMenu(browser, usernameField, ()=> {
+          let popupHeader = document.getElementById("fill-login");
 
-      // Check that the fields that should remain unchanged didn't got modified.
-      if (testCase.unchangedFields) {
-        Assert.ok(testCase.unchangedFields.every(fieldId => {
-          return unchangedFieldsValues[fieldId] == browser.contentWindow.document.getElementById(fieldId).value;
-        }), "Other fields were not changed.");
+          // If we don't want to see the actual popup menu,
+          // check if the popup is hidden or disabled.
+          if (!passwordField || usernameField.disabled || usernameField.readOnly ||
+              passwordField.disabled || passwordField.readOnly) {
+            if (!passwordField) {
+              Assert.ok(popupHeader.hidden, "Popup menu is hidden.");
+            } else {
+              Assert.ok(!popupHeader.hidden, "Popup menu is not hidden.");
+              Assert.ok(popupHeader.disabled, "Popup menu is disabled.");
+            }
+            contextMenu.hidePopup();
+            return false;
+          }
+          return true;
+        });
+
+        if (contextMenu.state != "open") {
+          continue;
+        }
+        // We shouldn't change any field that's not the target username field or the first password field
+        let unchangedFields = form.querySelectorAll('input:not(#' + usernameField.id + '):not(#' + passwordField.id + ')');
+        yield assertContextMenuFill(form, usernameField, passwordField, unchangedFields);
+        contextMenu.hidePopup();
       }
-
-      let contextMenu = document.getElementById("contentAreaContextMenu");
-      contextMenu.hidePopup();
     }
   });
 });
 
 /**
  * Check if the password field is correctly filled when it's in an iframe.
  */
 add_task(function* test_context_menu_iframe_fill() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
-    url: TEST_HOSTNAME + "/browser/toolkit/components/" +
-         "passwordmgr/test/browser/multiple_forms.html",
+    url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
   }, function* (browser) {
     let iframe = browser.contentWindow.document.getElementById("test-iframe");
     let passwordInput = iframe.contentDocument.getElementById("form-basic-password");
 
     let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
     let eventDetails = {type: "contextmenu", button: 2};
 
     // To click at the right point we have to take into account the iframe offset.
@@ -194,32 +233,85 @@ add_task(function* test_context_menu_ifr
     let contextMenu = document.getElementById("contentAreaContextMenu");
     contextMenu.hidePopup();
   });
 });
 
 /**
  * Synthesize mouse clicks to open the password manager context menu popup
  * for a target password input element.
+ *
+ * assertCallback should return true if we should continue or else false.
  */
-function* openPasswordContextMenu(browser, passwordInput) {
+function* openPasswordContextMenu(browser, passwordInput, assertCallback = null) {
   // Synthesize a right mouse click over the password input element.
   let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
   let eventDetails = {type: "contextmenu", button: 2};
   BrowserTestUtils.synthesizeMouseAtCenter(passwordInput, eventDetails, browser);
   yield contextMenuShownPromise;
 
+  if (assertCallback) {
+    if (!assertCallback.call()) {
+      return;
+    }
+  }
+
   // Synthesize a mouse click over the fill login menu header.
   let popupHeader = document.getElementById("fill-login");
   let popupShownPromise = BrowserTestUtils.waitForEvent(popupHeader, "popupshown");
   EventUtils.synthesizeMouseAtCenter(popupHeader, {});
   yield popupShownPromise;
 }
 
 /**
+ * Verify that only the expected form fields are filled.
+ */
+function* assertContextMenuFill(form, usernameField, passwordField, unchangedFields){
+  let popupMenu = document.getElementById("fill-login-popup");
+
+  // Store the value of fields that should remain unchanged.
+  if (unchangedFields.length) {
+    for (let field of unchangedFields) {
+      field.setAttribute("original-value", field.value);
+    }
+  }
+
+  // Execute the default command of the first login menuitem found at the context menu.
+  let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
+  firstLoginItem.doCommand();
+
+  yield BrowserTestUtils.waitForEvent(form, "input", "Username input value changed");
+
+  // Find the used login by it's username (Use only unique usernames in this test).
+  let login = getLoginFromUsername(firstLoginItem.label);
+
+  // If we have an username field, check if it's correctly filled
+  if (usernameField && usernameField.getAttribute("expectedFail") == null) {
+    Assert.equal(login.username, usernameField.value, "Username filled and correct.");
+  }
+
+  // If we have a password field, check if it's correctly filled
+  if (passwordField && passwordField.getAttribute("expectedFail") == null) {
+    Assert.equal(passwordField.value, login.password, "Password filled and correct.");
+  }
+
+  // Check that all fields that should not change have the same value as before.
+  if (unchangedFields.length) {
+    Assert.ok(()=> {
+      for (let field of unchangedFields) {
+        if (field.value != field.getAttribute("original-value")) {
+          return false;
+        }
+      }
+      return true;
+    }, "Other fields were not changed.");
+  }
+}
+
+/**
  * Check if every login that matches the page hostname are available at the context menu.
  */
 function checkMenu(contextMenu) {
   let logins = loginList().filter(login => login.hostname == TEST_HOSTNAME);
   // Make an array of menuitems for easier comparison.
   let menuitems = [...contextMenu.getElementsByClassName("context-login-item")];
   Assert.equal(menuitems.length, logins.length, "Same amount of menu items and expected logins.");
   Assert.ok(logins.every(l => menuitems.some(m => l.username == m.label)), "Every login have an item at the menu.");
--- a/toolkit/components/passwordmgr/test/browser/multiple_forms.html
+++ b/toolkit/components/passwordmgr/test/browser/multiple_forms.html
@@ -1,57 +1,129 @@
 <!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
-<!-- Password only form -->
-<form id='form-1'>
+
+<form class="test-form"
+      description="Password only form">
     <input id='test-password-1' type='password' name='pname' value=''>
     <input type='submit'>Submit</input>
 </form>
 
-<!-- Simple username and password blank form -->
-<form id='form-2'>
+
+<form class="test-form"
+      description="Username only form">
+    <input id='test-username-1' type='text' name='uname' value=''>
+    <input type='submit'>Submit</input>
+</form>
+
+
+<form class="test-form"
+      description="Simple username and password blank form">
     <input id='test-username-2' type='text'     name='uname' value=''>
     <input id='test-password-2' type='password' name='pname' value=''>
     <button type='submit'>Submit</button>
 </form>
 
-<!-- Simple username and password form, prefilled username -->
-<form id='form-3'>
+
+<form class="test-form"
+      description="Simple username and password form, prefilled username">
     <input id='test-username-3' type='text'     name='uname' value='testuser'>
     <input id='test-password-3' type='password' name='pname' value=''>
     <button type='submit'>Submit</button>
 </form>
 
-<!-- Simple username and password form, prefilled username and password -->
-<form id='form-4'>
+
+<form class="test-form"
+      description="Simple username and password form, prefilled username and password">
     <input id='test-username-4' type='text'     name='uname' value='testuser'>
     <input id='test-password-4' type='password' name='pname' value='testpass'>
     <button type='submit'>Submit</button>
 </form>
 
-<!-- One username and two passwords empty form -->
-<form id='form-5'>
+
+<form class="test-form"
+      description="One username and two passwords empty form">
     <input id='test-username-5'  type='text'     name='uname'>
     <input id='test-password-5'  type='password' name='pname'>
     <input id='test-password2-5' type='password' name='pname2'>
     <button type='submit'>Submit</button>
 </form>
 
-<!-- One username and two passwords form, fields prefiled -->
-<form id='form-6'>
+
+<form class="test-form"
+      description="One username and two passwords form, fields prefiled">
     <input id='test-username-6'  type='text'     name='uname' value="testuser">
     <input id='test-password-6'  type='password' name='pname' value="testpass">
     <input id='test-password2-6' type='password' name='pname2' value="testpass">
     <button type='submit'>Submit</button>
 </form>
 
-<!-- Password field with no form -->
-<div id='form-7'>
+
+<div class="test-form"
+     description="Username and password fields with no form">
+    <input id='test-username-7' type='text'     name='uname' value="testuser">
     <input id='test-password-7' type='password' name='pname' value="testpass">
 </div>
 
+
+<form class="test-form"
+      description="Simple username and password blank form, with disabled password">
+    <input id='test-username-8' type='text'     name='uname' value=''>
+    <input id='test-password-8' type='password' name='pname' value='' disabled>
+    <button type='submit'>Submit</button>
+</form>
+
+
+<form class="test-form"
+      description="Simple username and password blank form, with disabled username">
+    <input id='test-username-9' type='text'     name='uname' value='' disabled>
+    <input id='test-password-9' type='password' name='pname' value=''>
+    <button type='submit'>Submit</button>
+</form>
+
+
+<form class="test-form"
+      description="Simple username and password blank form, with readonly password">
+    <input id='test-username-10' type='text'     name='uname' value=''>
+    <input id='test-password-10' type='password' name='pname' value='' readonly>
+    <button type='submit'>Submit</button>
+</form>
+
+
+<form class="test-form"
+      description="Simple username and password blank form, with readonly username">
+    <input id='test-username-11' type='text'     name='uname' value='' readonly>
+    <input id='test-password-11' type='password' name='pname' value=''>
+    <button type='submit'>Submit</button>
+</form>
+
+
+<form class="test-form"
+      description="Two username and one passwords form, fields prefiled">
+    <input id='test-username-12'  type='text'     name='uname'  value="testuser">
+    <input id='test-username2-12' type='text'     name='uname2' value="testuser">
+    <input id='test-password-12'  type='password' name='pname'  value="testpass">
+    <button type='submit'>Submit</button>
+</form>
+
+
+<form class="test-form"
+      description="Two username and one passwords form, one disabled username field">
+    <input id='test-username-13'  type='text'     name='uname'>
+    <input id='test-username2-13' type='text'     name='uname2' disabled>
+    <input id='test-password-13'  type='password' name='pname'>
+    <button type='submit'>Submit</button>
+</form>
+
+
+<div class="test-form"
+     description="Second username and password fields with no form">
+    <input id='test-username-14' type='text'     name='uname'>
+    <input id='test-password-14' type='password' name='pname' expectedFail>
+</div>
+
 <!-- Form in an iframe -->
 <iframe src="https://example.org/browser/toolkit/components/passwordmgr/test/browser/form_basic.html" id="test-iframe"></iframe>
 
 </body>
 </html>