fune/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
2020-08-08 11:55:45 +00:00

625 lines
20 KiB
JavaScript

/*
* Test for form auto fill content helper fill all inputs function.
*/
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
const { setTimeout, clearTimeout } = ChromeUtils.import(
"resource://gre/modules/Timer.jsm",
{}
);
var FormAutofillHandler, OSKeyStore;
add_task(async function setup() {
({ FormAutofillHandler } = ChromeUtils.import(
"resource://formautofill/FormAutofillHandler.jsm"
));
({ OSKeyStore } = ChromeUtils.import(
"resource://gre/modules/OSKeyStore.jsm"
));
});
const TESTCASES = [
{
description: "Form without autocomplete property",
document: `<form><input id="given-name"><input id="family-name">
<input id="street-addr"><input id="city"><select id="country"></select>
<input id='email'><input id="tel"></form>`,
focusedInputId: "given-name",
profileData: {},
expectedResult: {
"street-addr": "",
city: "",
country: "",
email: "",
tel: "",
},
},
{
description: "Form with autocomplete properties and 1 token",
document: `<form><input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<input id="street-addr" autocomplete="street-address">
<input id="city" autocomplete="address-level2">
<select id="country" autocomplete="country">
<option/>
<option value="US">United States</option>
</select>
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel"></form>`,
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: `<form><input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<input id="street-addr" autocomplete="shipping street-address">
<input id="city" autocomplete="shipping address-level2">
<select id="country" autocomplete="shipping country">
<option/>
<option value="US">United States</option>
</select>
<input id='email' autocomplete="shipping email">
<input id="tel" autocomplete="shipping tel"></form>`,
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: `<form><input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<input id="street-addr" autocomplete="shipping street-address">
<input id="city" autocomplete="shipping address-level2">
<input id="country" autocomplete="shipping country">
<input id='email' autocomplete="shipping email">
<input id="tel" autocomplete="shipping tel"></form>`,
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: `<form><input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<input id="street-addr" autocomplete="billing street-address">
<input id="city" autocomplete="billing address-level2">
<input id="country" autocomplete="billing country">
<input id='email' autocomplete="shipping email">
<input id="tel" autocomplete="shipping tel"></form>`,
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 autocomplete select elements and matching option values",
document: `<form>
<input id="given-name" autocomplete="shipping given-name">
<select id="country" autocomplete="shipping country">
<option value=""></option>
<option value="US">United States</option>
</select>
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
<option value="WA">Washington</option>
</select>
</form>`,
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: `<form>
<input id="given-name" autocomplete="shipping given-name">
<select id="country" autocomplete="shipping country">
<option value=""></option>
<option value="US">United States</option>
</select>
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
<option value="WA">Washington</option>
</select>
</form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "United States",
"address-level1": "California",
},
expectedResult: {
country: "US",
state: "CA",
},
},
{
description: "Fill address fields in a form with addr and CC fields.",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<input id="street-addr" autocomplete="street-address">
<input id="city" autocomplete="address-level2">
<select id="country" autocomplete="country">
<option/>
<option value="US">United States</option>
</select>
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel">
<input id="cc-number" autocomplete="cc-number">
<input id="cc-name" autocomplete="cc-name">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
</form>`,
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 addr and CC fields.",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<input id="street-addr" autocomplete="street-address">
<input id="city" autocomplete="address-level2">
<select id="country" autocomplete="country">
<option/>
<option value="US">United States</option>
</select>
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel">
<input id="cc-number" autocomplete="cc-number">
<input id="cc-name" autocomplete="cc-name">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
</form>`,
focusedInputId: "cc-number",
profileData: {
guid: "123",
"cc-number": "4111111111111111",
"cc-name": "test name",
"cc-exp-month": "06",
"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",
},
},
];
const TESTCASES_INPUT_UNCHANGED = [
{
description:
"Form with autocomplete select elements; with default and no matching options",
document: `<form>
<input id="given-name" autocomplete="shipping given-name">
<select id="country" autocomplete="shipping country">
<option value="US">United States</option>
</select>
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
<option value="WA">Washington</option>
</select>
</form>`,
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: `<form>
<input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">California</option>
</select></form>`,
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: `<form>
<input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="ca">ca</option>
</select></form>`,
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: `<form>
<input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="CA">CA</option>
</select></form>`,
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: `<form>
<input id="given-name" autocomplete="shipping given-name">
<input id="family-name" autocomplete="shipping family-name">
<select id="state" autocomplete="shipping address-level1">
<option value=""></option>
<option value="US-WA">WA-Washington</option>
</select></form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "US",
"address-level1": "WA",
},
expectedResult: {
state: "US-WA",
},
},
// Country
{
description: "Form with country select elements",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<select id="country" autocomplete="country">
<option value=""></option>
<option value="US">United States</option>
</select></form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "US",
},
expectedResult: {
country: "US",
},
},
{
description: "Form with country select elements; with lower case key",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<select id="country" autocomplete="country">
<option value=""></option>
<option value="us">us</option>
</select></form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "US",
},
expectedResult: {
country: "us",
},
},
{
description: "Form with country select elements; with alternative name 1",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<select id="country" autocomplete="country">
<option value=""></option>
<option value="XX">United States</option>
</select></form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "US",
},
expectedResult: {
country: "XX",
},
},
{
description: "Form with country select elements; with alternative name 2",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<select id="country" autocomplete="country">
<option value=""></option>
<option value="XX">America</option>
</select></form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "US",
},
expectedResult: {
country: "XX",
},
},
{
description:
"Form with country select elements; with partial matching value",
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<select id="country" autocomplete="country">
<option value=""></option>
<option value="XX">Ship to America</option>
</select></form>`,
focusedInputId: "given-name",
profileData: {
guid: "123",
country: "US",
},
expectedResult: {
country: "XX",
},
},
];
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);
handler.focusedInput = focusedInput;
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 }
);
}),
];
});