/* * Test for form auto fill content helper fill all inputs function. */ /* eslint-disable mozilla/no-arbitrary-setTimeout */ "use strict"; const { setTimeout, clearTimeout } = ChromeUtils.importESModule( "resource://gre/modules/Timer.sys.mjs" ); var FormAutofillHandler, OSKeyStore; add_setup(async () => { ({ FormAutofillHandler } = ChromeUtils.import( "resource://autofill/FormAutofillHandler.jsm" )); ({ OSKeyStore } = ChromeUtils.importESModule( "resource://gre/modules/OSKeyStore.sys.mjs" )); }); const TESTCASES = [ { description: "Form without autocomplete property", document: `
`, focusedInputId: "given-name", profileData: {}, expectedResult: { "street-addr": "", city: "", country: "", email: "", tel: "", }, }, { description: "Form with autocomplete properties and 1 token", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "street-address": "2 Harrison St line2", "-moz-street-address-one-line": "2 Harrison St line2", "address-level2": "San Francisco", country: "US", email: "foo@mozilla.com", tel: "1234567", }, expectedResult: { "street-addr": "2 Harrison St line2", city: "San Francisco", country: "US", email: "foo@mozilla.com", tel: "1234567", }, }, { description: "Form with autocomplete properties and 2 tokens", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "street-address": "2 Harrison St", "address-level2": "San Francisco", country: "US", email: "foo@mozilla.com", tel: "1234567", }, expectedResult: { "street-addr": "2 Harrison St", city: "San Francisco", country: "US", email: "foo@mozilla.com", tel: "1234567", }, }, { description: "Form with autocomplete properties and profile is partly matched", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "street-address": "2 Harrison St", "address-level2": "San Francisco", country: "US", email: "", tel: "", }, expectedResult: { "street-addr": "2 Harrison St", city: "San Francisco", country: "US", email: "", tel: "", }, }, { description: "Form with autocomplete properties but mismatched", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "street-address": "", "address-level2": "", country: "", email: "foo@mozilla.com", tel: "1234567", }, expectedResult: { "street-addr": "", city: "", country: "", email: "foo@mozilla.com", tel: "1234567", }, }, { description: `Form with elements that have autocomplete set to "off"`, document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "given-name": "John", "family-name": "Doe", "street-address": "2 Harrison St", country: "US", organization: "Test organization", }, expectedResult: { "given-name": "John", "family-name": "Doe", "street-address": "2 Harrison St", organization: "Test organization", country: "US", }, }, { description: `Form with autocomplete set to "off" and no autocomplete attribute on the form's elements`, document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "given-name": "John", "family-name": "Doe", "street-address": "2 Harrison St", country: "US", "address-level2": "Somewhere", }, expectedResult: { "given-name": "John", "family-name": "Doe", "street-address": "2 Harrison St", city: "Somewhere", country: "US", }, }, { description: "Form with autocomplete select elements and matching option values", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", "address-level1": "CA", }, expectedResult: { country: "US", state: "CA", }, }, { description: "Form with autocomplete select elements and matching option texts", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "United States", "address-level1": "California", }, expectedResult: { country: "US", state: "CA", }, }, { description: "Form with a readonly input and non-readonly inputs", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "given-name": "John", "family-name": "Doe", "street-address": "100 Main Street", city: "Hamilton", }, expectedResult: { "given-name": "John", "family-name": "Doe", "street-addr": "100 Main Street", city: "TEST CITY", }, }, { description: "Fill address fields in a form with addr and CC fields.", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "street-address": "2 Harrison St line2", "-moz-street-address-one-line": "2 Harrison St line2", "address-level2": "San Francisco", country: "US", email: "foo@mozilla.com", tel: "1234567", }, expectedResult: { "street-addr": "2 Harrison St line2", city: "San Francisco", country: "US", email: "foo@mozilla.com", tel: "1234567", "cc-number": "", "cc-name": "", "cc-exp-month": "", "cc-exp-year": "", }, }, { description: "Fill credit card fields in a form with address and CC fields.", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { "street-addr": "", city: "", country: "", email: "", tel: "", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", }, }, { description: "Fill credit card fields in a form with a placeholder on expiration month input field", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", }, }, { description: "Fill credit card fields in a form without a placeholder on expiration month and expiration year input fields", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", }, }, { description: "Fill credit card fields in a form with a placeholder on expiration year input field", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 2025, }, expectedResult: { "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", }, }, { description: "Form with hidden input and visible input that share the same autocomplete attribute", document: `
`, focusedInputId: "visible-cc", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { guid: "123", "visible-cc": "4111111111111111", "visible-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", "hidden-cc": undefined, "hidden-cc-2": undefined, "hidden-name": undefined, "hidden-name-2": undefined, }, }, { description: "Fill credit card fields in a form where the value property is being used as a placeholder for cardholder name", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", }, }, { description: "Fill credit card number fields in a form with multiple cc-number inputs", document: `
`, focusedInputId: "cc-number1", profileData: { guid: "123", "cc-number": "371449635398431", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { guid: "123", "cc-number1": "3714", "cc-number2": "4963", "cc-number3": "5398", "cc-number4": "431", "cc-exp-month": "06", "cc-exp-year": "25", }, }, { description: "Fill credit card number fields in a form with multiple valid credit card sections", document: `
`, focusedInputId: "cc-number1", profileData: { guid: "123", "cc-type": "mastercard", "cc-number": "371449635398431", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { guid: "123", "cc-type1": "mastercard", "cc-number1": "3714", "cc-number2": "4963", "cc-number3": "5398", "cc-number4": "431", "cc-exp-month1": "06", "cc-exp-year1": "25", "cc-type2": "", "cc-number-5": "", "cc-number-6": "", "cc-number-7": "", "cc-number-8": "", "cc-exp-month2": "", "cc-exp-year2": "", }, }, { description: "Fill credit card fields in a form with placeholders on month and year and these inputs are type=tel", document: `
`, focusedInputId: "cardHolder", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 2025, }, expectedResult: { cardHolder: "test name", cardNumber: "4111111111111111", month: "06", year: "25", }, }, ]; const TESTCASES_INPUT_UNCHANGED = [ { description: "Form with autocomplete select elements; with default and no matching options", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", "address-level1": "unknown state", }, expectedResult: { country: "US", state: "", }, }, ]; const TESTCASES_FILL_SELECT = [ // US States { description: "Form with US states select elements", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", "address-level1": "CA", }, expectedResult: { state: "CA", }, }, { description: "Form with US states select elements; with lower case state key", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", "address-level1": "CA", }, expectedResult: { state: "ca", }, }, { description: "Form with US states select elements; with state name and extra spaces", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", "address-level1": " California ", }, expectedResult: { state: "CA", }, }, { description: "Form with US states select elements; with partial state key match", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", "address-level1": "WA", }, expectedResult: { state: "US-WA", }, }, // Country { description: "Form with country select elements", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", }, expectedResult: { country: "US", }, }, { description: "Form with country select elements; with lower case key", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", }, expectedResult: { country: "us", }, }, { description: "Form with country select elements; with alternative name 1", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", }, expectedResult: { country: "XX", }, }, { description: "Form with country select elements; with alternative name 2", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", }, expectedResult: { country: "XX", }, }, { description: "Form with country select elements; with partial matching value", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", country: "US", }, expectedResult: { country: "XX", }, }, { description: "Fill credit card expiration month field in a form with select field", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": "6", "cc-exp-year": "2025", }, }, { description: "Fill credit card information correctly when one of the card type options is 'American Express'", document: `
`, focusedInputId: "cc-number", profileData: { guid: "123", "cc-number": "378282246310005", "cc-type": "amex", "cc-name": "test name", "cc-exp-month": 8, "cc-exp-year": 26, }, expectedResult: { guid: "123", "cc-number": "378282246310005", "cc-type": "AX", "cc-name": "test name", "cc-exp-month": 8, "cc-exp-year": 26, }, }, ]; const TESTCASES_BOTH_CHANGED_AND_UNCHANGED = [ { description: "Form with a disabled input and non-disabled inputs. The 'country' field should not change", document: `
`, focusedInputId: "given-name", profileData: { guid: "123", "given-name": "John", "family-name": "Doe", "street-address": "100 Main Street", country: "CA", }, expectedResult: { "given-name": "John", "family-name": "Doe", "street-addr": "100 Main Street", country: "DE", }, }, ]; function do_test(testcases, testFn) { for (let tc of testcases) { (function() { let testcase = tc; add_task(async function() { info("Starting testcase: " + testcase.description); let ccNumber = testcase.profileData["cc-number"]; if (ccNumber) { testcase.profileData[ "cc-number-encrypted" ] = await OSKeyStore.encrypt(ccNumber); delete testcase.profileData["cc-number"]; } let doc = MockDocument.createTestDocument( "http://localhost:8080/test/", testcase.document ); let form = doc.querySelector("form"); let formLike = FormLikeFactory.createFromForm(form); let handler = new FormAutofillHandler(formLike); let promises = []; // Replace the internal decrypt method with OSKeyStore API, // but don't pass the reauth parameter to avoid triggering // reauth login dialog in these tests. let decryptHelper = async (cipherText, reauth) => { return OSKeyStore.decrypt(cipherText, false); }; handler.collectFormFields(); let focusedInput = doc.getElementById(testcase.focusedInputId); try { handler.focusedInput = focusedInput; } catch (e) { if (e.message.includes("WeakMap key must be an object")) { throw new Error( `Couldn't find the focusedInputId in the current form! Make sure focusedInputId exists in your test form! testcase description:${testcase.description}` ); } else { throw e; } } for (let section of handler.sections) { section._decrypt = decryptHelper; } handler.activeSection.fieldDetails.forEach(field => { let element = field.elementWeakRef.get(); if (!testcase.profileData[field.fieldName]) { // Avoid waiting for `change` event of a input with a blank value to // be filled. return; } promises.push(...testFn(testcase, element)); }); let [adaptedProfile] = handler.activeSection.getAdaptedProfiles([ testcase.profileData, ]); await handler.autofillFormFields(adaptedProfile, focusedInput); Assert.equal( handler.activeSection.filledRecordGUID, testcase.profileData.guid, "Check if filledRecordGUID is set correctly" ); await Promise.all(promises); }); })(); } } do_test(TESTCASES, (testcase, element) => { let id = element.id; return [ new Promise(resolve => { element.addEventListener( "input", () => { Assert.ok(true, "Checking " + id + " field fires input event"); resolve(); }, { once: true } ); }), new Promise(resolve => { element.addEventListener( "change", () => { Assert.ok(true, "Checking " + id + " field fires change event"); Assert.equal( element.value, testcase.expectedResult[id], "Check the " + id + " field was filled with correct data" ); resolve(); }, { once: true } ); }), ]; }); do_test(TESTCASES_INPUT_UNCHANGED, (testcase, element) => { return [ new Promise((resolve, reject) => { // Make sure no change or input event is fired when no change occurs. let cleaner; let timer = setTimeout(() => { let id = element.id; element.removeEventListener("change", cleaner); element.removeEventListener("input", cleaner); Assert.equal( element.value, testcase.expectedResult[id], "Check no value is changed on the " + id + " field" ); resolve(); }, 1000); cleaner = event => { clearTimeout(timer); reject(`${event.type} event should not fire`); }; element.addEventListener("change", cleaner); element.addEventListener("input", cleaner); }), ]; }); do_test(TESTCASES_FILL_SELECT, (testcase, element) => { let id = element.id; return [ new Promise(resolve => { element.addEventListener( "input", () => { Assert.equal( element.value, testcase.expectedResult[id], "Check the " + id + " field was filled with correct data" ); resolve(); }, { once: true } ); }), ]; }); do_test(TESTCASES_BOTH_CHANGED_AND_UNCHANGED, (testcase, element) => { // Ensure readonly and disabled inputs are not autofilled if (element.readOnly || element.disabled) { return [ new Promise((resolve, reject) => { // Make sure no change or input event is fired when no change occurs. let cleaner; let timer = setTimeout(() => { let id = element.id; element.removeEventListener("change", cleaner); element.removeEventListener("input", cleaner); Assert.equal( element.value, testcase.expectedResult[id], "Check no value is changed on the " + id + " field" ); resolve(); }, 1000); cleaner = event => { clearTimeout(timer); reject(`${event.type} event should not fire`); }; element.addEventListener("change", cleaner); element.addEventListener("input", cleaner); }), ]; } let id = element.id; // Ensure that non-disabled and non-readonly fields are filled correctly return [ new Promise(resolve => { element.addEventListener( "input", () => { Assert.ok(true, "Checking " + id + " field fires input event"); resolve(); }, { once: true } ); }), new Promise(resolve => { element.addEventListener( "change", () => { Assert.ok(true, "Checking " + id + " field fires change event"); Assert.equal( element.value, testcase.expectedResult[id], "Check the " + id + " field was filled with correct data" ); resolve(); }, { once: true } ); }), ]; });