Bug 433238 - Tests for the password manager contextual menu password fill. r=MattN
authorBernardo P. Rittmeyer <bernardo@rittme.com>
Thu, 06 Aug 2015 18:45:44 -0700
changeset 256803 96f128a9b4656aee393a3d022bb9d40a2a8c0384
parent 256802 02ff02df924ad184ec843ac0d9677cb38a95cba8
child 256804 d0a1ddd01c1e2b6e67bce8272b0a512d43667293
push id14512
push usermozilla@noorenberghe.ca
push dateFri, 07 Aug 2015 20:16:50 +0000
treeherderfx-team@a4836b5699de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs433238
milestone42.0a1
Bug 433238 - Tests for the password manager contextual menu password fill. r=MattN
browser/base/content/test/general/contextmenu_common.js
browser/base/content/test/general/test_contextmenu.html
browser/base/content/test/general/test_contextmenu_input.html
toolkit/components/passwordmgr/test/browser/browser.ini
toolkit/components/passwordmgr/test/browser/browser_context_menu.js
toolkit/components/passwordmgr/test/browser/multiple_forms.html
toolkit/components/passwordmgr/test/unit/head.js
toolkit/components/passwordmgr/test/unit/test_context_menu.js
toolkit/components/passwordmgr/test/unit/xpcshell.ini
--- a/browser/base/content/test/general/contextmenu_common.js
+++ b/browser/base/content/test/general/contextmenu_common.js
@@ -56,17 +56,19 @@ function getVisibleMenuItems(aMenu, aDat
             ok(label.length, "menuitem " + item.id + " has a label");
             if (isSpellSuggestion) {
               is(key, "", "Spell suggestions shouldn't have an access key");
               items.push("*" + label);
             } else if (isGenerated) {
               items.push("+" + label);
             } else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
                        item.id != "spell-no-suggestions" &&
-                       item.id != "spell-add-dictionaries-main") {
+                       item.id != "spell-add-dictionaries-main" &&
+                       item.id != "fill-login-saved-passwords" &&
+                       item.id != "fill-login-no-logins") {
               ok(key, "menuitem " + item.id + " has an access key");
               if (accessKeys[key])
                   ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
               else
                   accessKeys[key] = item.id;
             }
             if (!isSpellSuggestion && !isGenerated) {
               items.push(item.id);
--- a/browser/base/content/test/general/test_contextmenu.html
+++ b/browser/base/content/test/general/test_contextmenu.html
@@ -75,16 +75,26 @@ function runTest(testNum) {
   ok(true, "Starting test #" + testNum);
 
   var inspectItems = [];
   if (SpecialPowers.getBoolPref("devtools.inspector.enabled")) {
     inspectItems = ["---", null,
                     "context-inspect", true];
   }
 
+  var passwordFillItems = [
+    "---", null,
+    "fill-login", null,
+      [
+        "fill-login-no-logins", false,
+        "---", null,
+        "fill-login-saved-passwords", true
+      ], null,
+  ];
+
   var tests = [
     function () {
         // Invoke context menu for next test.
         openContextMenuFor(text);
     },
 
     function () {
         // Context menu for plain text
@@ -660,17 +670,18 @@ 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(inspectItems));
+                         ].concat(passwordFillItems)
+                          .concat(inspectItems));
         closeContextMenu();
         subwindow.getSelection().removeAllRanges();
         openContextMenuFor(plugin);
     },
 
     function () {
         // Context menu for click-to-play blocked plugin
         checkContextMenu(["context-navigation", null,
--- a/browser/base/content/test/general/test_contextmenu_input.html
+++ b/browser/base/content/test/general/test_contextmenu_input.html
@@ -162,16 +162,21 @@ function runTest(testNum) {
                           "---",                 null,
                           "context-cut",         true,
                           "context-copy",        true,
                           "context-paste",       null, // ignore clipboard state
                           "context-delete",      false,
                           "---",                 null,
                           "context-selectall",   false,
                           "---",                 null,
+                          "fill-login",          null,
+                            ["fill-login-no-logins",       false,
+                             "---",                        null,
+                             "fill-login-saved-passwords", true], null,
+                          "---",                 null,
                           "context-inspect",     true]);
 
         closeContextMenu();
 
         input.type = 'email';
 
         openContextMenuFor(input); // Invoke context menu for next test.
         break;
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -1,18 +1,20 @@
 [DEFAULT]
 support-files =
   authenticate.sjs
   form_basic.html
+  multiple_forms.html
 
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_filldoorhanger.js]
 [browser_notifications.js]
 skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
 [browser_passwordmgr_editing.js]
 skip-if = os == "linux"
+[browser_context_menu.js]
 [browser_passwordmgr_fields.js]
 [browser_passwordmgr_observers.js]
 [browser_passwordmgr_sort.js]
 [browser_passwordmgr_switchtab.js]
 [browser_passwordmgrcopypwd.js]
 [browser_passwordmgrdlg.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_context_menu.js
@@ -0,0 +1,270 @@
+/*
+ * Test the password manager context menu.
+ */
+
+"use strict";
+
+Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
+
+// The hostname for the test URIs.
+const TEST_HOSTNAME = "https://example.com";
+
+/**
+ * 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(() => {
+    Services.prefs.clearUserPref("signon.autofillForms");
+    Services.logins.removeAllLogins();
+  });
+  for (let login of loginList()) {
+    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() {
+  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");
+
+    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 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",
+  }, function* (browser) {
+    for (let testCase of testSet) {
+      let passwordInput = browser.contentWindow.document.getElementById(testCase.passwordInput);
+
+      yield openPasswordContextMenu(browser, passwordInput);
+
+      let popupMenu = document.getElementById("fill-login-popup");
+
+      // 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;
+        }
+      }
+
+      // 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(passwordInput, "input", "Password input value changed");
+
+      // Find the used login by it's username (Use only unique usernames in this test).
+      let login = getLoginFromUsername(firstLoginItem.label);
+
+      Assert.equal(login.password, passwordInput.value, "Password filled and correct.");
+
+      // 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.");
+      }
+
+      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",
+  }, 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.
+    let iframeRect = iframe.getBoundingClientRect();
+    let inputRect = passwordInput.getBoundingClientRect();
+    let clickPos = {
+      offsetX: iframeRect.left + inputRect.width / 2,
+      offsetY: iframeRect.top  + inputRect.height / 2,
+    };
+
+    // Synthesize a right mouse click over the password input element.
+    BrowserTestUtils.synthesizeMouse(passwordInput, clickPos.offsetX, clickPos.offsetY, eventDetails, browser);
+    yield contextMenuShownPromise;
+
+    // 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;
+
+    let popupMenu = document.getElementById("fill-login-popup");
+
+    // Stores the original value of username
+    let usernameInput = iframe.contentDocument.getElementById("form-basic-username");
+    let usernameOriginalValue = usernameInput.value;
+
+    // Execute the command of the first login menuitem found at the context menu.
+    let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
+    firstLoginItem.doCommand();
+
+    yield BrowserTestUtils.waitForEvent(passwordInput, "input", "Password input value changed");
+
+    // Find the used login by it's username.
+    let login = getLoginFromUsername(firstLoginItem.label);
+
+    Assert.equal(login.password, passwordInput.value, "Password filled and correct.");
+
+    Assert.equal(usernameOriginalValue,
+                 usernameInput.value,
+                 "Username value was not changed.");
+
+    let contextMenu = document.getElementById("contentAreaContextMenu");
+    contextMenu.hidePopup();
+  });
+});
+
+/**
+ * Synthesize mouse clicks to open the password manager context menu popup
+ * for a target password input element.
+ */
+function* openPasswordContextMenu(browser, passwordInput) {
+  // 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;
+
+  // 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;
+}
+
+/**
+ * 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.");
+}
+
+/**
+ * Search for a login by it's username.
+ *
+ * Only unique login/hostname combinations should be used at this test.
+ */
+function getLoginFromUsername(username) {
+  return loginList().find(login => login.username == username);
+}
+
+/**
+ * List of logins used for the test.
+ *
+ * We should only use unique usernames in this test,
+ * because we need to search logins by username.
+ */
+function loginList() {
+  return [
+    LoginTestUtils.testData.formLogin({
+      hostname: "https://example.com",
+      formSubmitURL: "https://example.com",
+      username: "username",
+      password: "password",
+    }),
+    LoginTestUtils.testData.formLogin({
+      hostname: "http://example.com",
+      formSubmitURL: "http://example.com",
+      username: "username1",
+      password: "password1",
+    }),
+    LoginTestUtils.testData.formLogin({
+      hostname: "https://example.com",
+      formSubmitURL: "https://example.com",
+      username: "username2",
+      password: "password2",
+    }),
+    LoginTestUtils.testData.formLogin({
+      hostname: "https://example.org",
+      formSubmitURL: "https://example.org",
+      username: "username-cross-origin",
+      password: "password-cross-origin",
+    }),
+  ];
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/multiple_forms.html
@@ -0,0 +1,57 @@
+<!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'>
+    <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'>
+    <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'>
+    <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'>
+    <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'>
+    <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'>
+    <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'>
+    <input id='test-password-7' type='password' name='pname' value="testpass">
+</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>
--- a/toolkit/components/passwordmgr/test/unit/head.js
+++ b/toolkit/components/passwordmgr/test/unit/head.js
@@ -124,21 +124,21 @@ const RecipeHelpers = {
     return (new LoginRecipesParent({ defaults: null })).initializationPromise;
   },
 };
 
 const MockDocument = {
   /**
    * Create a document for the given URL containing the given HTML with the ownerDocument of all <form>s having a mocked location.
    */
-  createTestDocument(aDocumentURL, aHTML = "<form>") {
+  createTestDocument(aDocumentURL, aContent = "<form>", aType = "text/html") {
     let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                  createInstance(Ci.nsIDOMParser);
     parser.init();
-    let parsedDoc = parser.parseFromString(aHTML, "text/html");
+    let parsedDoc = parser.parseFromString(aContent, aType);
 
     for (let element of parsedDoc.forms) {
       this.mockOwnerDocumentProperty(element, parsedDoc, aDocumentURL);
     }
     return parsedDoc;
   },
 
   mockOwnerDocumentProperty(aElement, aDoc, aURL) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/unit/test_context_menu.js
@@ -0,0 +1,165 @@
+/*
+ * Test the password manager context menu.
+ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LoginManagerContextMenu.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
+  return Services.strings.
+         createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
+});
+
+/**
+ * Prepare data for the following tests.
+ */
+add_task(function* test_initialize() {
+  for (let login of loginList()) {
+    Services.logins.addLogin(login);
+  }
+});
+
+/**
+ * Tests if the LoginManagerContextMenu returns the correct login items.
+ */
+add_task(function* test_contextMenuAddAndRemoveLogins() {
+  const DOCUMENT_CONTENT = "<form><input id='pw' type=password></form>";
+  const INPUT_QUERY = "input[type='password']";
+
+  let testHostnames = [
+    "http://www.example.com",
+    "http://www2.example.com",
+    "http://www3.example.com",
+    "http://empty.example.com",
+  ];
+
+  for (let hostname of testHostnames) {
+    do_print("test for hostname: " + hostname);
+    // Get expected logins for this test.
+    let logins = getExpectedLogins(hostname);
+
+    // Create the logins menuitems fragment.
+    let {fragment, document} = createLoginsFragment(hostname, DOCUMENT_CONTENT, INPUT_QUERY);
+
+    if (!logins.length) {
+      Assert.ok(fragment === null, "Null returned. No logins where found.");
+      continue;
+    }
+    let items = [...fragment.querySelectorAll("menuitem")];
+
+    // Check if the items are those expected to be listed.
+    Assert.ok(checkLoginItems(logins, items), "All expected logins found.");
+    document.body.appendChild(fragment);
+
+    // Try to clear the fragment.
+    LoginManagerContextMenu.clearLoginsFromMenu(document);
+    Assert.equal(fragment.querySelectorAll("menuitem").length, 0, "All items correctly cleared.");
+  }
+
+  Services.logins.removeAllLogins();
+});
+
+/**
+ * Create a fragment with a menuitem for each login.
+ */
+function createLoginsFragment(url, content, elementQuery) {
+  const CHROME_URL = "chrome://mock-chrome";
+
+  // Create a mock document.
+  let document = MockDocument.createTestDocument(CHROME_URL, content);
+  let inputElement = document.querySelector(elementQuery);
+  MockDocument.mockOwnerDocumentProperty(inputElement, document, url);
+
+  // We also need a simple mock Browser object for this test.
+  let browser = {
+    ownerDocument: document
+  };
+
+  let URI = Services.io.newURI(url, null, null);
+  return {
+    document,
+    fragment: LoginManagerContextMenu.addLoginsToMenu(inputElement, browser, URI),
+  }
+}
+
+/**
+ * Check if every login have it's corresponding menuitem.
+ * Duplicates and empty usernames have a date appended.
+ */
+function checkLoginItems(logins, items) {
+  function findDuplicates(loginList) {
+    var seen = new Set();
+    var duplicates = new Set();
+    for (let login of loginList) {
+      if (seen.has(login.username)) {
+        duplicates.add(login.username);
+      }
+      seen.add(login.username);
+    }
+    return duplicates;
+  }
+  let duplicates = findDuplicates(logins);
+
+  let dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
+                             { day: "numeric", month: "short", year: "numeric" });
+  for (let login of logins) {
+    if (login.username && !duplicates.has(login.username)) {
+      // If login is not duplicate and we can't find an item for it, fail.
+      if (!items.find(item => item.label == login.username)) {
+        return false;
+      }
+      continue;
+    }
+
+    let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
+    let time = dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
+    // If login is duplicate, check if we have a login item with appended date.
+    if (login.username && !items.find(item => item.label == login.username + " (" + time + ")")) {
+      return false;
+    }
+    // If login is empty, check if we have a login item with appended date.
+    if (!login.username &&
+        !items.find(item => item.label == _stringBundle.GetStringFromName("noUsername") + " (" + time + ")")) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Gets the list of expected logins for a hostname.
+ */
+function getExpectedLogins(hostname) {
+  return Services.logins.getAllLogins().filter(entry => entry["hostname"] === hostname);
+}
+
+function loginList() {
+  return [
+      new LoginInfo("http://www.example.com", "http://www.example.com", null,
+                    "username1", "password",
+                    "form_field_username", "form_field_password"),
+
+      new LoginInfo("http://www.example.com", "http://www.example.com", null,
+                    "username2", "password",
+                    "form_field_username", "form_field_password"),
+
+      new LoginInfo("http://www2.example.com", "http://www.example.com", null,
+                    "username", "password",
+                    "form_field_username", "form_field_password"),
+      new LoginInfo("http://www2.example.com", "http://www2.example.com", null,
+                    "username", "password2",
+                    "form_field_username", "form_field_password"),
+      new LoginInfo("http://www2.example.com", "http://www2.example.com", null,
+                    "username2", "password2",
+                    "form_field_username", "form_field_password"),
+
+      new LoginInfo("http://www3.example.com", "http://www.example.com", null,
+                    "", "password",
+                    "form_field_username", "form_field_password"),
+      new LoginInfo("http://www3.example.com", "http://www3.example.com", null,
+                    "", "password2",
+                    "form_field_username", "form_field_password"),
+  ];
+}
\ No newline at end of file
--- a/toolkit/components/passwordmgr/test/unit/xpcshell.ini
+++ b/toolkit/components/passwordmgr/test/unit/xpcshell.ini
@@ -10,16 +10,17 @@ skip-if = os == "android"
 [test_module_LoginStore.js]
 skip-if = os == "android"
 
 # Test SQLite database backup and migration, applicable to Android only.
 [test_storage_mozStorage.js]
 skip-if = true || os != "android" # Bug 1171687: Needs fixing on Android
 
 # The following tests apply to any storage back-end.
+[test_context_menu.js]
 [test_disabled_hosts.js]
 [test_getFormFields.js]
 [test_getPasswordFields.js]
 [test_legacy_empty_formSubmitURL.js]
 [test_legacy_validation.js]
 [test_logins_change.js]
 [test_logins_decrypt_failure.js]
 skip-if = os == "android" # Bug 1171687: Needs fixing on Android