diff options
Diffstat (limited to 'browser/extensions/formautofill/test/mochitest')
19 files changed, 3095 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/mochitest.ini b/browser/extensions/formautofill/test/mochitest/creditCard/mochitest.ini new file mode 100644 index 0000000000..d7a7ea2fd8 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/mochitest.ini @@ -0,0 +1,26 @@ +[DEFAULT] +prefs = + extensions.formautofill.creditCards.supported=on + extensions.formautofill.creditCards.enabled=true + extensions.formautofill.reauth.enabled=true +support-files = + !/toolkit/components/satchel/test/satchel_common.js + ../../../../../../toolkit/components/satchel/test/parent_utils.js + !/toolkit/components/satchel/test/parent_utils.js + !/browser/extensions/formautofill/test/mochitest/formautofill_common.js + !/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js +skip-if = xorigin + toolkit == 'android' # bug 1730213 + +[test_basic_creditcard_autocomplete_form.html] +scheme=https +[test_clear_form.html] +scheme=https +[test_clear_form_expiry_select_elements.html] +scheme=https +[test_creditcard_autocomplete_off.html] +scheme=https +[test_preview_highlight_with_multiple_cc_number_fields.html] +scheme=https +[test_preview_highlight_with_site_prefill.html] +scheme=https diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/test_basic_creditcard_autocomplete_form.html b/browser/extensions/formautofill/test/mochitest/creditCard/test_basic_creditcard_autocomplete_form.html new file mode 100644 index 0000000000..0764bef0eb --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_basic_creditcard_autocomplete_form.html @@ -0,0 +1,251 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="../formautofill_common.js"></script> + <script type="text/javascript" src="../../../../../..//toolkit/components/satchel/test/satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: simple form credit card autofill + +<script> +"use strict"; + +const MOCK_STORAGE = [{ + "cc-name": "John Doe", + "cc-number": "4929001587121045", + "cc-exp-month": 4, + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": 12, + "cc-exp-year": 2022, +}]; + +const reducedMockRecord = { + "cc-name": "John Doe", + "cc-number": "4929001587121045", +}; + +async function setupCreditCardStorage() { + await addCreditCard(MOCK_STORAGE[0]); + await addCreditCard(MOCK_STORAGE[1]); +} + +async function setupFormHistory() { + await updateFormHistory([ + {op: "add", fieldname: "cc-name", value: "John Smith"}, + {op: "add", fieldname: "cc-exp-year", value: 2023}, + ]); +} + +initPopupListener(); + +// Form with history only. +add_task(async function history_only_menu_checking() { + // TODO: eliminate the timeout when we're able to indicate the right + // timing to start. + // + // After test process was re-spawning to https scheme. Wait 2 secs + // to ensure the environment is ready to do storage setup. + await sleep(2000); + await setupFormHistory(); + + await setInput("#cc-exp-year", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["2023"], false); +}); + +// Display credit card result even if the number of fillable fields is less than the threshold. +add_task(async function all_saved_fields_less_than_threshold() { + await addCreditCard(reducedMockRecord); + + await setInput("#cc-name", ""); + await expectPopup(); + synthesizeKey("KEY_ArrowDown"); + checkMenuEntries([reducedMockRecord].map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-name"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `Visa ${cc["cc-name"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); + + await cleanUpCreditCards(); +}); + +// Form with both history and credit card storage. +add_task(async function check_menu_when_both_existed() { + await setupCreditCardStorage(); + + await setInput("#cc-number", ""); + await expectPopup(); + synthesizeKey("KEY_ArrowDown"); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primaryAffix: cc.ccNumberFmt.affix, + primary: cc.ccNumberFmt.label, + secondary: cc["cc-name"], + ariaLabel: `${getCCTypeName(cc)} ${cc.ccNumberFmt.affix} ${cc.ccNumberFmt.label} ${cc["cc-name"]}`, + }))); + + await setInput("#cc-name", ""); + await expectPopup(); + synthesizeKey("KEY_ArrowDown"); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-name"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); + + await setInput("#cc-exp-year", ""); + await expectPopup(); + synthesizeKey("KEY_ArrowDown"); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-exp-year"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-year"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); + + await setInput("#cc-exp-month", ""); + await expectPopup(); + synthesizeKey("KEY_ArrowDown"); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-exp-month"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-month"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); + + await cleanUpCreditCards(); +}); + +// Display history search result if no matched data in credit card. +add_task(async function check_fallback_for_mismatched_field() { + await addCreditCard(reducedMockRecord); + + await setInput("#cc-exp-year", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["2023"], false); + + await cleanUpCreditCards(); +}); + +// Display history search result if credit card autofill is disabled. +add_task(async function check_search_result_for_pref_off() { + await setupCreditCardStorage(); + + await SpecialPowers.pushPrefEnv({ + set: [["extensions.formautofill.creditCards.enabled", false]], + }); + + await setInput("#cc-name", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["John Smith"], false); + + await SpecialPowers.popPrefEnv(); +}); + +let canTest; + +// Autofill the credit card from dropdown menu. +add_task(async function check_fields_after_form_autofill() { + canTest = await canTestOSKeyStoreLogin(); + if (!canTest) { + todo(canTest, "Cannot test OS key store login on official builds."); + return; + } + + await setInput("#cc-exp-year", 202); + + synthesizeKey("KEY_ArrowDown"); + // The popup doesn't auto-show on focus because the field isn't empty + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.slice(1).map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-exp-year"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-year"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); + + synthesizeKey("KEY_ArrowDown"); + let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true); + await new Promise(resolve => SimpleTest.executeSoon(resolve)); + await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]); + await osKeyStoreLoginShown; +}); + +// Fallback to history search after autofill values (for non-empty fields). +add_task(async function check_fallback_after_form_autofill() { + if (!canTest) { + return; + } + + await setInput("#cc-name", "J", true); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["John Smith"], false); +}); + +// Present credit card popup immediately when user blanks a field +add_task(async function check_cc_popup_on_field_blank() { + if (!canTest) { + return; + } + + await setInput("#cc-name", "", true); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-name"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); +}); + +// Resume form autofill once all the autofilled fileds are changed. +add_task(async function check_form_autofill_resume() { + if (!canTest) { + return; + } + + document.querySelector("#cc-name").blur(); + document.querySelector("#form1").reset(); + + await setInput("#cc-name", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-name"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1"> + <p>This is a basic form.</p> + <p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p> + <p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p> + <p><label>Expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></label></p> + <p><label>Expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></label></p> + <p><label>Card Type: <select id="cc-type" autocomplete="cc-type"> + <option value="discover">Discover</option> + <option value="jcb">JCB</option> + <option value="visa">Visa</option> + <option value="mastercard">MasterCard</option> + </select></label></p> + <p><label>CSC: <input id="cc-csc" autocomplete="cc-csc"></label></p> + </form> +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html b/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html new file mode 100644 index 0000000000..3d8049f053 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form.html @@ -0,0 +1,205 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test form autofill - clear form button</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="../formautofill_common.js"></script> + <script type="text/javascript" src="../../../../../../toolkit/components/satchel/test/satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: clear form button + +<script> +"use strict"; + +const MOCK_ADDR_STORAGE = [{ + organization: "Sesame Street", + "street-address": "2 Harrison St\nline2\nline3", + tel: "+13453453456", +}, { + organization: "Mozilla", + "street-address": "331 E. Evelyn Avenue", +}, { + organization: "Tel org", + tel: "+12223334444", +}]; +const MOCK_CC_STORAGE = [{ + "cc-name": "John Doe", + "cc-number": "4929001587121045", + "cc-exp-month": 4, + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": 12, + "cc-exp-year": 2022, +}]; + +const MOCK_CC_STORAGE_EXPECTED_FILL = [{ + "cc-name": "John Doe", + "cc-number": "4929001587121045", + "cc-exp-month": "04", + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": "12", + "cc-exp-year": 2022, +}]; + +initPopupListener(); + +add_task(async function setup_storage() { + await addAddress(MOCK_ADDR_STORAGE[0]); + await addAddress(MOCK_ADDR_STORAGE[1]); + await addAddress(MOCK_ADDR_STORAGE[2]); + + await addCreditCard(MOCK_CC_STORAGE[0]); + await addCreditCard(MOCK_CC_STORAGE[1]); +}); + + +async function checkIsFormCleared(patch = {}) { + const form = document.getElementById("form1"); + + for (const elem of form.elements) { + const expectedValue = patch[elem.id] || ""; + checkFieldValue(elem, expectedValue); + await checkFieldHighlighted(elem, false); + await checkFieldPreview(elem, ""); + } +} + +async function confirmClear(selector) { + info("Await for clearing input"); + let promise = new Promise(resolve => { + let beforeInputFired = false; + let element = document.querySelector(selector); + element.addEventListener("beforeinput", (event) => { + beforeInputFired = true; + ok(event instanceof InputEvent, + '"beforeinput" event should be dispatched with InputEvent interface'); + is(event.cancelable, SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input"), + `"beforeinput" event should be cancelable unless it's disabled by the pref`); + is(event.bubbles, true, + '"beforeinput" event should always bubble'); + is(event.inputType, "insertReplacementText", + 'inputType value of "beforeinput" should be "insertReplacementText"'); + is(event.data, "", + 'data value of "beforeinput" should be empty string'); + is(event.dataTransfer, null, + 'dataTransfer value of "beforeinput" should be null'); + is(event.getTargetRanges().length, 0, + 'getTargetRanges() of "beforeinput" event should return empty array'); + }, {once: true}); + element.addEventListener("input", (event) => { + ok(beforeInputFired, `"beforeinput" event should've been fired before "input" on <${element.tagName} type="${element.type}">`); + ok(event instanceof InputEvent, + '"input" event should be dispatched with InputEvent interface'); + is(event.cancelable, false, + '"input" event should be never cancelable'); + is(event.bubbles, true, + '"input" event should always bubble'); + is(event.inputType, "insertReplacementText", + 'inputType value of "input" should be "insertReplacementText"'); + is(event.data, "", + 'data value of "input" should be empty string'); + is(event.dataTransfer, null, + 'dataTransfer value of "input" should be null'); + is(event.getTargetRanges().length, 0, + 'getTargetRanges() of "input" should return empty array'); + resolve(); + }, {once: true}) + }); + synthesizeKey("KEY_Enter"); + await promise; +} + +add_task(async function simple_clear() { + await triggerPopupAndHoverItem("#organization", 0); + await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]); + + await triggerPopupAndHoverItem("#tel", 0); + await confirmClear("#tel"); + await checkIsFormCleared(); +}); + +add_task(async function clear_adapted_record() { + await triggerPopupAndHoverItem("#street-address", 0); + await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]); + + await triggerPopupAndHoverItem("#street-address", 0); + await confirmClear("#street-address"); + await checkIsFormCleared(); +}); + +add_task(async function clear_modified_form() { + await triggerPopupAndHoverItem("#organization", 0); + await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]); + + await setInput("#tel", "+1111111111", true); + + await triggerPopupAndHoverItem("#street-address", 0); + await confirmClear("#street-address"); + await checkIsFormCleared({tel: "+1111111111"}); +}); + +add_task(async function clear_distinct_section() { + if (!(await canTestOSKeyStoreLogin())) { + todo(false, "Cannot test OS key store login on official builds."); + return; + } + + document.getElementById("form1").reset(); + await triggerPopupAndHoverItem("#cc-name", 0); + let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true); + await triggerAutofillAndCheckProfile(MOCK_CC_STORAGE_EXPECTED_FILL[0]); + await osKeyStoreLoginShown; + + await triggerPopupAndHoverItem("#organization", 0); + await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]); + await triggerPopupAndHoverItem("#street-address", 0); + await confirmClear("#street-address"); + + for (const [id, val] of Object.entries(MOCK_CC_STORAGE_EXPECTED_FILL[0])) { + const element = document.getElementById(id); + if (!element) { + return; + } + checkFieldValue(element, val); + await checkFieldHighlighted(element, true); + } + + await triggerPopupAndHoverItem("#cc-name", 0); + await confirmClear("#cc-name"); + await checkIsFormCleared(); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1"> + <p>This is a basic form.</p> + <p><label>organization: <input id="organization" autocomplete="organization"></label></p> + <p><label>streetAddress: <input id="street-address" autocomplete="street-address"></label></p> + <p><label>tel: <input id="tel" autocomplete="tel"></label></p> + <p><label>country: <input id="country" autocomplete="country"></label></p> + + <p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p> + <p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p> + <p><label>Expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></label></p> + <p><label>Expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></label></p> + <p><label>CSC: <input id="cc-csc" autocomplete="cc-csc"></label></p> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form_expiry_select_elements.html b/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form_expiry_select_elements.html new file mode 100644 index 0000000000..4fc989a36e --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_clear_form_expiry_select_elements.html @@ -0,0 +1,211 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test form autofill - clear form button with select elements</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="../formautofill_common.js"></script> + <script type="text/javascript" src="../../../../../../toolkit/components/satchel/test/satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: clear form button with select elements. + +<script> +"use strict"; +const MOCK_ADDR_STORAGE = [{ + organization: "Sesame Street", + "street-address": "2 Harrison St\nline2\nline3", + tel: "+13453453456", +}, { + organization: "Mozilla", + "street-address": "331 E. Evelyn Avenue", +}, { + organization: "Tel org", + tel: "+12223334444", +}]; + +const MOCK_CC_STORAGE = [{ + "cc-name": "John Doe", + "cc-number": "4929001587121045", + "cc-exp-month": 4, + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": 12, + "cc-exp-year": 2022, +}]; + +initPopupListener(); + +add_task(async function setup_storage() { + await addAddress(MOCK_ADDR_STORAGE[0]); + await addCreditCard(MOCK_CC_STORAGE[0]); + await addCreditCard(MOCK_CC_STORAGE[1]); +}); + + +async function checkIsFormCleared(patch = {}) { + const form = document.getElementById("form1"); + + for (const elem of form.elements) { + const expectedValue = patch[elem.id] || ""; + checkFieldValue(elem, expectedValue); + await checkFieldHighlighted(elem, false); + await checkFieldPreview(elem, ""); + } +} + +async function confirmClear(selector) { + info("Await for clearing input"); + let promise = new Promise(resolve => { + let beforeInputFired = false; + let element = document.querySelector(selector); + info(`Which element are we clearing? ${element.id}`); + element.addEventListener("beforeinput", (event) => { + beforeInputFired = true; + ok(event instanceof InputEvent, + '"beforeinput" event should be dispatched with InputEvent interface'); + is(event.cancelable, SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input"), + `"beforeinput" event should be cancelable unless it's disabled by the pref`); + is(event.bubbles, true, + '"beforeinput" event should always bubble'); + is(event.inputType, "insertReplacementText", + 'inputType value of "beforeinput" should be "insertReplacementText"'); + is(event.data, "", + 'data value of "beforeinput" should be empty string'); + is(event.dataTransfer, null, + 'dataTransfer value of "beforeinput" should be null'); + is(event.getTargetRanges().length, 0, + 'getTargetRanges() of "beforeinput" event should return empty array'); + }, {once: true}); + element.addEventListener("input", (event) => { + ok(beforeInputFired, `"beforeinput" event should've been fired before "input" on <${element.tagName} type="${element.type}">`); + ok(event instanceof InputEvent, + '"input" event should be dispatched with InputEvent interface'); + is(event.cancelable, false, + '"input" event should be never cancelable'); + is(event.bubbles, true, + '"input" event should always bubble'); + is(event.inputType, "insertReplacementText", + 'inputType value of "input" should be "insertReplacementText"'); + is(event.data, "", + 'data value of "input" should be empty string'); + is(event.dataTransfer, null, + 'dataTransfer value of "input" should be null'); + is(event.getTargetRanges().length, 0, + 'getTargetRanges() of "input" should return empty array'); + resolve(); + }, {once: true}) + }); + synthesizeKey("KEY_Enter"); + await promise; +} + +// tgiles: We need this task due to timing issues between focusAndWaitForFieldsIdentified and popupShownListener. +// There's a 300ms delay in focusAndWaitForFieldsIdentified that can cause triggerPopupAndHoverItem to get out of sync +// and cause the popup to appear before the test expects a popup to appear. + +// Without this task we end up either getting a consistent timeout or getting the following exception: +// 0:20.55 GECKO(31108) JavaScript error: , line 0: uncaught exception: Checking selected index - timed out after 50 tries. +// This exception appears if you attempt to create the expectPopup promise earlier than it currently is in triggetPopupAndHoverItem +add_task(async function a_dummy_task() { + await triggerPopupAndHoverItem("#organization", 0); + await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]); + + await triggerPopupAndHoverItem("#tel", 0); + await confirmClear("#tel"); + await checkIsFormCleared({ + "cc-exp-month": "MM", + "cc-exp-year": "YY" + }); +}); + +add_task(async function clear_distinct_section() { + if (!(await canTestOSKeyStoreLogin())) { + todo(false, "Cannot test OS key store login on official builds."); + return; + } + let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true); + await triggerPopupAndHoverItem("#cc-name", 0); + await triggerAutofillAndCheckProfile(MOCK_CC_STORAGE[0]); + await osKeyStoreLoginShown; + + for (const [id, val] of Object.entries(MOCK_CC_STORAGE[0])) { + const element = document.getElementById(id); + if (!element) { + return; + } + checkFieldValue(element, val); + await checkFieldHighlighted(element, true); + } + + await triggerPopupAndHoverItem("#cc-name", 0); + await confirmClear("#cc-name"); + await checkIsFormCleared({ + "cc-exp-month": "MM", + "cc-exp-year": "YY" + }); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1"> + <p>This is a basic form.</p> + <p><label>organization: <input id="organization" autocomplete="organization"></label></p> + <p><label>streetAddress: <input id="street-address" autocomplete="street-address"></label></p> + <p><label>tel: <input id="tel" autocomplete="tel"></label></p> + <p><label>country: <input id="country" autocomplete="country"></label></p> + + <p><label>Name: <input id="cc-name" autocomplete="cc-name"></label></p> + <p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p> + <!-- NOTE: If you're going to write a test like this, + ensure that the selected option doesn't match the data that you're trying to autofill, + otherwise your test will wait forever for an event that will never fire. + I.e, if your saved cc-exp-month is 01, make sure your selected option ISN'T 01. + --> + <p><label>Expiration month: <select id="cc-exp-month" autocomplete="cc-exp-month"> + <option value="MM" selected>MM</option> + <option value="1">01</option> + <option value="2">02</option> + <option value="3">03</option> + <option value="4">04</option> + <option value="5">05</option> + <option value="6">06</option> + <option value="7">07</option> + <option value="8">08</option> + <option value="9">09</option> + <option value="10">10</option> + <option value="11">11</option> + <option value="12">12</option> + </select> + </label></p> + <!-- NOTE: If you're going to write a test like this, + ensure that the selected option doesn't match the data that you're trying to autofill, + otherwise your test will wait forever for an event that will never fire. + I.e, if your saved cc-exp-year is 2017, make sure your selected option ISN'T 2017. + --> + <p><label>Expiration year: <select id="cc-exp-year" autocomplete="cc-exp-year"> + <option value="YY" selected>YY</option> + <option value="2017">2017</option> + <option value="2018">2018</option> + <option value="2019">2019</option> + <option value="2020">2020</option> + <option value="2021">2021</option> + <option value="2022">2022</option> + </select> + </label></p> + <p><label>CSC: <input id="cc-csc" autocomplete="cc-csc"></label></p> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/test_creditcard_autocomplete_off.html b/browser/extensions/formautofill/test/mochitest/creditCard/test_creditcard_autocomplete_off.html new file mode 100644 index 0000000000..225d828ccb --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_creditcard_autocomplete_off.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="../formautofill_common.js"></script> + <script type="text/javascript" src="../../../../../../toolkit/components/satchel/test/satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: simple form credit card autofill + +<script> +"use strict"; + +const MOCK_STORAGE = [{ + "cc-name": "John Doe", + "cc-number": "4929001587121045", + "cc-exp-month": 4, + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": 12, + "cc-exp-year": 2022, +}]; + +async function setupCreditCardStorage() { + await addCreditCard(MOCK_STORAGE[0]); + await addCreditCard(MOCK_STORAGE[1]); +} + +async function setupFormHistory() { + await updateFormHistory([ + {op: "add", fieldname: "cc-name", value: "John Smith"}, + {op: "add", fieldname: "cc-number", value: "6011029476355493"}, + ]); +} + +initPopupListener(); + +// Show Form History popup for non-autocomplete="off" field only +add_task(async function history_only_menu_checking() { + await setupFormHistory(); + + await setInput("#cc-number", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["6011029476355493"], false); + + await setInput("#cc-name", ""); + synthesizeKey("KEY_ArrowDown"); + await notExpectPopup(); +}); + +// Show Form Autofill popup for the credit card fields. +add_task(async function check_menu_when_both_with_autocomplete_off() { + await setupCreditCardStorage(); + + await setInput("#cc-number", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primaryAffix: cc.ccNumberFmt.affix, + primary: cc.ccNumberFmt.label, + secondary: cc["cc-name"], + ariaLabel: `${getCCTypeName(cc)} ${cc.ccNumberFmt.affix} ${cc.ccNumberFmt.label} ${cc["cc-name"]}`, + }))); + + await setInput("#cc-name", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({ + primary: cc["cc-name"], + secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label, + ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt.affix}${cc.ccNumberFmt.label}`, + }))); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + <form id="form1"> + <p>This is a Credit Card form with autocomplete="off" cc-name field.</p> + <p><label>Name: <input id="cc-name" autocomplete="off"></label></p> + <p><label>Card Number: <input id="cc-number" autocomplete="cc-number"></label></p> + </form> +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/test_preview_highlight_with_multiple_cc_number_fields.html b/browser/extensions/formautofill/test/mochitest/creditCard/test_preview_highlight_with_multiple_cc_number_fields.html new file mode 100644 index 0000000000..c39877d1b7 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_preview_highlight_with_multiple_cc_number_fields.html @@ -0,0 +1,174 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test form autofill - preview and highlight with multiple cc number fields</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="../formautofill_common.js"></script> + <script type="text/javascript" src="../../../../../../toolkit/components/satchel/test/satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: preview and highlight multiple cc number fields + +<script> +"use strict"; + +const MOCK_STORAGE = [{ + "cc-name": "Test Name", + "cc-number": "4929001587121045", + "cc-exp-month": 4, + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": 12, + "cc-exp-year": 2022, +}]; + +const MOCK_STORAGE_EXPECTED_FILL = [{ + "cc-name": "Test Name", + "cc-number": "4929001587121045", + "cc-exp-month": "04", + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": "12", + "cc-exp-year": 2022, +}] + +const MOCK_STORAGE_PREVIEW = [{ + "cc-name": "Test Name", + "cc-number": "************1045", + "cc-exp-month": "04", + "cc-exp-year": "2017", +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "************7870", + "cc-exp-month": "12", + "cc-exp-year": "2022", +}]; + + +/* + This function is similar to checkFormFieldsStyle in formautofill_common.js, but deals with the case + when one value is spread across multiple fields. + + This function is needed because of the multiple cc-number filling behavior introduced in Bug 1688607. + Since the cc-number is stored as a whole value in the profile, + there has to be specific handling in the test to assert the correct fillable value. + Otherwise, we would try to grab a value out of profile["cc-number1"] which doesn't exist. +*/ +async function checkMultipleCCNumberFormStyle(profile, isPreviewing = true) { + const elements = document.querySelectorAll("input, select"); + for (const element of elements) { + let fillableValue; + if (element.id.includes("cc-number") && isPreviewing) { + fillableValue = profile["cc-number"].slice(-8); + } else if (element.id.includes("cc-number")) { + fillableValue = profile["cc-number"]; + } else { + fillableValue = profile[element.id]; + } + let previewValue = (isPreviewing && fillableValue) || ""; + await checkFieldHighlighted(element, !!fillableValue); + await checkFieldPreview(element, previewValue); + + } +} + +/* + This function sets up 'change' event listeners so that we can safely + assert an element's value after autofilling has occurred. + This is essentially a barebones copy of triggerAutofillAndCheckProfile + that exists in formautofill_common.js. + + We can't use triggerAutofillAndCheckProfile because "cc-number1" through "cc-number4" + do not exist in the profile. + Again, we store the whole cc-number in the profile, not its subsections. + So if we tried to grab the element by ID using "cc-number", this element would not exist in the doc, + causing triggerAutofillAndCheckProfile to throw an exception. +*/ +async function setupListeners(elements, profile) { + for (const element of elements) { + let id = element.id; + element.addEventListener("change", () => { + let filledValue; + if (id == "cc-number1") { + filledValue = profile["cc-number"].slice(0, 4); + } else if (id == "cc-number2") { + filledValue = profile["cc-number"].slice(4, 8); + } else if (id == "cc-number3") { + filledValue = profile["cc-number"].slice(8, 12); + } else if (id == "cc-number4") { + filledValue = profile["cc-number"].slice(12, 16); + } else { + filledValue = profile[element.id]; + } + checkFieldValue(element, filledValue); + }, {once: true}) + } + +} + +initPopupListener(); + +add_task(async function setup_storage() { + await addCreditCard(MOCK_STORAGE[0]); + await addCreditCard(MOCK_STORAGE[1]); +}); + +add_task(async function check_preview() { + let canTest = await canTestOSKeyStoreLogin(); + if (!canTest) { + todo(canTest, "Cannot test OS key store login on official builds."); + return; + } + let popup = expectPopup(); + const focusedInput = await setInput("#cc-name", ""); + await popup; + for (let i = 0; i < MOCK_STORAGE_PREVIEW.length; i++) { + synthesizeKey("KEY_ArrowDown"); + await notifySelectedIndex(i); + await checkMultipleCCNumberFormStyle(MOCK_STORAGE_PREVIEW[i]); + } + + focusedInput.blur(); +}); + +add_task(async function check_filled_highlight() { + let canTest = await canTestOSKeyStoreLogin(); + if (!canTest) { + todo(canTest, "Cannot test OS key store login on official builds."); + return; + } + await triggerPopupAndHoverItem("#cc-name", 0); + let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true); + // filled 1st credit card option + synthesizeKey("KEY_Enter"); + await osKeyStoreLoginShown; + let elements = document.querySelectorAll("input, select"); + let profile = MOCK_STORAGE_EXPECTED_FILL[0]; + await setupListeners(elements, profile); + await checkMultipleCCNumberFormStyle(profile, false); +}); +</script> +<p id="display"></p> +<div id="content"> + + <form id="form1"> + <p>This is a basic credit card form.</p> + <p>card number subsection 1: <input id="cc-number1" maxlength="4"></p> + <p>card number subsection 2: <input id="cc-number2" maxlength="4"></p> + <p>card number subsection 3: <input id="cc-number3" maxlength="4"></p> + <p>card number subsection 4: <input id="cc-number4" maxlength="4"></p> + <p>cardholder name: <input id="cc-name" autocomplete="cc-name"></p> + <p>expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></p> + <p>expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></p> + </form> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/creditCard/test_preview_highlight_with_site_prefill.html b/browser/extensions/formautofill/test/mochitest/creditCard/test_preview_highlight_with_site_prefill.html new file mode 100644 index 0000000000..090eb9290e --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/creditCard/test_preview_highlight_with_site_prefill.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test form autofill - preview and highlight with site prefill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="../formautofill_common.js"></script> + <script type="text/javascript" src="../../../../../../toolkit/components/satchel/test/satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: preview and highlight field that has been filled by site + +<script> +"use strict"; + +const MOCK_STORAGE = [{ + "cc-name": "Test Name", + "cc-number": "4929001587121045", + "cc-exp-month": 4, + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": 12, + "cc-exp-year": 2022, +}]; + +const MOCK_STORAGE_PREVIEW = [{ + "cc-name": "Test Name", + "cc-number": "************1045", + "cc-exp-month": "04", + "cc-exp-year": "2017", +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "************7870", + "cc-exp-month": "12", + "cc-exp-year": "2022", +}]; + +const MOCK_STORAGE_EXPECTED_FILL = [{ + "cc-name": "Test Name", + "cc-number": "4929001587121045", + "cc-exp-month": "04", + "cc-exp-year": 2017, +}, { + "cc-name": "Timothy Berners-Lee", + "cc-number": "5103059495477870", + "cc-exp-month": "12", + "cc-exp-year": 2022, +}]; + +initPopupListener(); + +add_task(async function setup_storage() { + await addCreditCard(MOCK_STORAGE[0]); + await addCreditCard(MOCK_STORAGE[1]); +}); + +add_task(async function check_preview() { + let canTest = await canTestOSKeyStoreLogin(); + if (!canTest) { + todo(canTest, "Cannot test OS key store login on official builds."); + return; + } + + let cardholderName = document.querySelector("#cc-name"); + let sitePrefillValue = cardholderName.value; + let popup = expectPopup(); + const focusedInput = await setInput("#cc-number", ""); + await popup; + for (let i = 0; i < MOCK_STORAGE_PREVIEW.length; i++) { + synthesizeKey("KEY_ArrowDown"); + await notifySelectedIndex(i); + await checkFormFieldsStyle(MOCK_STORAGE_PREVIEW[i]); + } + + focusedInput.blur(); + is(cardholderName.value, sitePrefillValue, "value should not have changed because previous value was a site prefill"); +}); + +add_task(async function check_filled_highlight() { + let canTest = await canTestOSKeyStoreLogin(); + if (!canTest) { + todo(canTest, "Cannot test OS key store login on official builds."); + return; + } + await triggerPopupAndHoverItem("#cc-number", 0); + let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true); + // filled 1st credit card option + await triggerAutofillAndCheckProfile(MOCK_STORAGE_EXPECTED_FILL[0]); + await osKeyStoreLoginShown; + await checkFormFieldsStyle(MOCK_STORAGE_EXPECTED_FILL[0], false); +}); +</script> +<p id="display"></p> +<div id="content"> + + <form id="form1"> + <p>This is a basic credit card form.</p> + <p>card number: <input id="cc-number" autocomplete="cc-number"></p> + <p>cardholder name: <input id="cc-name" autocomplete="cc-name" value="JOHN DOE"></p> + <p>expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></p> + <p>expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></p> + </form> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/formautofill_common.js b/browser/extensions/formautofill/test/mochitest/formautofill_common.js new file mode 100644 index 0000000000..d65f0beb49 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js @@ -0,0 +1,478 @@ +/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */ +/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/EventUtils.js */ +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ +/* eslint-disable no-unused-vars */ + +"use strict"; + +let formFillChromeScript; +let defaultTextColor; +let defaultDisabledTextColor; +let expectingPopup = null; + +const { FormAutofillUtils } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/shared/FormAutofillUtils.sys.mjs" +); + +async function sleep(ms = 500, reason = "Intentionally wait for UI ready") { + SimpleTest.requestFlakyTimeout(reason); + await new Promise(resolve => setTimeout(resolve, ms)); +} + +async function focusAndWaitForFieldsIdentified( + input, + mustBeIdentified = false +) { + info("expecting the target input being focused and indentified"); + if (typeof input === "string") { + input = document.querySelector(input); + } + const rootElement = input.form || input.ownerDocument.documentElement; + const previouslyFocused = input != document.activeElement; + + input.focus(); + + if (mustBeIdentified) { + rootElement.removeAttribute("test-formautofill-identified"); + } + if (rootElement.hasAttribute("test-formautofill-identified")) { + return; + } + if (!previouslyFocused) { + await new Promise(resolve => { + formFillChromeScript.addMessageListener( + "FormAutofillTest:FieldsIdentified", + function onIdentified() { + formFillChromeScript.removeMessageListener( + "FormAutofillTest:FieldsIdentified", + onIdentified + ); + resolve(); + } + ); + }); + } + // In order to ensure that "markAsAutofillField" is fully executed, a short period + // of timeout is still required. + await sleep(300, "Guarantee asynchronous identifyAutofillFields is invoked"); + rootElement.setAttribute("test-formautofill-identified", "true"); +} + +async function setInput(selector, value, userInput = false) { + const input = document.querySelector("input" + selector); + if (userInput) { + SpecialPowers.wrap(input).setUserInput(value); + } else { + input.value = value; + } + await focusAndWaitForFieldsIdentified(input); + + return input; +} + +function clickOnElement(selector) { + let element = document.querySelector(selector); + + if (!element) { + throw new Error("Can not find the element"); + } + + SimpleTest.executeSoon(() => element.click()); +} + +// The equivalent helper function to getAdaptedProfiles in FormAutofillHandler.jsm that +// transforms the given profile to expected filled profile. +function _getAdaptedProfile(profile) { + const adaptedProfile = Object.assign({}, profile); + + if (profile["street-address"]) { + adaptedProfile["street-address"] = FormAutofillUtils.toOneLineAddress( + profile["street-address"] + ); + } + + return adaptedProfile; +} + +async function checkFieldHighlighted(elem, expectedValue) { + let isHighlightApplied; + await SimpleTest.promiseWaitForCondition(function checkHighlight() { + isHighlightApplied = elem.matches(":autofill"); + return isHighlightApplied === expectedValue; + }, `Checking #${elem.id} highlight style`); + + is(isHighlightApplied, expectedValue, `Checking #${elem.id} highlight style`); +} + +async function checkFieldPreview(elem, expectedValue) { + is( + SpecialPowers.wrap(elem).previewValue, + expectedValue, + `Checking #${elem.id} previewValue` + ); + let isTextColorApplied; + await SimpleTest.promiseWaitForCondition(function checkPreview() { + const computedStyle = window.getComputedStyle(elem); + const actualColor = computedStyle.getPropertyValue("color"); + if (elem.disabled) { + isTextColorApplied = actualColor !== defaultDisabledTextColor; + } else { + isTextColorApplied = actualColor !== defaultTextColor; + } + return isTextColorApplied === !!expectedValue; + }, `Checking #${elem.id} preview style`); + + is(isTextColorApplied, !!expectedValue, `Checking #${elem.id} preview style`); +} + +async function checkFormFieldsStyle(profile, isPreviewing = true) { + const elems = document.querySelectorAll("input, select"); + + for (const elem of elems) { + let fillableValue; + let previewValue; + let isElementEligible = + FormAutofillUtils.isCreditCardOrAddressFieldType(elem) && + FormAutofillUtils.isFieldAutofillable(elem); + if (!isElementEligible) { + fillableValue = ""; + previewValue = ""; + } else { + fillableValue = profile && profile[elem.id]; + previewValue = (isPreviewing && fillableValue) || ""; + } + await checkFieldHighlighted(elem, !!fillableValue); + await checkFieldPreview(elem, previewValue); + } +} + +function checkFieldValue(elem, expectedValue) { + if (typeof elem === "string") { + elem = document.querySelector(elem); + } + is(elem.value, String(expectedValue), "Checking " + elem.id + " field"); +} + +async function triggerAutofillAndCheckProfile(profile) { + let adaptedProfile = _getAdaptedProfile(profile); + const promises = []; + for (const [fieldName, value] of Object.entries(adaptedProfile)) { + info(`triggerAutofillAndCheckProfile: ${fieldName}`); + const element = document.getElementById(fieldName); + const expectingEvent = + document.activeElement == element ? "input" : "change"; + const checkFieldAutofilled = Promise.all([ + new Promise(resolve => { + let beforeInputFired = false; + let hadEditor = SpecialPowers.wrap(element).hasEditor; + element.addEventListener( + "beforeinput", + event => { + beforeInputFired = true; + is( + event.inputType, + "insertReplacementText", + 'inputType value should be "insertReplacementText"' + ); + is( + event.data, + String(value), + `data value of "beforeinput" should be "${value}"` + ); + is( + event.dataTransfer, + null, + 'dataTransfer of "beforeinput" should be null' + ); + is( + event.getTargetRanges().length, + 0, + 'getTargetRanges() of "beforeinput" should return empty array' + ); + is( + event.cancelable, + SpecialPowers.getBoolPref( + "dom.input_event.allow_to_cancel_set_user_input" + ), + `"beforeinput" event should be cancelable on ${element.tagName} unless it's suppressed by the pref` + ); + is( + event.bubbles, + true, + `"beforeinput" event should always bubble on ${element.tagName}` + ); + resolve(); + }, + { once: true } + ); + element.addEventListener( + "input", + event => { + if (element.tagName == "INPUT" && element.type == "text") { + if (hadEditor) { + ok( + beforeInputFired, + `"beforeinput" event should've been fired before "input" event on ${element.tagName}` + ); + } else { + ok( + beforeInputFired, + `"beforeinput" event should've been fired before "input" event on ${element.tagName}` + ); + } + ok( + event instanceof InputEvent, + `"input" event should be dispatched with InputEvent interface on ${element.tagName}` + ); + is( + event.inputType, + "insertReplacementText", + 'inputType value should be "insertReplacementText"' + ); + is(event.data, String(value), `data value should be "${value}"`); + is(event.dataTransfer, null, "dataTransfer should be null"); + is( + event.getTargetRanges().length, + 0, + "getTargetRanges() should return empty array" + ); + } else { + ok( + !beforeInputFired, + `"beforeinput" event shouldn't be fired on ${element.tagName}` + ); + ok( + event instanceof Event && !(event instanceof UIEvent), + `"input" event should be dispatched with Event interface on ${element.tagName}` + ); + } + is( + event.cancelable, + false, + `"input" event should be never cancelable on ${element.tagName}` + ); + is( + event.bubbles, + true, + `"input" event should always bubble on ${element.tagName}` + ); + resolve(); + }, + { once: true } + ); + }), + new Promise(resolve => + element.addEventListener(expectingEvent, resolve, { once: true }) + ), + ]).then(() => checkFieldValue(element, value)); + + promises.push(checkFieldAutofilled); + } + // Press Enter key and trigger form autofill. + synthesizeKey("KEY_Enter"); + + return Promise.all(promises); +} + +async function onStorageChanged(type) { + info(`expecting the storage changed: ${type}`); + return new Promise(resolve => { + formFillChromeScript.addMessageListener( + "formautofill-storage-changed", + function onChanged(data) { + formFillChromeScript.removeMessageListener( + "formautofill-storage-changed", + onChanged + ); + is(data.data, type, `Receive ${type} storage changed event`); + resolve(); + } + ); + }); +} + +function checkMenuEntries(expectedValues, isFormAutofillResult = true) { + let actualValues = getMenuEntries(); + // Expect one more item would appear at the bottom as the footer if the result is from form autofill. + let expectedLength = isFormAutofillResult + ? expectedValues.length + 1 + : expectedValues.length; + + is(actualValues.length, expectedLength, " Checking length of expected menu"); + for (let i = 0; i < expectedValues.length; i++) { + is(actualValues[i], expectedValues[i], " Checking menu entry #" + i); + } +} + +function invokeAsyncChromeTask(message, payload = {}) { + info(`expecting the chrome task finished: ${message}`); + return formFillChromeScript.sendQuery(message, payload); +} + +async function addAddress(address) { + await invokeAsyncChromeTask("FormAutofillTest:AddAddress", { address }); + await sleep(); +} + +async function removeAddress(guid) { + return invokeAsyncChromeTask("FormAutofillTest:RemoveAddress", { guid }); +} + +async function updateAddress(guid, address) { + return invokeAsyncChromeTask("FormAutofillTest:UpdateAddress", { + address, + guid, + }); +} + +async function checkAddresses(expectedAddresses) { + return invokeAsyncChromeTask("FormAutofillTest:CheckAddresses", { + expectedAddresses, + }); +} + +async function cleanUpAddresses() { + return invokeAsyncChromeTask("FormAutofillTest:CleanUpAddresses"); +} + +async function addCreditCard(creditcard) { + await invokeAsyncChromeTask("FormAutofillTest:AddCreditCard", { creditcard }); + await sleep(); +} + +async function removeCreditCard(guid) { + return invokeAsyncChromeTask("FormAutofillTest:RemoveCreditCard", { guid }); +} + +async function checkCreditCards(expectedCreditCards) { + return invokeAsyncChromeTask("FormAutofillTest:CheckCreditCards", { + expectedCreditCards, + }); +} + +async function cleanUpCreditCards() { + return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards"); +} + +async function cleanUpStorage() { + await cleanUpAddresses(); + await cleanUpCreditCards(); +} + +async function canTestOSKeyStoreLogin() { + let { canTest } = await invokeAsyncChromeTask( + "FormAutofillTest:CanTestOSKeyStoreLogin" + ); + return canTest; +} + +async function waitForOSKeyStoreLogin(login = false) { + await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", { login }); +} + +function patchRecordCCNumber(record) { + const number = record["cc-number"]; + const ccNumberFmt = { + affix: "****", + label: number.substr(-4), + }; + + return Object.assign({}, record, { ccNumberFmt }); +} + +// Utils for registerPopupShownListener(in satchel_common.js) that handles dropdown popup +// Please call "initPopupListener()" in your test and "await expectPopup()" +// if you want to wait for dropdown menu displayed. +function expectPopup() { + info("expecting a popup"); + return new Promise(resolve => { + expectingPopup = resolve; + }); +} + +function notExpectPopup(ms = 500) { + info("not expecting a popup"); + return new Promise((resolve, reject) => { + expectingPopup = reject.bind(this, "Unexpected Popup"); + // TODO: We don't have an event to notify no popup showing, so wait for 500 + // ms (in default) to predict any unexpected popup showing. + setTimeout(resolve, ms); + }); +} + +function popupShownListener() { + info("popup shown for test "); + if (expectingPopup) { + expectingPopup(); + expectingPopup = null; + } +} + +function initPopupListener() { + registerPopupShownListener(popupShownListener); +} + +async function triggerPopupAndHoverItem(fieldSelector, selectIndex) { + await focusAndWaitForFieldsIdentified(fieldSelector); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + for (let i = 0; i <= selectIndex; i++) { + synthesizeKey("KEY_ArrowDown"); + } + await notifySelectedIndex(selectIndex); +} + +function formAutoFillCommonSetup() { + // Remove the /creditCard path segement when referenced from the 'creditCard' subdirectory. + let chromeURL = SimpleTest.getTestFileURL( + "formautofill_parent_utils.js" + ).replace(/\/creditCard/, ""); + formFillChromeScript = SpecialPowers.loadChromeScript(chromeURL); + formFillChromeScript.addMessageListener("onpopupshown", ({ results }) => { + gLastAutoCompleteResults = results; + if (gPopupShownListener) { + gPopupShownListener({ results }); + } + }); + + add_setup(async () => { + info(`expecting the storage setup`); + await formFillChromeScript.sendQuery("setup"); + }); + + SimpleTest.registerCleanupFunction(async () => { + info(`expecting the storage cleanup`); + await formFillChromeScript.sendQuery("cleanup"); + + formFillChromeScript.destroy(); + expectingPopup = null; + }); + + document.addEventListener( + "DOMContentLoaded", + function () { + defaultTextColor = window + .getComputedStyle(document.querySelector("input")) + .getPropertyValue("color"); + + // This is needed for test_formautofill_preview_highlight.html to work properly + let disabledInput = document.querySelector(`input[disabled]`); + if (disabledInput) { + defaultDisabledTextColor = window + .getComputedStyle(disabledInput) + .getPropertyValue("color"); + } + }, + { once: true } + ); +} + +/* + * Extremely over-simplified detection of card type from card number just for + * our tests. This is needed to test the aria-label of credit card menu entries. + */ +function getCCTypeName(creditCard) { + return creditCard["cc-number"][0] == "4" ? "Visa" : "MasterCard"; +} + +formAutoFillCommonSetup(); diff --git a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js new file mode 100644 index 0000000000..0d9af772b5 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js @@ -0,0 +1,304 @@ +/* eslint-env mozilla/chrome-script */ + +"use strict"; + +const { FormAutofill } = ChromeUtils.importESModule( + "resource://autofill/FormAutofill.sys.mjs" +); +const { FormAutofillUtils } = ChromeUtils.importESModule( + "resource://gre/modules/shared/FormAutofillUtils.sys.mjs" +); +const { OSKeyStoreTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/OSKeyStoreTestUtils.sys.mjs" +); + +let { formAutofillStorage } = ChromeUtils.importESModule( + "resource://autofill/FormAutofillStorage.sys.mjs" +); + +const { ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME } = + FormAutofillUtils; + +let destroyed = false; + +var ParentUtils = { + getFormAutofillActor() { + let win = Services.wm.getMostRecentWindow("navigator:browser"); + let selectedBrowser = win.gBrowser.selectedBrowser; + return selectedBrowser.browsingContext.currentWindowGlobal.getActor( + "FormAutofill" + ); + }, + + _getRecords(collectionName) { + return this.getFormAutofillActor().receiveMessage({ + name: "FormAutofill:GetRecords", + data: { + searchString: "", + collectionName, + }, + }); + }, + + async _storageChangeObserved({ + topic = "formautofill-storage-changed", + type, + times = 1, + }) { + let count = times; + + return new Promise(resolve => { + Services.obs.addObserver(function observer(subject, obsTopic, data) { + if ((type && data != type) || !!--count) { + return; + } + + // every notification type should have the collection name. + // We're not allowed to trigger assertions during mochitest + // cleanup functions. + if (!destroyed) { + let allowedNames = [ + ADDRESSES_COLLECTION_NAME, + CREDITCARDS_COLLECTION_NAME, + ]; + assert.ok( + allowedNames.includes(subject.wrappedJSObject.collectionName), + "should include the collection name" + ); + // every notification except removeAll should have a guid. + if (data != "removeAll") { + assert.ok(subject.wrappedJSObject.guid, "should have a guid"); + } + } + Services.obs.removeObserver(observer, obsTopic); + resolve(); + }, topic); + }); + }, + + async _operateRecord(collectionName, type, msgData) { + let msgName, times, topic; + + if (collectionName == ADDRESSES_COLLECTION_NAME) { + switch (type) { + case "add": { + msgName = "FormAutofill:SaveAddress"; + break; + } + case "update": { + msgName = "FormAutofill:SaveAddress"; + break; + } + case "remove": { + msgName = "FormAutofill:RemoveAddresses"; + times = msgData.guids.length; + break; + } + default: + return; + } + } else { + switch (type) { + case "add": { + msgData = Object.assign({}, msgData); + msgName = "FormAutofill:SaveCreditCard"; + break; + } + case "remove": { + msgName = "FormAutofill:RemoveCreditCards"; + times = msgData.guids.length; + break; + } + default: + return; + } + } + + let storageChangePromise = this._storageChangeObserved({ + type, + times, + topic, + }); + this.getFormAutofillActor().receiveMessage({ + name: msgName, + data: msgData, + }); + await storageChangePromise; + }, + + async operateAddress(type, msgData) { + await this._operateRecord(ADDRESSES_COLLECTION_NAME, ...arguments); + }, + + async operateCreditCard(type, msgData) { + await this._operateRecord(CREDITCARDS_COLLECTION_NAME, ...arguments); + }, + + async cleanUpAddresses() { + const guids = (await this._getRecords(ADDRESSES_COLLECTION_NAME)).map( + record => record.guid + ); + + if (!guids.length) { + return; + } + + await this.operateAddress( + "remove", + { guids }, + "FormAutofillTest:AddressesCleanedUp" + ); + }, + + async cleanUpCreditCards() { + if (!FormAutofill.isAutofillCreditCardsAvailable) { + return; + } + const guids = (await this._getRecords(CREDITCARDS_COLLECTION_NAME)).map( + record => record.guid + ); + + if (!guids.length) { + return; + } + + await this.operateCreditCard( + "remove", + { guids }, + "FormAutofillTest:CreditCardsCleanedUp" + ); + }, + + setup() { + OSKeyStoreTestUtils.setup(); + }, + + async cleanup() { + await this.cleanUpAddresses(); + await this.cleanUpCreditCards(); + await OSKeyStoreTestUtils.cleanup(); + + Services.obs.removeObserver(this, "formautofill-storage-changed"); + }, + + _areRecordsMatching(recordA, recordB, collectionName) { + for (let field of formAutofillStorage[collectionName].VALID_FIELDS) { + if (recordA[field] !== recordB[field]) { + return false; + } + } + // Check the internal field if both addresses have valid value. + for (let field of formAutofillStorage.INTERNAL_FIELDS) { + if ( + field in recordA && + field in recordB && + recordA[field] !== recordB[field] + ) { + return false; + } + } + return true; + }, + + async _checkRecords(collectionName, expectedRecords) { + const records = await this._getRecords(collectionName); + + if (records.length !== expectedRecords.length) { + return false; + } + + for (let record of records) { + let matching = expectedRecords.some(expectedRecord => { + return ParentUtils._areRecordsMatching( + record, + expectedRecord, + collectionName + ); + }); + + if (!matching) { + return false; + } + } + + return true; + }, + + async checkAddresses({ expectedAddresses }) { + return this._checkRecords(ADDRESSES_COLLECTION_NAME, expectedAddresses); + }, + + async checkCreditCards({ expectedCreditCards }) { + return this._checkRecords(CREDITCARDS_COLLECTION_NAME, expectedCreditCards); + }, + + observe(subject, topic, data) { + if (!destroyed) { + assert.ok(topic === "formautofill-storage-changed"); + } + sendAsyncMessage("formautofill-storage-changed", { + subject: null, + topic, + data, + }); + }, +}; + +Services.obs.addObserver(ParentUtils, "formautofill-storage-changed"); + +Services.mm.addMessageListener("FormAutofill:FieldsIdentified", () => { + return null; +}); + +addMessageListener("FormAutofillTest:AddAddress", msg => { + return ParentUtils.operateAddress("add", msg); +}); + +addMessageListener("FormAutofillTest:RemoveAddress", msg => { + return ParentUtils.operateAddress("remove", msg); +}); + +addMessageListener("FormAutofillTest:UpdateAddress", msg => { + return ParentUtils.operateAddress("update", msg); +}); + +addMessageListener("FormAutofillTest:CheckAddresses", msg => { + return ParentUtils.checkAddresses(msg); +}); + +addMessageListener("FormAutofillTest:CleanUpAddresses", msg => { + return ParentUtils.cleanUpAddresses(); +}); + +addMessageListener("FormAutofillTest:AddCreditCard", msg => { + return ParentUtils.operateCreditCard("add", msg); +}); + +addMessageListener("FormAutofillTest:RemoveCreditCard", msg => { + return ParentUtils.operateCreditCard("remove", msg); +}); + +addMessageListener("FormAutofillTest:CheckCreditCards", msg => { + return ParentUtils.checkCreditCards(msg); +}); + +addMessageListener("FormAutofillTest:CleanUpCreditCards", msg => { + return ParentUtils.cleanUpCreditCards(); +}); + +addMessageListener("FormAutofillTest:CanTestOSKeyStoreLogin", msg => { + return { canTest: OSKeyStoreTestUtils.canTestOSKeyStoreLogin() }; +}); + +addMessageListener("FormAutofillTest:OSKeyStoreLogin", async msg => { + await OSKeyStoreTestUtils.waitForOSKeyStoreLogin(msg.login); +}); + +addMessageListener("setup", async () => { + ParentUtils.setup(); +}); + +addMessageListener("cleanup", async () => { + destroyed = true; + await ParentUtils.cleanup(); +}); diff --git a/browser/extensions/formautofill/test/mochitest/mochitest.ini b/browser/extensions/formautofill/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..2964fd5b13 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/mochitest.ini @@ -0,0 +1,23 @@ +[DEFAULT] +prefs = + extensions.formautofill.creditCards.supported=on + extensions.formautofill.creditCards.enabled=true + extensions.formautofill.addresses.supported=on + extensions.formautofill.addresses.enabled=true +skip-if = toolkit == 'android' # bug 1730213 +support-files = + ../../../../../toolkit/components/satchel/test/satchel_common.js + ../../../../../toolkit/components/satchel/test/parent_utils.js + formautofill_common.js + formautofill_parent_utils.js + +[test_address_level_1_submission.html] +[test_autofill_and_ordinal_forms.html] +[test_autofocus_form.html] +[test_basic_autocomplete_form.html] +[test_form_changes.html] +[test_formautofill_preview_highlight.html] +skip-if = verify +[test_multi_locale_CA_address_form.html] +[test_multiple_forms.html] +[test_on_address_submission.html] diff --git a/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html b/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html new file mode 100644 index 0000000000..9d6ad1f7ea --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill submission for a country without address-level1</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: Test autofill submission for a country without address-level1 + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +const TEST_ADDRESSES = [{ + organization: "Mozilla", + "street-address": "123 Sesame Street", + "address-level1": "AL", + country: "DE", + timesUsed: 1, +}]; + +add_task(async function test_DE_is_valid_testcase() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.formautofill.addresses.capture.enabled", true], + ["extensions.formautofill.addresses.supportedCountries", "US,CA,DE"], + ["extensions.formautofill.creditCards.supportedCountries", "US,CA,DE"], + ], + }); + let chromeScript = SpecialPowers.loadChromeScript(function test_country_data() { + /* eslint-env mozilla/chrome-script */ + const {AddressDataLoader} = ChromeUtils.importESModule("resource://gre/modules/shared/FormAutofillUtils.sys.mjs"); + let data = AddressDataLoader.getData("DE"); + addMessageListener("CheckSubKeys", () => { + return !data.defaultLocale.sub_keys; + }); + }); + + SimpleTest.registerCleanupFunction(() => { + chromeScript.destroy(); + }); + + let result = await chromeScript.sendQuery("CheckSubKeys"); + ok(result, "Check that there are no sub_keys for the test country"); +}); + +add_task(async function test_form_will_submit_without_sub_keys() { + await SpecialPowers.pushPrefEnv({ + set: [ + // This needs to match the country in the previous test and must have no sub_keys. + ["browser.search.region", "DE"], + // We already verified the first time use case in browser test + ["extensions.formautofill.firstTimeUse", false], + ["extensions.formautofill.addresses.capture.enabled", true], + ["extensions.formautofill.addresses.supportedCountries", "US,CA,DE"], + ["extensions.formautofill.addresses.supported", "detect"] + ], + }); + // Click a field to get the form handler created + await focusAndWaitForFieldsIdentified("input[autocomplete='organization']"); + + let loadPromise = new Promise(resolve => { + /* eslint-disable-next-line mozilla/balanced-listeners */ + document.getElementById("submit_frame").addEventListener("load", resolve); + }); + + clickOnElement("input[type=submit]"); + await onStorageChanged("add"); + // Check if timesUsed is set correctly + let matching = await checkAddresses(TEST_ADDRESSES); + ok(matching, "Address saved as expected"); + + await loadPromise; + isnot(window.submit_frame.location.href, "about:blank", "Check form submitted"); +}); + +</script> + +<div> + <!-- Submit to the frame so that the test doesn't get replaced. We don't return + -- false in onsubmit since we're testing the submission succeeds. --> + <iframe id="submit_frame" name="submit_frame"></iframe> + <form action="/" target="submit_frame" method="POST"> + <p><label>organization: <input autocomplete="organization" value="Mozilla"></label></p> + <p><label>streetAddress: <input autocomplete="street-address" value="123 Sesame Street"></label></p> + <p><label>address-level1: <select autocomplete="address-level1"> + <option selected>AL</option> + <option>AK</option> + </select></label></p> + <p><label>country: <input autocomplete="country" value="DE"></label></p> + <p><input type="submit"></p> + </form> + +</div> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_autofill_and_ordinal_forms.html b/browser/extensions/formautofill/test/mochitest/test_autofill_and_ordinal_forms.html new file mode 100644 index 0000000000..5c143c3f4a --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_autofill_and_ordinal_forms.html @@ -0,0 +1,116 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill submit</title> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", true]]}); + +let MOCK_STORAGE = [{ + "given-name": "John", + "additional-name": "R", + "family-name": "Smith", + "organization": "Sesame Street", + "street-address": "123 Sesame Street.", + "tel": "+13453453456", + "country": "US", + "address-level1": "NY", +}]; + +initPopupListener(); + +add_task(async function setupStorage() { + await addAddress(MOCK_STORAGE[0]); + + await updateFormHistory([ + {op: "add", fieldname: "username", value: "petya"}, + {op: "add", fieldname: "current-password", value: "abrh#25_,K"}, + ]); +}); + +add_task(async function check_switch_autofill_form_popup() { + await setInput("#tel", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries( + [ + `{"primary":"+13453453456","secondary":"123 Sesame Street."}`, + `{"primary":"","secondary":"","categories":["name","organization","address","tel"],"focusedCategory":"tel"}`, + ], + false + ); + + await testMenuEntry(0, "!(el instanceof MozElements.MozAutocompleteRichlistitem)"); +}); + +add_task(async function check_switch_oridnal_form_popup() { + // We need an intentional wait here before switching form. + await sleep(); + await setInput("#username", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["petya"], false); + + await testMenuEntry(0, "el instanceof MozElements.MozAutocompleteRichlistitem"); +}); + +add_task(async function check_switch_autofill_form_popup_back() { + // We need an intentional wait here before switching form. + await sleep(); + await setInput("#tel", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries( + [ + `{"primary":"+13453453456","secondary":"123 Sesame Street."}`, + `{"primary":"","secondary":"","categories":["name","organization","address","tel"],"focusedCategory":"tel"}`, + ], + false + ); + + await testMenuEntry(0, "!(el instanceof MozElements.MozAutocompleteRichlistitem)"); +}); + +</script> + +<div> + + <h2>Address form</h2> + <form class="alignedLabels"> + <label>given-name: <input autocomplete="given-name" autofocus></label> + <label>additional-name: <input id="additional-name" autocomplete="additional-name"></label> + <label>family-name: <input autocomplete="family-name"></label> + <label>organization: <input autocomplete="organization"></label> + <label>street-address: <input autocomplete="street-address"></label> + <label>address-level1: <input autocomplete="address-level1"></label> + <label>postal-code: <input autocomplete="postal-code"></label> + <label>country: <input autocomplete="country"></label> + <label>country-name: <input autocomplete="country-name"></label> + <label>tel: <input id="tel" autocomplete="tel"></label> + <p> + <input type="submit" value="Submit"> + <button type="reset">Reset</button> + </p> + </form> + + <h2>Ordinal form</h2> + <form class="alignedLabels"> + <label>username: <input id="username" autocomplete="username"></label> + <p><input type="submit" value="Username"></p> + </form> + +</div> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_autofocus_form.html b/browser/extensions/formautofill/test/mochitest/test_autofocus_form.html new file mode 100644 index 0000000000..e2240474c8 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_autofocus_form.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: autocomplete on an autofocus form + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +let MOCK_STORAGE = [{ + organization: "Sesame Street", + "street-address": "123 Sesame Street.", + tel: "1-345-345-3456", +}, { + organization: "Mozilla", + "street-address": "331 E. Evelyn Avenue", + tel: "1-650-903-0800", +}]; + +initPopupListener(); + +async function setupAddressStorage() { + await addAddress(MOCK_STORAGE[0]); + await addAddress(MOCK_STORAGE[1]); +} + +add_task(async function check_autocomplete_on_autofocus_field() { + await setupAddressStorage(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({primary: address.organization, secondary: address["street-address"]}) + )); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1"> + <p>This is a basic form.</p> + <p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p> + <script> + "use strict"; + // Focuses the input before DOMContentLoaded + document.getElementById("organization").focus(); + </script> + <p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p> + <p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p> + <p><label>country: <input id="country" name="country" autocomplete="country" type="text"></label></p> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html new file mode 100644 index 0000000000..a642b2abca --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html @@ -0,0 +1,220 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: simple form address autofill + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +let MOCK_STORAGE = [{ + organization: "Sesame Street", + "street-address": "123 Sesame Street.\n2-line\n3-line", + tel: "+13453453456", + country: "US", + "address-level1": "NY", +}, { + organization: "Mozilla", + "street-address": "331 E. Evelyn Avenue\n2-line\n3-line", + tel: "+16509030800", + country: "US", + "address-level1": "CA", +}]; + +async function setupAddressStorage() { + await addAddress(MOCK_STORAGE[0]); + await addAddress(MOCK_STORAGE[1]); +} + +async function setupFormHistory() { + await updateFormHistory([ + {op: "add", fieldname: "tel", value: "+1234567890"}, + {op: "add", fieldname: "email", value: "foo@mozilla.com"}, + ]); +} + +initPopupListener(); + +// Form with history only. +add_task(async function history_only_menu_checking() { + await setupFormHistory(); + + await setInput("#tel", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["+1234567890"], false); +}); + +// Display history search result if less than 3 inputs are covered by all saved +// fields in the storage. +add_task(async function all_saved_fields_less_than_threshold() { + await addAddress({ + email: "test@test.com", + }); + + await setInput("#email", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["foo@mozilla.com"], false); + + await cleanUpAddresses(); +}); + +// Form with both history and address storage. +add_task(async function check_menu_when_both_existed() { + await setupAddressStorage(); + + await setInput("#organization", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({ + primary: address.organization, + secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]), + }) + )); + + await setInput("#street-address", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({ + primary: FormAutofillUtils.toOneLineAddress(address["street-address"]), + secondary: address.organization, + }) + )); + + await setInput("#tel", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({ + primary: address.tel, + secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]), + }) + )); + + await setInput("#address-line1", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({ + primary: FormAutofillUtils.toOneLineAddress(address["street-address"]), + secondary: address.organization, + }) + )); +}); + +// Display history search result if no matched data in addresses. +add_task(async function check_fallback_for_mismatched_field() { + await setInput("#email", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["foo@mozilla.com"], false); +}); + +// Display history search result if address autofill is disabled. +add_task(async function check_search_result_for_pref_off() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.formautofill.addresses.enabled", false]], + }); + + await setInput("#tel", ""); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(["+1234567890"], false); + + await SpecialPowers.popPrefEnv(); +}); + +// Autofill the address from dropdown menu. +add_task(async function check_fields_after_form_autofill() { + const focusedInput = await setInput("#organization", "Moz"); + await notExpectPopup(); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({ + primary: address.organization, + secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]), + }) + ).slice(1)); + synthesizeKey("KEY_ArrowDown"); + await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]); + synthesizeKey("KEY_Escape"); + is(focusedInput.value, "Mozilla", "Filled field shouldn't be reverted by ESC key"); +}); + +// Fallback to history search after autofill address. +add_task(async function check_fallback_after_form_autofill() { + await setInput("#tel", "", true); + await triggerPopupAndHoverItem("#tel", 0); + checkMenuEntries(["+1234567890"], false); + await triggerAutofillAndCheckProfile({ + tel: "+1234567890", + }); +}); + +// Resume form autofill once all the autofilled fileds are changed. +add_task(async function check_form_autofill_resume() { + document.querySelector("#tel").blur(); + document.querySelector("#form1").reset(); + await setInput("#tel", ""); + await triggerPopupAndHoverItem("#tel", 0); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({ + primary: address.tel, + secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]), + }) + )); + await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1"> + <p>This is a basic form.</p> + <p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p> + <p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p> + <p><label>address-line1: <input id="address-line1" name="address-line1" autocomplete="address-line1" type="text"></label></p> + <p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p> + <p><label>email: <input id="email" name="email" autocomplete="email" type="text"></label></p> + <p><label>country: <select id="country" name="country" autocomplete="country"> + <option/> + <option value="US">United States</option> + </select></label></p> + <p><label>states: <select id="address-level1" name="address-level1" autocomplete="address-level1"> + <option/> + <option value="CA">California</option> + <option value="NY">New York</option> + <option value="WA">Washington</option> + </select></label></p> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_form_changes.html b/browser/extensions/formautofill/test/mochitest/test_form_changes.html new file mode 100644 index 0000000000..2eace91a53 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_form_changes.html @@ -0,0 +1,128 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: autocomplete on an autofocus form + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +let MOCK_STORAGE = [{ + name: "John Doe", + organization: "Sesame Street", + "address-level2": "Austin", + tel: "+13453453456", +}, { + name: "Foo Bar", + organization: "Mozilla", + "address-level2": "San Francisco", + tel: "+16509030800", +}]; + +initPopupListener(); + +async function setupAddressStorage() { + await addAddress(MOCK_STORAGE[0]); + await addAddress(MOCK_STORAGE[1]); +} + +function addInputField(form, className) { + let newElem = document.createElement("input"); + newElem.name = className; + newElem.autocomplete = className; + newElem.type = "text"; + form.appendChild(newElem); +} + +async function checkFieldsAutofilled(formId, profile) { + const elements = document.querySelectorAll(`#${formId} input`); + for (const element of elements) { + await SimpleTest.promiseWaitForCondition(() => { + return element.value == profile[element.name]; + }); + await checkFieldHighlighted(element, true); + } +} + +async function checkFormChangeHappened(formId) { + info("expecting form changed"); + await focusAndWaitForFieldsIdentified(`#${formId} input[name=tel]`); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + synthesizeKey("KEY_ArrowDown"); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({primary: address.tel, secondary: address.name}) + )); + + // Click the first entry of the autocomplete popup and make sure all fields are autofilled + synthesizeKey("KEY_Enter"); + await checkFieldsAutofilled(formId, MOCK_STORAGE[0]); + // This is for checking the changes of element count. + addInputField(document.querySelector(`#${formId}`), "address-level2"); + + await focusAndWaitForFieldsIdentified(`#${formId} input[name=name]`); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + + // Click on an autofilled field would show an autocomplete popup with "clear form" entry + checkMenuEntries([ + JSON.stringify({primary: "", secondary: ""}), // Clear Autofill Form + JSON.stringify({primary: "", secondary: ""}) // FormAutofill Preferemce + ], false); + + // This is for checking the changes of element removed and added then. + document.querySelector(`#${formId} input[name=address-level2]`).remove(); + addInputField(document.querySelector(`#${formId}`), "address-level2"); + + await focusAndWaitForFieldsIdentified(`#${formId} input[name=address-level2]`, true); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(MOCK_STORAGE.map(address => + JSON.stringify({primary: address["address-level2"], secondary: address.name}) + )); + + // Make sure everything is autofilled in the end + synthesizeKey("KEY_ArrowDown"); + synthesizeKey("KEY_Enter"); + await checkFieldsAutofilled(formId, MOCK_STORAGE[0]); +} + +add_task(async function init_storage() { + await setupAddressStorage(); +}); + +add_task(async function check_change_happened_in_form() { + await checkFormChangeHappened("form1"); +}); + +add_task(async function check_change_happened_in_body() { + await checkFormChangeHappened("form2"); +}); +</script> + +<p id="display"></p> +<div id="content"> + <form id="form1"> + <p><label>organization: <input name="organization" autocomplete="organization" type="text"></label></p> + <p><label>tel: <input name="tel" autocomplete="tel" type="text"></label></p> + <p><label>name: <input name="name" autocomplete="name" type="text"></label></p> + </form> + <div id="form2"> + <p><label>organization: <input name="organization" autocomplete="organization" type="text"></label></p> + <p><label>tel: <input name="tel" autocomplete="tel" type="text"></label></p> + <p><label>name: <input name="name" autocomplete="name" type="text"></label></p> + </div> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html b/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html new file mode 100644 index 0000000000..b32b036c9c --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test form autofill - preview and highlight</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: preview and highlight + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +const MOCK_STORAGE = [ + { + organization: "Sesame Street", + "street-address": "123 Sesame Street.", + tel: "+13453453456", + }, + { + organization: "Mozilla", + "street-address": "331 E. Evelyn Avenue", + }, + { + organization: "Tel org", + tel: "+12223334444", + }, + { + organization: "Random Org", + "address-level1": "First Admin Level", + tel: "+13453453456", + }, + { + organization: "readonly Org", + "address-level1": "First Admin Level", + tel: "+13453453456", + name: "John Doe", + }, + { + organization: "test org", + "address-level2": "Not a Town", + tel: "+13453453456", + name: "John Doe", + } +]; + + +initPopupListener(); + +add_task(async function setup_storage() { + for (const storage of MOCK_STORAGE) { + await addAddress(storage); + } +}); + +add_task(async function check_preview() { + const focusedInput = await setInput("#organization", ""); + + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + await checkFormFieldsStyle(null); + + for (let i = 0; i < MOCK_STORAGE.length; i++) { + info(`Checking organization: ${MOCK_STORAGE[i].organization} preview`); + synthesizeKey("KEY_ArrowDown"); + await notifySelectedIndex(i); + await checkFormFieldsStyle(MOCK_STORAGE[i]); + } + + // Navigate to the footer + synthesizeKey("KEY_ArrowDown"); + await notifySelectedIndex(MOCK_STORAGE.length); + await checkFormFieldsStyle(null); + + synthesizeKey("KEY_ArrowDown"); + await notifySelectedIndex(-1); + await checkFormFieldsStyle(null); + + focusedInput.blur(); +}); + +add_task(async function check_filled_highlight() { + await triggerPopupAndHoverItem("#organization", 0); + // filled 1st address + await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]); + await checkFormFieldsStyle(MOCK_STORAGE[0], false); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1"> + <p>This is a basic form.</p> + <p><label>organization: <input id="organization" autocomplete="organization"></label></p> + <p><label>streetAddress: <input id="street-address" autocomplete="street-address"></label></p> + <p><label>tel: <input id="tel" autocomplete="tel"></label></p> + <p><label>country: <input id="country" autocomplete="country"></label></p> + <p><label>address-level1: + <select id="address-level1" autocomplete="address-level1"> + <option>First Admin Level</option> + <option>Second Admin Level</option> + </select> + </label></p> + <p><label>full name: <input id="name" autocomplete="name" readonly value="UNCHANGED"></label></p> + <p><label>address-level2: <input id="address-level2" autocomplete="address-level2" disabled value="Town"></label></p> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html b/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html new file mode 100644 index 0000000000..48e0caa785 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_multi_locale_CA_address_form.html @@ -0,0 +1,273 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: simple form address autofill + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +let MOCK_STORAGE = [{ + organization: "Mozilla Vancouver", + "street-address": "163 W Hastings St.\n#209\n3-line", + tel: "+17787851540", + country: "CA", + "address-level1": "BC", +}, { + organization: "Mozilla Toronto", + "street-address": "366 Adelaide St.\nW Suite 500\n3-line", + tel: "+14168483114", + country: "CA", + "address-level1": "ON", +}, { + organization: "Prince of Wales Northern Heritage", + "street-address": "4750 48 St.\nYellowknife\n3-line", + tel: "+18677679347", + country: "CA", + "address-level1": "Northwest Territories", +}, { + organization: "ExpoCité", + "street-address": "250 Boulevard Wilfrid-Hamel\nVille de Québec\n3-line", + tel: "+14186917110", + country: "CA", + "address-level1": "Québec", +}]; + +function checkElementFilled(element, expectedvalue) { + let focusFired = false; + let inputFired = false; + let changeFired = false; + return [ + new Promise(resolve => { + element.addEventListener("focus", function onChange() { + ok(true, "Checking " + element.name + " field fires focus event"); + focusFired = true; + resolve(); + }, {once: true}); + }), + new Promise(resolve => { + let beforeInputFired = false; + let oldValue = element.value; + element.addEventListener("beforeinput", function onBeforeInput(event) { + ok(true, "Checking " + element.name + " field fires beforeinput event"); + ok(focusFired, "Focus fired before `beforeinput` event"); + beforeInputFired = true; + ok(event instanceof InputEvent, + `"beforeinput" event should be dispatched with InputEvent interface on ${element.name}`); + is(event.inputType, "insertReplacementText", + 'inputType value of "beforeinput" event should be "insertReplacementText"'); + is(event.data, expectedvalue, + 'data value of "beforeinput" event should be same as expected value'); + is(event.dataTransfer, null, + 'dataTransfer value of "beforeinput" event should be null'); + is(event.getTargetRanges().length, 0, + 'getTargetRanges() of "beforeinput" event should return empty array'); + is(event.cancelable, SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input"), + `"beforeinput" event should be cancelable on ${element.name} unless it's suppressed by the pref`); + is(event.bubbles, true, + `"input" event should always bubble on ${element.name}`); + is(element.value, oldValue, + 'value of the element should not be modified at "beforeinput" event yet'); + }, {once: true}); + element.addEventListener("input", function onInput(event) { + ok(true, "Checking " + element.name + " field fires input event"); + if (element.tagName == "INPUT" && element.type == "text") { + ok(beforeInputFired, `"beforeinput" event shoud've been fired on ${element.name} before "input" event`); + ok(event instanceof InputEvent, + `"input" event should be dispatched with InputEvent interface on ${element.name}`); + is(event.inputType, "insertReplacementText", + "inputType value should be \"insertReplacementText\""); + is(event.data, expectedvalue, + "data value should be same as expected value"); + is(event.dataTransfer, null, + "dataTransfer value should be null"); + is(event.getTargetRanges().length, 0, + 'getTargetRanges() should return empty array'); + is(element.value, expectedvalue, + 'value of the element should be modified at "input" event'); + } else { + ok(!beforeInputFired, `"beforeinput" event shoudn't be fired on ${element.name} before "input" event`); + ok(event instanceof Event && !(event instanceof UIEvent), + `"input" event should be dispatched with Event interface on ${element.name}`); + } + is(event.cancelable, false, + `"input" event should be never cancelable on ${element.name}`); + is(event.bubbles, true, + `"input" event should always bubble on ${element.name}`); + inputFired = true; + resolve(); + }, {once: true}); + }), + new Promise(resolve => { + element.addEventListener("change", function onChange() { + ok(true, "Checking " + element.name + " field fires change event"); + is(element.value, expectedvalue, "Checking " + element.name + " field"); + ok(focusFired, "Focus fired before `change` event"); + changeFired = true; + resolve(); + }, {once: true}); + }), + new Promise(resolve => { + element.addEventListener("blur", function onChange() { + ok(true, "Checking " + element.name + " field fires blur event"); + ok(changeFired, "Change fired before `blur` event"); + ok(inputFired, "Input fired before `blur` event"); + is(element.value, expectedvalue, "Checking " + element.name + " field"); + resolve(); + }, {once: true}); + }), + ]; +} + +function checkAutoCompleteInputFilled(element, expectedvalue) { + return new Promise(resolve => { + element.addEventListener("input", function onInput() { + is(element.value, expectedvalue, "Checking " + element.name + " field"); + resolve(); + }, {once: true}); + }); +} + +function checkFormFilled(selector, address) { + info("expecting form filled"); + let promises = []; + let form = document.querySelector(selector); + for (let prop in address) { + let element = form.querySelector(`[name=${prop}]`); + if (document.activeElement == element) { + promises.push(checkAutoCompleteInputFilled(element, address[prop])); + } else { + let converted = address[prop]; + if (prop == "street-address") { + converted = FormAutofillUtils.toOneLineAddress(converted); + } + promises.push(...checkElementFilled(element, converted)); + } + } + synthesizeKey("KEY_Enter"); + return Promise.all(promises); +} + +async function setupAddressStorage() { + for (let address of MOCK_STORAGE) { + await addAddress(address); + } +} + +initPopupListener(); + +add_setup(async () => { + // This test relies on being able to fill a Canadian address which isn't possible + // without `supportedCountries` allowing Canada + await SpecialPowers.pushPrefEnv({"set": [["extensions.formautofill.supportedCountries", "US,CA"]]}); + + await setupAddressStorage(); +}); + +// Autofill the address with address level 1 code. +add_task(async function autofill_with_level1_code() { + await setInput("#organization-en", "Mozilla Toront"); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + + synthesizeKey("KEY_ArrowDown"); + // Replace address level 1 code with full name in English for test result + let result = Object.assign({}, MOCK_STORAGE[1], {"address-level1": "Ontario"}); + await checkFormFilled("#form-en", result); + + await setInput("#organization-fr", "Mozilla Vancouve"); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + + synthesizeKey("KEY_ArrowDown"); + // Replace address level 1 code with full name in French for test result + result = Object.assign({}, MOCK_STORAGE[0], {"address-level1": "Colombie-Britannique"}); + await checkFormFilled("#form-fr", result); + document.querySelector("#form-en").reset(); + document.querySelector("#form-fr").reset(); +}); + +// Autofill the address with address level 1 full name. +add_task(async function autofill_with_level1_full_name() { + await setInput("#organization-en", "ExpoCit"); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + + synthesizeKey("KEY_ArrowDown"); + // Replace address level 1 code with full name in French for test result + let result = Object.assign({}, MOCK_STORAGE[3], {"address-level1": "Quebec"}); + await checkFormFilled("#form-en", result); + + await setInput("#organization-fr", "Prince of Wales"); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + + synthesizeKey("KEY_ArrowDown"); + // Replace address level 1 code with full name in English for test result + result = Object.assign({}, MOCK_STORAGE[2], {"address-level1": "Territoires du Nord-Ouest"}); + await checkFormFilled("#form-fr", result); +}); + +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form-en"> + <p>This is a basic CA form with en address level 1 select.</p> + <p><label>organization: <input id="organization-en" name="organization" autocomplete="organization" type="text"></label></p> + <p><label>streetAddress: <input id="street-address-en" name="street-address" autocomplete="street-address" type="text"></label></p> + <p><label>address-line1: <input id="address-line1-en" name="address-line1" autocomplete="address-line1" type="text"></label></p> + <p><label>tel: <input id="tel-en" name="tel" autocomplete="tel" type="text"></label></p> + <p><label>email: <input id="email-en" name="email" autocomplete="email" type="text"></label></p> + <p><label>country: <select id="country-en" name="country" autocomplete="country"> + <option/> + <option value="US">United States</option> + <option value="CA">Canada</option> + </select></label></p> + <p><label>states: <select id="address-level1-en" name="address-level1" autocomplete="address-level1"> + <option/> + <option value="British Columbia">British Columbia</option> + <option value="Ontario">Ontario</option> + <option value="Northwest Territories">Northwest Territories</option> + <option value="Quebec">Quebec</option> + </select></label></p> + </form> + + <form id="form-fr"> + <p>This is a basic CA form with fr address level 1 select.</p> + <p><label>organization: <input id="organization-fr" name="organization" autocomplete="organization" type="text"></label></p> + <p><label>streetAddress: <input id="street-address-fr" name="street-address" autocomplete="street-address" type="text"></label></p> + <p><label>address-line1: <input id="address-line1-fr" name="address-line1" autocomplete="address-line1" type="text"></label></p> + <p><label>tel: <input id="tel-fr" name="tel" autocomplete="tel" type="text"></label></p> + <p><label>email: <input id="email-fr" name="email" autocomplete="email" type="text"></label></p> + <p><label>country: <select id="country-fr" name="country" autocomplete="country"> + <option/> + <option value="US">United States</option> + <option value="CA">Canada</option> + </select></label></p> + <p><label>states: <select id="address-level1-fr" name="address-level1" autocomplete="address-level1"> + <option/> + <option value="Colombie-Britannique">Colombie-Britannique</option> + <option value="Ontario">Ontario</option> + <option value="Territoires du Nord-Ouest">Territoires du Nord-Ouest</option> + <option value="Québec">Québec</option> + </select></label></p> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_multiple_forms.html b/browser/extensions/formautofill/test/mochitest/test_multiple_forms.html new file mode 100644 index 0000000000..feea55aae6 --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_multiple_forms.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill submit</title> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +let MOCK_STORAGE = [{ + "given-name": "John", + "additional-name": "R", + "family-name": "Smith", +}]; + +initPopupListener(); + +add_task(async function setupStorage() { + await addAddress(MOCK_STORAGE[0]); +}); + +add_task(async function check_switch_form_popup() { + await setInput("#additional-name", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + + // We need an intentional wait here before switching form. + await sleep(); + await setInput("#organization", ""); + synthesizeKey("KEY_ArrowDown"); + const {open: popupOpen} = await getPopupState(); + is(popupOpen, false); + + await sleep(); + await setInput("#given-name", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); +}); + +</script> + +<div> + + <form> + <label>Name:<input id="name" autocomplete="name"></label> + <label>Organization:<input id="organization" autocomplete="organization"></label> + <label>City:<input autocomplete="address-level2"></label> + </form> + + <form> + <label>Given-Name: <input id="given-name" autocomplete="given-name"></label> + <label>Additional-Name/Middle: <input id="additional-name" autocomplete="additional-name"></label> + <label>FamilyName-LastName: <input id="family-name" autocomplete="family-name"></label> + </form> + +</div> +</body> +</html> diff --git a/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html b/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html new file mode 100644 index 0000000000..79da48c77b --- /dev/null +++ b/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill submit</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="formautofill_common.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Form autofill test: check if address is saved/updated correctly + +<script> +/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */ + +"use strict"; + +let TEST_ADDRESSES = [{ + organization: "Sesame Street", + "street-address": "123 Sesame Street.", + tel: "+13453453456", +}, { + organization: "Mozilla", + "street-address": "331 E. Evelyn Avenue", + tel: "+16509030800", +}]; + +add_task(async function setup_prefs() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["extensions.formautofill.addresses.enabled", true], + ["extensions.formautofill.addresses.capture.enabled", true], + ], + }); +}); + +initPopupListener(); + +// Submit first address for saving. +add_task(async function check_storage_after_form_submitted() { + // We already verified the first time use case in browser test + await SpecialPowers.pushPrefEnv({ + "set": [["extensions.formautofill.firstTimeUse", false]], + }); + + for (let key in TEST_ADDRESSES[0]) { + await setInput("#" + key, TEST_ADDRESSES[0][key]); + } + + clickOnElement("input[type=submit]"); + + let expectedAddresses = TEST_ADDRESSES.slice(0, 1); + await onStorageChanged("add"); + // Check if timesUsed is set correctly + expectedAddresses[0].timesUsed = 1; + let matching = await checkAddresses(expectedAddresses); + ok(matching, "Address saved as expected"); + delete expectedAddresses[0].timesUsed; +}); + +// Submit another new address. +add_task(async function check_storage_after_another_address_submitted() { + await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]}); + + document.querySelector("form").reset(); + for (let key in TEST_ADDRESSES[1]) { + await setInput("#" + key, TEST_ADDRESSES[1][key]); + } + + clickOnElement("input[type=submit]"); + + // The 2nd test address should be on the top since it's the last used one. + let addressesInMenu = TEST_ADDRESSES.slice(1); + addressesInMenu.push(TEST_ADDRESSES[0]); + + // let expectedAddresses = TEST_ADDRESSES.slice(0); + await onStorageChanged("add"); + let matching = await checkAddresses(TEST_ADDRESSES); + ok(matching, "New address saved as expected"); + + await setInput("#organization", ""); + synthesizeKey("KEY_ArrowDown"); + await expectPopup(); + checkMenuEntries(addressesInMenu.map(address => + JSON.stringify({primary: address.organization, secondary: address["street-address"]}) + )); +}); + +// Submit another new address that is mergeable. +add_task(async function new_address_submitted_and_merged() { + // TODO: Bug Bug 1812294 +}); + +// Submit an updated autofill address and merge. +add_task(async function check_storage_after_form_submitted() { + // TODO: Bug Bug 1812294 +}); + +// Submit a subset address manually. +add_task(async function submit_subset_manually() { + // TODO: Bug Bug 1812294 +}); + +</script> + +<div> + + <form onsubmit="return false"> + <p>This is a basic form for submitting test.</p> + <p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p> + <p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p> + <p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p> + <p><label>country: <input id="country" name="country" autocomplete="country" type="text"></label></p> + <p><input type="submit"></p> + </form> + +</div> +</body> +</html> |