/* * 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" ); 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: { "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: { "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: { "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: { 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: { 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: { "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: { "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: { "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: { "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: { "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: { "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: { "cc-number": "4111111111111111", "cc-name": "test name", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { "visible-cc": "4111111111111111", "visible-name": "test name", "cc-exp-month": "06", "cc-exp-year": "25", "hidden-cc": "4111111111111111", "hidden-cc-2": "4111111111111111", "hidden-name": "test name", "hidden-name-2": "test name", }, }, { 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: { "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 number fields in a form with multiple cc-number inputs", document: ``, focusedInputId: "cc-number1", profileData: { "cc-number": "371449635398431", "cc-exp-month": 6, "cc-exp-year": 25, }, expectedResult: { "cc-number1": "3714", "cc-number2": "4963", "cc-number3": "5398", "cc-number4": "431", "cc-exp-month": "06", "cc-exp-year": "25", }, }, // TODO: Bug 1902233 - Migrate this test to browser test //{ //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: { "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: { 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: { 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: { 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: { country: "US", "address-level1": "WA", }, expectedResult: { state: "US-WA", }, }, // Country { description: "Form with country select elements", document: ``, focusedInputId: "given-name", profileData: { country: "US", }, expectedResult: { country: "US", }, }, { description: "Form with country select elements; with lower case key", document: ``, focusedInputId: "given-name", profileData: { country: "US", }, expectedResult: { country: "us", }, }, { description: "Form with country select elements; with alternative name 1", document: ``, focusedInputId: "given-name", profileData: { country: "US", }, expectedResult: { country: "XX", }, }, { description: "Form with country select elements; with alternative name 2", document: ``, focusedInputId: "given-name", profileData: { country: "US", }, expectedResult: { country: "XX", }, }, { description: "Form with country select elements; with partial matching value", document: ``, focusedInputId: "given-name", profileData: { country: "US", }, expectedResult: { country: "XX", }, }, { description: "Fill credit card expiration month field in a form with select field", document: ``, focusedInputId: "cc-number", profileData: { "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: { "cc-number": "378282246310005", "cc-type": "amex", "cc-name": "test name", "cc-exp-month": 8, "cc-exp-year": 26, }, expectedResult: { "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: { "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 (const tc of testcases) { (function () { const testcase = tc; add_task(async function () { info("Starting testcase: " + testcase.description); const doc = MockDocument.createTestDocument( "http://localhost:8080/test/", testcase.document ); const form = doc.querySelector("form"); const formLike = FormLikeFactory.createFromForm(form); const handler = new FormAutofillHandler(formLike); const promises = []; const fieldDetails = FormAutofillHandler.collectFormFieldDetails( handler.form ); // TODO: This test should be a browser test instead FormAutofillHeuristics.parseAndUpdateFieldNamesParent(fieldDetails); handler.setIdentifiedFieldDetails(fieldDetails); let focusedInputIdentifier; handler.fieldDetails.forEach(field => { const element = field.element; if (element.id == testcase.focusedInputId) { focusedInputIdentifier = field.elementId; } 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)); }); const ids = handler.fieldDetails.map( fieldDetail => fieldDetail.elementId ); await handler.fillFields( focusedInputIdentifier, ids, testcase.profileData ); 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 } ); }), ]; });