Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/browser/extensions/formautofill/test/browser/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 54 kB image not shown  

Quelle  head.js   Sprache: JAVA

 
"use strict";

const { ManageAddresses } = ChromeUtils.importESModule(
  "chrome://formautofill/content/manageDialog.mjs"
);

const { OSKeyStore } = ChromeUtils.importESModule(
  "resource://gre/modules/OSKeyStore.sys.mjs"
);
const { OSKeyStoreTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/OSKeyStoreTestUtils.sys.mjs"
);

const { FormAutofillParent } = ChromeUtils.importESModule(
  "resource://autofill/FormAutofillParent.sys.mjs"
);

const { AutofillDoorhanger, AddressEditDoorhanger, AddressSaveDoorhanger } =
  ChromeUtils.importESModule(
    "resource://autofill/FormAutofillPrompter.sys.mjs"
  );

const { FormAutofillNameUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs"
);

const { VALID_ADDRESS_FIELDS, VALID_CREDIT_CARD_FIELDS } =
  ChromeUtils.importESModule(
    "resource://autofill/FormAutofillStorageBase.sys.mjs"
  );

const { FormAutofillUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/shared/FormAutofillUtils.sys.mjs"
);

const { Region } = ChromeUtils.importESModule(
  "resource://gre/modules/Region.sys.mjs"
);

let { sinon } = ChromeUtils.importESModule(
  "resource://testing-common/Sinon.sys.mjs"
);

// Always pretend OS Auth is enabled in this dir.
if (
  gTestPath.includes("browser/creditCard") &&
  OSKeyStoreTestUtils.canTestOSKeyStoreLogin() &&
  OSKeyStore.canReauth()
) {
  info("Stubbing out getOSAuthEnabled so it always returns true");
  sinon.stub(FormAutofillUtils, "getOSAuthEnabled").returns(true);
  registerCleanupFunction(() => {
    sinon.restore();
  });
}

const MANAGE_ADDRESSES_DIALOG_URL =
  "chrome://formautofill/content/manageAddresses.xhtml";
const MANAGE_CREDIT_CARDS_DIALOG_URL =
  "chrome://formautofill/content/manageCreditCards.xhtml";
const EDIT_ADDRESS_DIALOG_URL =
  "chrome://formautofill/content/editAddress.xhtml";
const EDIT_CREDIT_CARD_DIALOG_URL =
  "chrome://formautofill/content/editCreditCard.xhtml";
const PRIVACY_PREF_URL = "about:preferences#privacy";

const HTTP_TEST_PATH = "/browser/browser/extensions/formautofill/test/browser/";
const BASE_URL = "http://mochi.test:8888" + HTTP_TEST_PATH;
const BASE_URL_HTTPS = "https://mochi.test" + HTTP_TEST_PATH;
const CROSS_ORIGIN_BASE_URL = "https://example.org" + HTTP_TEST_PATH;
const CROSS_ORIGIN_2_BASE_URL = "https://example.com" + HTTP_TEST_PATH;
const FORM_URL = BASE_URL + "autocomplete_basic.html";
const ADDRESS_FORM_URL =
  "https://example.org" +
  HTTP_TEST_PATH +
  "address/autocomplete_address_basic.html";
const ADDRESS_FORM_WITHOUT_AUTOCOMPLETE_URL =
  "https://example.org" +
  HTTP_TEST_PATH +
  "address/without_autocomplete_address_basic.html";
const ADDRESS_FORM_WITH_PAGE_NAVIGATION_BUTTONS =
  "https://example.org" +
  HTTP_TEST_PATH +
  "address/capture_address_on_page_navigation.html";
const FORM_IFRAME_SANDBOXED_URL =
  "https://example.org" + HTTP_TEST_PATH + "autocomplete_iframe_sandboxed.html";
const CREDITCARD_FORM_URL =
  "https://example.org" +
  HTTP_TEST_PATH +
  "creditCard/autocomplete_creditcard_basic.html";
const CREDITCARD_FORM_IFRAME_URL =
  "https://example.org" +
  HTTP_TEST_PATH +
  "creditCard/autocomplete_creditcard_iframe.html";
const CREDITCARD_FORM_COMBINED_EXPIRY_URL =
  "https://example.org" +
  HTTP_TEST_PATH +
  "creditCard/autocomplete_creditcard_cc_exp_field.html";
const CREDITCARD_FORM_WITHOUT_AUTOCOMPLETE_URL =
  "https://example.org" +
  HTTP_TEST_PATH +
  "creditCard/without_autocomplete_creditcard_basic.html";
const CREDITCARD_FORM_WITH_PAGE_NAVIGATION_BUTTONS =
  "https://example.org" +
  HTTP_TEST_PATH +
  "creditCard/capture_creditCard_on_page_navigation.html";
const EMPTY_URL = "https://example.org" + HTTP_TEST_PATH + "empty.html";

const TOP_LEVEL_HOST = "https://example.com";
const TOP_LEVEL_URL = TOP_LEVEL_HOST + HTTP_TEST_PATH;
const SAME_SITE_URL = "https://test1.example.com" + HTTP_TEST_PATH;
const CROSS_ORIGIN_URL = "https://example.net" + HTTP_TEST_PATH;
const CROSS_ORIGIN_2_URL = "https://example.org" + HTTP_TEST_PATH;

const ENABLED_AUTOFILL_ADDRESSES_PREF =
  "extensions.formautofill.addresses.enabled";
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF =
  "extensions.formautofill.addresses.capture.enabled";
const AUTOFILL_ADDRESSES_AVAILABLE_PREF =
  "extensions.formautofill.addresses.supported";
const ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF =
  "extensions.formautofill.addresses.supportedCountries";
const AUTOFILL_CREDITCARDS_AVAILABLE_PREF =
  "extensions.formautofill.creditCards.supported";
const ENABLED_AUTOFILL_CREDITCARDS_PREF =
  "extensions.formautofill.creditCards.enabled";
const SUPPORTED_COUNTRIES_PREF = "extensions.formautofill.supportedCountries";
const SYNC_USERNAME_PREF = "services.sync.username";
const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
const SYNC_CREDITCARDS_PREF = "services.sync.engine.creditcards";
const SYNC_CREDITCARDS_AVAILABLE_PREF =
  "services.sync.engine.creditcards.available";

// For iframe autofill tests
const SAME_ORIGIN_ALL_FIELDS =
  TOP_LEVEL_URL + "../fixtures/autocomplete_cc_mandatory_embeded.html";
const SAME_ORIGIN_CC_NUMBER =
  TOP_LEVEL_URL + "../fixtures/autocomplete_cc_number_embeded.html";
const SAME_ORIGIN_CC_NAME =
  TOP_LEVEL_URL + "../fixtures/autocomplete_cc_name_embeded.html";
const SAME_ORIGIN_CC_EXP =
  TOP_LEVEL_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
const SAME_ORIGIN_CC_TYPE =
  TOP_LEVEL_URL + "../fixtures/autocomplete_cc_type_embeded.html";

const SAME_SITE_ALL_FIELDS =
  SAME_SITE_URL + "../fixtures/autocomplete_cc_mandatory_embeded.html";
const SAME_SITE_CC_NUMBER =
  SAME_SITE_URL + "../fixtures/autocomplete_cc_number_embeded.html";
const SAME_SITE_CC_NAME =
  SAME_SITE_URL + "../fixtures/autocomplete_cc_name_embeded.html";
const SAME_SITE_CC_EXP =
  SAME_SITE_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
const SAME_SITE_CC_TYPE =
  SAME_SITE_URL + "../fixtures/autocomplete_cc_type_embeded.html";

const CROSS_ORIGIN_ALL_FIELDS =
  CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_mandatory_embeded.html";
const CROSS_ORIGIN_CC_NUMBER =
  CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_number_embeded.html";
const CROSS_ORIGIN_CC_NAME =
  CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_name_embeded.html";
const CROSS_ORIGIN_CC_EXP =
  CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
const CROSS_ORIGIN_CC_TYPE =
  CROSS_ORIGIN_URL + "../fixtures/autocomplete_cc_type_embeded.html";

const CROSS_ORIGIN_2_ALL_FIELDS =
  CROSS_ORIGIN_2_URL +
  "../fixtures/" +
  "autocomplete_cc_mandatory_embeded.html";
const CROSS_ORIGIN_2_CC_NUMBER =
  CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_number_embeded.html";
const CROSS_ORIGIN_2_CC_NAME =
  CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_name_embeded.html";
const CROSS_ORIGIN_2_CC_EXP =
  CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_exp_embeded.html";
const CROSS_ORIGIN_2_CC_TYPE =
  CROSS_ORIGIN_2_URL + "../fixtures/autocomplete_cc_type_embeded.html";

// Test profiles
const TEST_ADDRESS_1 = {
  "given-name""John",
  "additional-name""R.",
  "family-name""Smith",
  organization: "World Wide Web Consortium",
  "street-address""32 Vassar Street\nMIT Room 32-G524",
  "address-level2""Cambridge",
  "address-level1""MA",
  "postal-code""02139",
  country: "US",
  tel: "+16172535702",
  email: "timbl@w3.org",
};

const TEST_ADDRESS_2 = {
  "given-name""Anonymouse",
  "street-address""Some Address",
  country: "US",
};

const TEST_ADDRESS_3 = {
  "given-name""John",
  "street-address""Other Address",
  "postal-code""12345",
};

const TEST_ADDRESS_4 = {
  "given-name""Timothy",
  "family-name""Berners-Lee",
  organization: "World Wide Web Consortium",
  "street-address""32 Vassar Street\nMIT Room 32-G524",
  country: "US",
  email: "timbl@w3.org",
};

// TODO: Number of field less than AUTOFILL_FIELDS_THRESHOLD
//       need to confirm whether this is intentional
const TEST_ADDRESS_5 = {
  tel: "+16172535702",
};

const TEST_ADDRESS_CA_1 = {
  "given-name""John",
  "additional-name""R.",
  "family-name""Smith",
  organization: "Mozilla",
  "street-address""163 W Hastings\nSuite 209",
  "address-level2""Vancouver",
  "address-level1""BC",
  "postal-code""V6B 1H5",
  country: "CA",
  tel: "+17787851540",
  email: "timbl@w3.org",
};

const TEST_ADDRESS_DE_1 = {
  "given-name""John",
  "additional-name""R.",
  "family-name""Smith",
  organization: "Mozilla",
  "street-address":
    "Geb\u00E4ude 3, 4. Obergeschoss\nSchlesische Stra\u00DFe 27",
  "address-level2""Berlin",
  "postal-code""10997",
  country: "DE",
  tel: "+4930983333000",
  email: "timbl@w3.org",
};

const TEST_ADDRESS_IE_1 = {
  "given-name""Bob",
  "additional-name""Z.",
  "family-name""Builder",
  organization: "Best Co.",
  "street-address""123 Kilkenny St.",
  "address-level3""Some Townland",
  "address-level2""Dublin",
  "address-level1""Co. Dublin",
  "postal-code""A65 F4E2",
  country: "IE",
  tel: "+13534564947391",
  email: "ie@example.com",
};

const TEST_CREDIT_CARD_1 = {
  "cc-name""John Doe",
  "cc-number""4111111111111111",
  "cc-exp-month": 4,
  "cc-exp-year"new Date().getFullYear(),
};

const TEST_CREDIT_CARD_2 = {
  "cc-name""Timothy Berners-Lee",
  "cc-number""4929001587121045",
  "cc-exp-month": 12,
  "cc-exp-year"new Date().getFullYear() + 10,
};

const TEST_CREDIT_CARD_3 = {
  "cc-number""5103059495477870",
  "cc-exp-month": 1,
  "cc-exp-year": 2000,
};

const TEST_CREDIT_CARD_4 = {
  "cc-number""5105105105105100",
};

const TEST_CREDIT_CARD_5 = {
  "cc-name""Chris P. Bacon",
  "cc-number""4012888888881881",
};

const MAIN_BUTTON = "button";
const SECONDARY_BUTTON = "secondaryButton";
const MENU_BUTTON = "menubutton";
const EDIT_ADDRESS_BUTTON = "edit";
const ADDRESS_MENU_BUTTON = "addressMenuButton";
const ADDRESS_MENU_LEARN_MORE = "learnMore";
const ADDRESS_MENU_PREFENCE = "preference";

/**
 * Collection of timeouts that are used to ensure something should not happen.
 */

const TIMEOUT_ENSURE_PROFILE_NOT_SAVED = 1000;
const TIMEOUT_ENSURE_CC_DIALOG_NOT_CLOSED = 500;
const TIMEOUT_ENSURE_AUTOCOMPLETE_NOT_SHOWN = 1000;
const TIMEOUT_ENSURE_DOORHANGER_NOT_SHOWN = 1000;

async function ensureCreditCardDialogNotClosed(win) {
  const unloadHandler = () => {
    ok(false"Credit card dialog shouldn't be closed");
  };
  win.addEventListener("unload", unloadHandler);
  await new Promise(resolve =>
    setTimeout(resolve, TIMEOUT_ENSURE_CC_DIALOG_NOT_CLOSED)
  );
  win.removeEventListener("unload", unloadHandler);
}

function getDisplayedPopupItems(
  browser,
  selector = ".autocomplete-richlistitem"
) {
  info("getDisplayedPopupItems");
  const {
    autoCompletePopup: { richlistbox: itemsBox },
  } = browser;
  const listItemElems = itemsBox.querySelectorAll(selector);

  return [...listItemElems].filter(
    item => item.getAttribute("collapsed") != "true"
  );
}

async function sleep(ms = 500) {
  await new Promise(resolve => setTimeout(resolve, ms));
}

async function ensureNoAutocompletePopup(browser) {
  await new Promise(resolve =>
    setTimeout(resolve, TIMEOUT_ENSURE_AUTOCOMPLETE_NOT_SHOWN)
  );
  const items = getDisplayedPopupItems(browser);
  ok(!items.length, "Should not find autocomplete items");
}

async function ensureNoDoorhanger() {
  await new Promise(resolve =>
    setTimeout(resolve, TIMEOUT_ENSURE_DOORHANGER_NOT_SHOWN)
  );

  let notifications = PopupNotifications.panel.childNodes;
  ok(!notifications.length, "Should not find a doorhanger");
}

/**
 * Wait for "formautofill-storage-changed" events
 *
 * @param {Array<string>} eventTypes
 *        eventType must be one of the following:
 *        `add`, `update`, `remove`, `notifyUsed`, `removeAll`, `reconcile`
 *
 * @returns {Promise} resolves when all events are received
 */

async function waitForStorageChangedEvents(...eventTypes) {
  return Promise.all(
    eventTypes.map(type =>
      TestUtils.topicObserved(
        "formautofill-storage-changed",
        (subject, data) => {
          return data == type;
        }
      )
    )
  );
}

/**
 * Wait until the element found matches the expected autofill value
 *
 * @param {object} target
 *        The target in which to run the task.
 * @param {string} selector
 *        A selector used to query the element.
 * @param {string} value
 *        The expected autofilling value for the element
 */

async function waitForAutofill(target, selector, value) {
  await SpecialPowers.spawn(
    target,
    [selector, value],
    async function (selector, val) {
      await ContentTaskUtils.waitForCondition(() => {
        let element = content.document.querySelector(selector);
        return element.value == val;
      }, "Autofill never fills");
    }
  );
}

/**
 * Waits for the subDialog to be loaded
 *
 * @param {Window} win The window of the dialog
 * @param {string} dialogUrl The url of the dialog that we are waiting for
 *
 * @returns {Promise} resolves when the sub dialog is loaded
 */

function waitForSubDialogLoad(win, dialogUrl) {
  return new Promise(resolve => {
    win.gSubDialog._dialogStack.addEventListener(
      "dialogopen",
      async function dialogopen(evt) {
        let cwin = evt.detail.dialog._frame.contentWindow;
        if (cwin.location != dialogUrl) {
          return;
        }
        content.gSubDialog._dialogStack.removeEventListener(
          "dialogopen",
          dialogopen
        );

        resolve(cwin);
      }
    );
  });
}

/**
 * Use this function when you want to update the value of elements in
 * a form and then submit the form. This function makes sure the form
 * is "identified" (`identifyAutofillFields` is called) before submitting
 * the form.
 * This is guaranteed by first focusing on an element in the form to trigger
 * the 'FormAutofill:FieldsIdentified' message.
 *
 * @param {object} target
 *        The target in which to run the task.
 * @param {object} args
 * @param {string} args.focusSelector
 *        A selector used to query the element to be focused
 * @param {string} args.formId
 *        The id of the form to be updated. This function uses "form" if
 *        this argument is not present
 * @param {string} args.formSelector
 *        A selector used to query the form element
 * @param {object} args.newValues
 *        Elements to be updated. Key is the element selector, value is the
 *        new value of the element.
 *
 * @param {boolean} submit
 *        Set to true to submit the form after the task is done, false otherwise.
 */

async function focusUpdateSubmitForm(target, args, submit = true) {
  let fieldsIdentifiedPromiseResolver;
  let fieldsIdentifiedObserver = {
    fieldsIdentified() {
      FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
      fieldsIdentifiedPromiseResolver();
    },
  };

  let fieldsIdentifiedPromise = new Promise(resolve => {
    fieldsIdentifiedPromiseResolver = resolve;
    FormAutofillParent.addMessageObserver(fieldsIdentifiedObserver);
  });

  let alreadyFocused = await SpecialPowers.spawn(target, [args], obj => {
    let focused = false;

    let form;
    if (obj.formSelector) {
      form = content.document.querySelector(obj.formSelector);
    } else {
      form = content.document.getElementById(obj.formId ?? "form");
    }
    form ||= content.document;

    let element = form.querySelector(obj.focusSelector);
    if (element != content.document.activeElement) {
      info(`focus on element (id=${element.id})`);
      element.focus();
    } else {
      focused = true;
    }

    for (const [selector, value] of Object.entries(obj.newValues)) {
      element = form.querySelector(selector);
      if (content.HTMLInputElement.isInstance(element)) {
        element.setUserInput(value);
      } else if (
        content.HTMLSelectElement.isInstance(element) &&
        Array.isArray(value)
      ) {
        element.multiple = true;
        [...element.options].forEach(option => {
          option.selected = value.includes(option.value);
        });
      } else {
        element.value = value;
      }
    }

    return focused;
  });

  if (alreadyFocused) {
    // If the element is already focused, assume the FieldsIdentified message
    // was sent before.
    FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
    fieldsIdentifiedPromiseResolver();
  }

  await fieldsIdentifiedPromise;

  if (submit) {
    await SpecialPowers.spawn(target, [args], obj => {
      let form;
      if (obj.formSelector) {
        form = content.document.querySelector(obj.formSelector);
      } else {
        form = content.document.getElementById(obj.formId ?? "form");
      }
      form ||= content.document;
      info(`submit form (id=${form.id})`);
      form.querySelector("input[type=submit]").click();
    });
  }
}

async function focusAndWaitForFieldsIdentified(browserOrContext, selector) {
  info("expecting the target input being focused and identified");
  /* eslint no-shadow: ["error", { "allow": ["selector", "previouslyFocused", "previouslyIdentified"] }] */

  // If the input is previously focused, no more notifications will be
  // sent as the notification goes along with focus event.
  let fieldsIdentifiedPromiseResolver;
  let fieldsIdentifiedObserver = {
    fieldsIdentified() {
      fieldsIdentifiedPromiseResolver();
    },
  };

  let fieldsIdentifiedPromise = new Promise(resolve => {
    fieldsIdentifiedPromiseResolver = resolve;
    FormAutofillParent.addMessageObserver(fieldsIdentifiedObserver);
  });

  const { previouslyFocused, previouslyIdentified } = await SpecialPowers.spawn(
    browserOrContext,
    [selector],
    async function (selector) {
      const { FormLikeFactory } = ChromeUtils.importESModule(
        "resource://gre/modules/FormLikeFactory.sys.mjs"
      );
      const input = content.document.querySelector(selector);
      const rootElement = FormLikeFactory.findRootForField(input);
      const previouslyFocused = content.document.activeElement == input;
      const previouslyIdentified = rootElement.hasAttribute(
        "test-formautofill-identified"
      );

      input.focus();

      return { previouslyFocused, previouslyIdentified };
    }
  );

  // Only wait for the fields identified notification if the
  // focus was not previously assigned to the input.
  if (previouslyFocused) {
    fieldsIdentifiedPromiseResolver();
  } else {
    info("!previouslyFocused");
  }

  // If a browsing context was supplied, focus its parent frame as well.
  if (
    BrowsingContext.isInstance(browserOrContext) &&
    browserOrContext.parent != browserOrContext
  ) {
    await SpecialPowers.spawn(
      browserOrContext.parent,
      [browserOrContext],
      async function (browsingContext) {
        browsingContext.embedderElement.focus();
      }
    );
  }

  if (previouslyIdentified) {
    info("previouslyIdentified");
    FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);
    return previouslyFocused;
  }

  // Wait 500ms to ensure that "markAsAutofillField" is completely finished.
  await fieldsIdentifiedPromise;
  info("FieldsIdentified");
  FormAutofillParent.removeMessageObserver(fieldsIdentifiedObserver);

  await sleep();
  await SpecialPowers.spawn(browserOrContext, [], async function () {
    const { FormLikeFactory } = ChromeUtils.importESModule(
      "resource://gre/modules/FormLikeFactory.sys.mjs"
    );
    FormLikeFactory.findRootForField(
      content.document.activeElement
    ).setAttribute("test-formautofill-identified""true");
  });

  return previouslyFocused;
}

/**
 * Run the task and wait until the autocomplete popup is opened.
 *
 * @param {object} browser A xul:browser.
 * @param {Function} taskFunction Task that will trigger the autocomplete popup
 */

async function runAndWaitForAutocompletePopupOpen(browser, taskFunction) {
  info("runAndWaitForAutocompletePopupOpen");
  let popupShown = BrowserTestUtils.waitForPopupEvent(
    browser.autoCompletePopup,
    "shown"
  );

  // Run the task will open the autocomplete popup
  await taskFunction();

  await popupShown;
}

async function waitForPopupEnabled(browser) {
  const {
    autoCompletePopup: { richlistbox: itemsBox },
  } = browser;
  info("Wait for list elements to become enabled");
  await BrowserTestUtils.waitForMutationCondition(
    itemsBox,
    { subtree: true, attributes: true, attributeFilter: ["disabled"] },
    () => !itemsBox.querySelectorAll(".autocomplete-richlistitem")[0].disabled
  );
}

// Wait for the popup state change notification to happen.
async function waitForAutoCompletePopupOpen(browser, taskFunction) {
  const popupShown = BrowserTestUtils.waitForPopupEvent(
    browser.autoCompletePopup,
    "shown"
  );

  if (taskFunction) {
    await taskFunction();
  }

  return popupShown;
}

async function openPopupOn(browser, selector) {
  await SimpleTest.promiseFocus(browser);

  await runAndWaitForAutocompletePopupOpen(browser, async () => {
    const previouslyFocused = await focusAndWaitForFieldsIdentified(
      browser,
      selector
    );
    // If the field is already focused, we need to send a key event to
    // open the popup
    if (previouslyFocused || !selector.includes("cc-")) {
      info(`openPopupOn: before VK_DOWN on ${selector}`);
      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
    }
  });
}

async function openPopupOnSubframe(browser, frameBrowsingContext, selector) {
  await SimpleTest.promiseFocus(browser);

  await runAndWaitForAutocompletePopupOpen(browser, async () => {
    const previouslyFocused = await focusAndWaitForFieldsIdentified(
      frameBrowsingContext,
      selector
    );
    // If the field is already focused, we need to send a key event to
    // open the popup
    if (previouslyFocused || !selector.includes("cc-")) {
      info(`openPopupOnSubframe: before VK_DOWN on ${selector}`);
      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, frameBrowsingContext);
    }
  });
}

async function closePopup(browser) {
  // Return if the popup isn't open.
  if (!browser.autoCompletePopup.popupOpen) {
    return;
  }

  let popupClosePromise = BrowserTestUtils.waitForPopupEvent(
    browser.autoCompletePopup,
    "hidden"
  );

  await SpecialPowers.spawn(browser, [], async function () {
    content.document.activeElement.blur();
  });

  await popupClosePromise;
}

async function closePopupForSubframe(browser, frameBrowsingContext) {
  // Return if the popup isn't open.
  if (!browser.autoCompletePopup.popupOpen) {
    return;
  }

  const popupClosePromise = BrowserTestUtils.waitForPopupEvent(
    browser.autoCompletePopup,
    "hidden"
  );

  await SpecialPowers.spawn(frameBrowsingContext, [], async function () {
    content.document.activeElement.blur();
  });

  await popupClosePromise;
}

function emulateMessageToBrowser(name, data) {
  let actor =
    gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor(
      "FormAutofill"
    );

  return actor.receiveMessage({ name, data });
}

function getRecords(data) {
  info(`expecting record retrievals: ${data.collectionName}`);
  return emulateMessageToBrowser("FormAutofill:GetRecords", data).then(
    result => result.records
  );
}

function getAddresses() {
  return getRecords({ collectionName: "addresses" });
}

async function ensureNoAddressSaved() {
  await new Promise(resolve =>
    setTimeout(resolve, TIMEOUT_ENSURE_PROFILE_NOT_SAVED)
  );
  const addresses = await getAddresses();
  is(addresses.length, 0, "No address was saved");
}

function getCreditCards() {
  return getRecords({ collectionName: "creditCards" });
}

async function saveAddress(address) {
  info("expecting address saved");
  let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
  await emulateMessageToBrowser("FormAutofill:SaveAddress", { address });
  await observePromise;
}

async function saveCreditCard(creditcard) {
  info("expecting credit card saved");
  let creditcardClone = Object.assign({}, creditcard);
  let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
  await emulateMessageToBrowser("FormAutofill:SaveCreditCard", {
    creditcard: creditcardClone,
  });
  await observePromise;
}

async function removeAddresses(guids) {
  info("expecting address removed");
  let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
  await emulateMessageToBrowser("FormAutofill:RemoveAddresses", { guids });
  await observePromise;
}

async function removeCreditCards(guids) {
  info("expecting credit card removed");
  let observePromise = TestUtils.topicObserved("formautofill-storage-changed");
  await emulateMessageToBrowser("FormAutofill:RemoveCreditCards", { guids });
  await observePromise;
}

function getNotification(index = 0) {
  let notifications = PopupNotifications.panel.childNodes;
  ok(!!notifications.length, "at least one notification displayed");
  ok(true, notifications.length + " notification(s)");
  return notifications[index];
}

function waitForPopupShown() {
  return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
}

/**
 * Clicks the popup notification button and wait for popup hidden.
 *
 * @param {string} buttonType The button type in popup notification.
 * @param {number} index The action's index in menu list.
 */

async function clickDoorhangerButton(buttonType, index = 0) {
  let popuphidden = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popuphidden"
  );

  let button;
  if (buttonType == MAIN_BUTTON || buttonType == SECONDARY_BUTTON) {
    button = getNotification()[buttonType];
  } else if (buttonType == MENU_BUTTON) {
    // Click the dropmarker arrow and wait for the menu to show up.
    info("expecting notification menu button present");
    await BrowserTestUtils.waitForCondition(() => getNotification().menubutton);
    await sleep(2000); // menubutton needs extra time for binding
    let notification = getNotification();

    ok(notification.menubutton, "notification menupopup displayed");
    let dropdownPromise = BrowserTestUtils.waitForEvent(
      notification.menupopup,
      "popupshown"
    );

    notification.menubutton.click();
    info("expecting notification popup show up");
    await dropdownPromise;

    button = notification.querySelectorAll("menuitem")[index];
  }

  button.click();
  info("expecting notification popup hidden");
  await popuphidden;
}

async function clickAddressDoorhangerButton(buttonType, subType) {
  const notification = getNotification();
  let button;
  if (buttonType == EDIT_ADDRESS_BUTTON) {
    button = AddressSaveDoorhanger.editButton(notification);
  } else if (buttonType == ADDRESS_MENU_BUTTON) {
    const menu = AutofillDoorhanger.menuButton(notification);
    const menupopup = AutofillDoorhanger.menuPopup(notification);
    const promise = BrowserTestUtils.waitForEvent(menupopup, "popupshown");
    menu.click();
    await promise;
    if (subType == ADDRESS_MENU_PREFENCE) {
      button = AutofillDoorhanger.preferenceButton(notification);
    } else if (subType == ADDRESS_MENU_LEARN_MORE) {
      button = AutofillDoorhanger.learnMoreButton(notification);
    }
  } else {
    await clickDoorhangerButton(buttonType);
    return;
  }

  EventUtils.synthesizeMouseAtCenter(button, {});
}

function getDoorhangerCheckbox() {
  return getNotification().checkbox;
}

function getDoorhangerButton(button) {
  return getNotification()[button];
}

/**
 * Removes all addresses and credit cards from storage.
 *
 * **NOTE: If you add or update a record in a test, then you must wait for the
 * respective storage event to fire before calling this function.**
 * This is because this function doesn't guarantee that a record that
 * is about to be added or update will also be removed,
 * since the add or update is triggered by an asynchronous call.
 *
 * @see waitForStorageChangedEvents for more details about storage events to wait for
 */

async function removeAllRecords() {
  let addresses = await getAddresses();
  if (addresses.length) {
    await removeAddresses(addresses.map(address => address.guid));
  }
  let creditCards = await getCreditCards();
  if (creditCards.length) {
    await removeCreditCards(creditCards.map(cc => cc.guid));
  }
}

async function waitForFocusAndFormReady(win) {
  return Promise.all([
    new Promise(resolve => waitForFocus(resolve, win)),
    BrowserTestUtils.waitForEvent(win, "FormReadyForTests"),
  ]);
}

// Verify that the warning in the autocomplete popup has the expected text.
async function expectWarningText(browser, expectedText) {
  const {
    autoCompletePopup: { richlistbox: itemsBox },
  } = browser;
  let warningBox = itemsBox.querySelector(".ac-status");
  ok(warningBox.parentNode.disabled, "Got warning box and is disabled");

  await BrowserTestUtils.waitForMutationCondition(
    warningBox,
    { childList: true, characterData: true },
    () => warningBox.textContent == expectedText
  );
  ok(true, `Got expected warning text: ${expectedText}`);
}

async function testDialog(url, testFn, arg = undefined) {
  // Skip this step for test cards that lack an encrypted
  // number since they will fail to decrypt.
  if (
    url == EDIT_CREDIT_CARD_DIALOG_URL &&
    arg &&
    arg.record &&
    arg.record["cc-number-encrypted"]
  ) {
    arg.record = Object.assign({}, arg.record, {
      "cc-number": await OSKeyStore.decrypt(arg.record["cc-number-encrypted"]),
    });
  }
  const win = window.openDialog(url, null"width=600,height=600", {
    ...arg,
    l10nStrings: ManageAddresses.getAddressL10nStrings(),
  });
  await waitForFocusAndFormReady(win);
  const unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
  await testFn(win);
  return unloadPromise;
}

/**
 * Initializes the test storage for a task.
 *
 * @param {...object} items Can either be credit card or address objects
 */

async function setStorage(...items) {
  for (let item of items) {
    if (item["cc-number"]) {
      await saveCreditCard(item);
    } else {
      await saveAddress(item);
    }
  }
}

function verifySectionAutofillResult(section, result, expectedSection) {
  const fieldDetails = section.fieldDetails;
  const expectedFieldDetails = expectedSection.fields;

  fieldDetails.forEach((field, fieldIndex) => {
    const expected = expectedFieldDetails[fieldIndex];
    Assert.equal(
      result.get(field.elementId).value,
      expected.autofill ?? "",
      `Autofilled value for element(identifier:${field.identifier}, field name:${field.fieldName}) should be equal`
    );
  });
}

function getSelectorFromFieldDetail(fieldDetail) {
  // identifier is set with `${element.id}/${element.name}`;
  const id = fieldDetail.identifier.split("/")[0];
  return `input#${id}, select#${id}`;
}

/**
 * Discards all recorded Glean telemetry in parent and child processes
 * and resets FOG and the Glean SDK.
 *
 * @param {boolean} onlyInParent Whether we only discard the metric data in the parent process
 *
 * Since the current method Services.fog.testResetFOG only discards metrics recorded in the parent process,
 * we would like to keep this option in our method as well.
 */

async function clearGleanTelemetry(onlyInParent = false) {
  if (!onlyInParent) {
    await Services.fog.testFlushAllChildren();
  }
  Services.fog.testResetFOG();
}

function fillEditDoorhanger(record) {
  const notification = getNotification();

  for (const [key, value] of Object.entries(record)) {
    const id = AddressEditDoorhanger.getInputId(key);
    const element = notification.querySelector(`#${id}`);
    element.value = value;
  }
}

// TODO: This function should be removed. We should make normalizeFields in
// FormAutofillStorageBase.sys.mjs static and using it directly
function normalizeAddressFields(record) {
  let normalized = { ...record };

  if (normalized.name != undefined) {
    let nameParts = FormAutofillNameUtils.splitName(normalized.name);
    normalized["given-name"] = nameParts.given;
    normalized["additional-name"] = nameParts.middle;
    normalized["family-name"] = nameParts.family;
    delete normalized.name;
  }
  return normalized;
}

async function verifyConfirmationHint(
  browser,
  forceClose,
  anchorID = "identity-icon-box"
) {
  let hintElem = browser.ownerGlobal.ConfirmationHint._panel;
  let popupshown = BrowserTestUtils.waitForPopupEvent(hintElem, "shown");
  let popuphidden;

  if (!forceClose) {
    popuphidden = BrowserTestUtils.waitForPopupEvent(hintElem, "hidden");
  }

  await popupshown;
  try {
    Assert.equal(hintElem.state, "open""hint popup is open");
    Assert.ok(
      BrowserTestUtils.isVisible(hintElem.anchorNode),
      "hint anchorNode is visible"
    );
    Assert.equal(
      hintElem.anchorNode.id,
      anchorID,
      "Hint should be anchored on the expected notification icon"
    );
    info("verifyConfirmationHint, hint is shown and has its anchorNode");
    if (forceClose) {
      await closePopup(hintElem);
    } else {
      info("verifyConfirmationHint, assertion ok, wait for poopuphidden");
      await popuphidden;
      info("verifyConfirmationHint, hintElem popup is hidden");
    }
  } catch (ex) {
    Assert.ok(false"Confirmation hint not shown: " + ex.message);
  } finally {
    info("verifyConfirmationHint promise finalized");
  }
}

async function showAddressDoorhanger(browser, values = null) {
  const defaultValues = {
    "#given-name""John",
    "#family-name""Doe",
    "#organization""Mozilla",
    "#street-address""123 Sesame Street",
  };

  const onPopupShown = waitForPopupShown();
  const promise = BrowserTestUtils.browserLoaded(browser);
  await focusUpdateSubmitForm(browser, {
    focusSelector: "#given-name",
    newValues: values ?? defaultValues,
  });
  await promise;
  await onPopupShown;
}

async function findContext(browser, selector) {
  const contexts =
    browser.browsingContext.top.getAllBrowsingContextsInSubtree();
  for (const context of contexts) {
    const find = await SpecialPowers.spawn(
      context,
      [selector],
      async selector => {
        // This is a workaround to address issues when there are multiple
        // elements with the same id and name on a page. This is a common pattern
        // when sites use multiple iframes for credit card fields. Each iframe
        // contains all the CC-related fields, but only one of the fields is visible.
        // TODO: replace the following with an approach that can precisely find the
        // element we want without basing on visibility.
        const e = content.document.querySelector(selector);
        if (e && content.HTMLInputElement.isInstance(e)) {
          return !!(
            e.checkVisibility({
              checkOpacity: true,
              checkVisibilityCSS: true,
            }) && e.getAttribute("aria-hidden") != "true"
          );
        }
        return !!e;
      }
    );
    if (find) {
      return context;
    }
  }

  return null;
}

async function verifyCaptureRecord(guid, expectedRecord) {
  let fields;
  let record = (await getAddresses()).find(addr => addr.guid == guid);
  if (record) {
    fields = VALID_ADDRESS_FIELDS;
  } else {
    record = (await getCreditCards()).find(cc => cc.guid == guid);
    if (record) {
      fields = VALID_CREDIT_CARD_FIELDS;
    } else {
      Assert.ok(false"Cannot find record by guid");
    }
  }

  for (const field of fields) {
    Assert.equal(record[field], expectedRecord[field], `${field} is the same`);
  }
}

// Compare the saved addresses with the expected addresses.
async function expectSavedAddresses(expectedAddresses) {
  const addresses = await expectSavedAddressesCount(expectedAddresses.length);

  for (let i = 0; i < expectedAddresses.length; i++) {
    for (const [key, value] of Object.entries(expectedAddresses[i])) {
      is(addresses[i][key] ?? "", value, `field ${key} should be equal`);
    }
  }
  return addresses;
}

// Compare the number of saved addresses with the expected saved address count.
async function expectSavedAddressesCount(expectedCount) {
  const addresses = await getAddresses();
  is(
    addresses.length,
    expectedCount,
    `${addresses.length} address in the storage`
  );
  return addresses;
}

async function verifyPreviewResult(browser, section, expectedSection) {
  info(`Verify preview result`);
  const fieldDetails = section.fieldDetails;
  const expectedFieldDetails = expectedSection.fields;

  for (let i = 0; i < fieldDetails.length; i++) {
    const selector = getSelectorFromFieldDetail(fieldDetails[i]);
    const context = await findContext(browser, selector);
    let expected = expectedFieldDetails[i].autofill ?? "";
    if (fieldDetails[i].fieldName == "cc-number" && expected.length) {
      expected = "•".repeat(expected.length - 4) + expected.slice(-4);
    }

    await SpecialPowers.spawn(context, [{ expected, selector }], async obj => {
      const element = content.document.querySelector(obj.selector);
      if (content.HTMLSelectElement.isInstance(element)) {
        if (obj.expected) {
          for (let idx = 0; idx < element.options.length; idx++) {
            if (element.options[idx].value == obj.expected) {
              obj.expected = element.options[idx].text;
              break;
            }
          }
        } else {
          obj.expected = "";
        }
      }
      Assert.equal(
        element.previewValue,
        obj.expected,
        `element ${obj.selector} previewValue is the same ${element.previewValue}`
      );
    });
  }
}

async function verifyAutofillResult(browser, section, expectedSection) {
  info(`Verify autofill result`);
  const fieldDetails = section.fieldDetails;
  const expectedFieldDetails = expectedSection.fields;

  for (let i = 0; i < fieldDetails.length; i++) {
    const selector = getSelectorFromFieldDetail(fieldDetails[i]);
    const context = await findContext(browser, selector);
    const expected = expectedFieldDetails[i].autofill ?? "";
    await SpecialPowers.spawn(context, [{ expected, selector }], async obj => {
      const element = content.document.querySelector(obj.selector);
      if (content.HTMLSelectElement.isInstance(element)) {
        if (!obj.expected) {
          obj.expected = element.options[0].value;
        }
      }
      Assert.equal(
        element.value,
        obj.expected,
        `element ${obj.selector} value is the same ${element.value}`
      );
    });
  }
}

async function verifyClearResult(browser, section) {
  info(`Verify clear form result`);
  const fieldDetails = section.fieldDetails;

  for (let i = 0; i < fieldDetails.length; i++) {
    const selector = getSelectorFromFieldDetail(fieldDetails[i]);
    const context = await findContext(browser, selector);
    const expected = "";
    await SpecialPowers.spawn(context, [{ expected, selector }], async obj => {
      const element = content.document.querySelector(obj.selector);
      if (content.HTMLSelectElement.isInstance(element)) {
        obj.expected = element.options[0].value;
      }
      Assert.equal(
        element.value,
        obj.expected,
        `element ${obj.selector} value is the same ${element.value}`
      );
    });
  }
}

function verifySectionFieldDetails(sections, expectedSectionsInfo) {
  sections.forEach((section, index) => {
    const expectedSection = expectedSectionsInfo[index];

    const fieldDetails = section.fieldDetails;
    const expectedFieldDetails = expectedSection.fields;

    info(`section[${index}] ${expectedSection.description ?? ""}:`);
    info(`FieldName Prediction Results: ${fieldDetails.map(i => i.fieldName)}`);
    info(
      `FieldName Expected Results:   ${expectedFieldDetails.map(
        detail => detail.fieldName
      )}`
    );
    Assert.equal(
      fieldDetails.length,
      expectedFieldDetails.length,
      `Expected field count.`
    );

    fieldDetails.forEach((fieldDetail, fieldIndex) => {
      const expectedFieldDetail = expectedFieldDetails[fieldIndex];

      const expected = {
        ...{
          reason: "autocomplete",
          section: "",
          contactType: "",
          addressType: "",
          part: undefined,
        },
        ...expectedSection.default,
        ...expectedFieldDetail,
      };

      const keys = [
        "reason",
        "section",
        "contactType",
        "addressType",
        "fieldName",
        "part",
      ];

      for (const key of keys) {
        const expectedValue = expected[key];
        const actualValue = fieldDetail[key];
        Assert.equal(
          actualValue,
          expectedValue,
          `[${fieldDetail.fieldName}]: ${key} should be equal, expect ${expectedValue}, got ${actualValue}`
        );
      }
    });

    Assert.equal(
      section.isValidSection(),
      !expectedSection.invalid,
      `Should be an ${expectedSection.invalid ? "invalid" : "valid"} section`
    );
  });
}

async function triggerAutofillAndPreview(
  browser,
  selector,
  previewCallback,
  autofillCallback,
  clearCallback
) {
  const focusedContext = await findContext(browser, selector);

  if (focusedContext == focusedContext.top) {
    info(`Open the popup`);
    await openPopupOn(browser, selector);
  } else {
    info(`Open the popup on subframe`);
    await openPopupOnSubframe(browser, focusedContext, selector);
  }

  // Preview
  info(`Send key down to trigger preview`);
  let promise = TestUtils.topicObserved("formautofill-preview-complete");
  const firstItem = getDisplayedPopupItems(browser)[0];
  if (!firstItem.selected) {
    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, focusedContext);
  }
  await promise;
  await previewCallback();

  // Autofill
  info(`Send key return to trigger autofill`);

  promise = TestUtils.topicObserved("formautofill-autofill-complete");
  await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, focusedContext);

  await promise;
  await autofillCallback();

  // Clear Form
  if (focusedContext == focusedContext.top) {
    info(`Open the popup again for clearing form`);
    await openPopupOn(browser, selector);
  } else {
    info(`Open the popup on subframe again for clearing form`);
    await openPopupOnSubframe(browser, focusedContext, selector);
  }

  info(`Send key down and return to clear form`);
  promise = TestUtils.topicObserved("formautofill-clear-form-complete");
  await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, focusedContext);
  await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, focusedContext);
  await promise;
  await clearCallback();
}

async function triggerCapture(browser, submitButtonSelector, fillSelectors) {
  for (const [selector, value] of Object.entries(fillSelectors)) {
    const context = await findContext(browser, selector);
    await SpecialPowers.spawn(context, [{ selector, value }], obj => {
      const element = content.document.querySelector(obj.selector);
      if (content.HTMLInputElement.isInstance(element)) {
        element.setUserInput(obj.value);
      } else if (
        content.HTMLSelectElement.isInstance(element) &&
        Array.isArray(obj.value)
      ) {
        element.multiple = true;
        [...element.options].forEach(option => {
          option.selected = obj.value.includes(option.value);
        });
      } else {
        element.value = obj.value;
      }
    });
  }

  const onAdded = waitForStorageChangedEvents("add");
  const onPopupShown = waitForPopupShown();
  submitButtonSelector ||= "input[type=submit]";
  const context = await findContext(browser, submitButtonSelector);
  await SpecialPowers.spawn(context, [submitButtonSelector], selector => {
    content.document.querySelector(selector).click();
  });
  await onPopupShown;
  await clickDoorhangerButton(MAIN_BUTTON);

  const [subject] = (await onAdded)[0];
  return subject.wrappedJSObject.guid;
}

/**
 * Runs heuristics test for form autofill on given patterns.
 *
 * @param {Array<object>} patterns
 *        An array of test patterns to run the heuristics test on.
 * @param {string} patterns.description
 *        Description of this heuristic test
 * @param {string} patterns.fixurePath
 *        The path of the test document
 * @param {string} patterns.fixureData
 *        Test document by string. Use either fixurePath or fixtureData.
 * @param {Array} patterns.prefs
 *        Array of preferences to be set before running the test.
 * @param {object} patterns.profile
 *        The profile to autofill. This is required only when running autofill test
 * @param {Array} patterns.region
 *        Region to assign before running the test
 * @param {Array} patterns.expectedResult
 *        The expected result of this heuristic test. See below for detailed explanation
 * @param {Function} patterns.onTestStart
 *        Function that is executed before the test starts. This runs after the form
 *        field has been focused.
 * @param {Function} patterns.onTestComplete
 *        Function that is executed when the test is complete, but before the tab is closed.
 *        This can be used by the test to verify the status after running the test.
 *
 * @param {string} patterns.autofillTrigger
 *        The selector to find the element to trigger the autocomplete popup.
 *        Currently we only supports id selector so the value must start with `#`.
 *        This parameter is only used when `options.testAutofill` is set.
 *
 * @param {string} patterns.submitButtonSelector
 *        The selector to find the submit button for capture test. This parameter
 *        is only used when `options.testCapture` is set.
 * @param {object} patterns.captureFillValue
 *        An object that is keyed by selector, and the value to be set for the element
 *        that is found by matching selector before submitting the form. This parameter
 *        is only used when `options.testCapture` is set.
 * @param {object} patterns.captureExpectedRecord
 *        The expected saved record after capturing the form. Keyed by field name. This
 *        parameter is only used when `options.testCapture` is set.
 * @param {object} patterns.only
 *        This parameter is used solely for debugging purposes. When set to true,
 *        it restricts the execution to only the specified testcase.
 *
 * @param {string} [fixturePathPrefix=""]
 *        The prefix to the path of fixture files.
 * @param {object} [options={ testAutofill: false, testCapture: false }]
 *        An options object containing additional configuration for running the test.
 * @param {boolean} [options.testAutofill]
 *        When set to true, the following tests will be run:
 *        1. Trigger preview and verify the preview result
 *        2. Trigger autofill and verify the autofill result
 *        3. Trigger clear form and verify the clear result
 * @param {boolean} [options.testCapture]
 *        When set to true, the test submits the form after autofilling test finishes.
 *        Before submitting the form, the test first filles value if `captureFillValue`
 *        is set then submits the form. This test then verifies that the capture
 *        doorhanger appears, and the doorhanger captures the expected value (captureExpectedRecord).
 *
 * @returns {Promise} A promise that resolves when all the tests are completed.
 *
 * The `patterns.expectedResult` array contains test data for different address or credit card sections.
 * Each section in the array is represented by an object and can include the following properties:
 * - description (optional): A string describing the section, primarily used for debugging purposes.
 * - default (optional): An object that sets the default values for all the fields within this section.
 *            The default object contains the same keys as the individual field objects.
 * - fields: An array of field details (class FieldDetails) within the section.
 *
 * Each field object can have the following keys:
 * - fieldName: The name of the field (e.g., "street-name", "cc-name" or "cc-number").
 * - reason: The reason for the field value (e.g., "autocomplete", "regex-heuristic" or "fathom").
 * - section: The section to which the field belongs (e.g., "billing", "shipping").
 * - part: The part of the field.
 * - contactType: The contact type of the field.
 * - addressType: The address type of the field.
 * - autofill: Set the expected autofill value when running autofill test
 *
 * For more information on the field object properties, refer to the FieldDetails class.
 *
 * Example test data:
 * add_heuristic_tests(
 * [{
 *   description: "first test pattern",
 *   fixuturePath: "autocomplete_off.html",
 *   profile: {organization: "Mozilla", country: "US", tel: "123"},
 *   expectedResult: [
 *   {
 *     description: "First section"
 *     fields: [
 *       { fieldName: "organization", reason: "autocomplete", autofill: "Mozilla" },
 *       { fieldName: "country", reason: "regex-heuristic", autofill: "US" },
 *       { fieldName: "tel", reason: "regex-heuristic", autofill: "123" },
 *     ]
 *   },
 *   {
 *     default: {
 *       reason: "regex-heuristic",
 *       section: "billing",
 *     },
 *     fields: [
 *       { fieldName: "cc-number", reason: "fathom" },
 *       { fieldName: "cc-nane" },
 *       { fieldName: "cc-exp" },
 *     ],
 *    }],
 *  },
 *  {
 *    // second test pattern //
 *  }
 * ],
 * "/fixturepath",
 * {
 *   testAutofill: true,
 *   testCapture: true,
 * }  // test options
 * )
 */

async function add_heuristic_tests(
  patterns,
  fixturePathPrefix = "",
  options = { testAutofill: false, testCapture: false }
) {
  async function runTest(testPattern) {
    const TEST_URL = testPattern.fixtureData
      ? TOP_LEVEL_HOST +
        `/document-builder.sjs?html=${encodeURIComponent(
          testPattern.fixtureData
        )}`
      : `${TOP_LEVEL_URL}../${fixturePathPrefix}${testPattern.fixturePath}`;

    info(`Test "${testPattern.description}"`);

    let regionInfo = null;
    if (testPattern.region) {
      regionInfo = { home: Region._home, current: Region._current };

      const region = testPattern.region;
      Region._setCurrentRegion(region);
      Region._setHomeRegion(region);
    }

    if (testPattern.prefs) {
      await SpecialPowers.pushPrefEnv({
        set: testPattern.prefs,
      });
    }

    if (testPattern.profile) {
      await setStorage(testPattern.profile);
    }

    await BrowserTestUtils.withNewTab(TEST_URL, async browser => {
      await SimpleTest.promiseFocus(browser);

      info(`Focus on each field in the test document`);
      const contexts =
        browser.browsingContext.getAllBrowsingContextsInSubtree();

      // This is a workaround for when we set focus on elements across iframes (in the previous step).
      // The popup is not refreshed, and consequently, it does not receive key events needed to trigger
      // the autocomplete popup.
      const sleepAfterFocus = contexts.length > 1;

      for (const context of contexts) {
        await SpecialPowers.spawn(context, [], async () => {
          const elements = Array.from(
            content.document.querySelectorAll("input, select")
          );
          // Focus on each field in the test document to trigger autofill field detection
          // on all the fields.
          elements.forEach(element => {
            element.focus();
          });
        });

        try {
          await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, context);
          if (sleepAfterFocus) {
            await sleep();
          }
        } catch (e) {
          // Error occurs when sending a key event to an invisible iframe, ignore the error.
        }
      }

      if (testPattern.onTestStart) {
        await testPattern.onTestStart();
      }

      info(`Waiting for expected section count`);
      const actor =
        browser.browsingContext.currentWindowGlobal.getActor("FormAutofill");
      await BrowserTestUtils.waitForCondition(() => {
        const sections = Array.from(actor.sectionsByRootId.values()).flat();
        return sections.length == testPattern.expectedResult.length;
      }, "Expected section count.");

      // Verify the identified fields in each section.
      info(`Verify the identified fields in each section`);
      const sections = Array.from(actor.sectionsByRootId.values()).flat();
      verifySectionFieldDetails(sections, testPattern.expectedResult);

      // Verify the autofilled value.
      if (options.testAutofill) {
        info(`test preview, autofill, and clear form`);
        let section;
        let expected;
        let autofillTrigger = testPattern.autofillTrigger;
        if (autofillTrigger) {
          const idx = testPattern.expectedResult.findIndex(expectedSection =>
            expectedSection.fields.some(field => "autofill" in field)
          );
          section = sections[idx];
          expected = testPattern.expectedResult[idx];
        } else {
          section = sections[0];
          expected = testPattern.expectedResult[0];
          autofillTrigger = getSelectorFromFieldDetail(section.fieldDetails[0]);
        }

        await triggerAutofillAndPreview(
          browser,
          autofillTrigger,
          async () => verifyPreviewResult(browser, section, expected),
          async () => verifyAutofillResult(browser, section, expected),
          async () => verifyClearResult(browser, section)
        );
      }

      if (options.testCapture) {
        info(`test capture`);
        const guid = await triggerCapture(
          browser,
          testPattern.submitButtonSelector,
          testPattern.captureFillValue
        );
        verifyCaptureRecord(guid, testPattern.captureExpectedRecord);
        await removeAllRecords();
      }

      if (testPattern.onTestComplete) {
        await testPattern.onTestComplete();
      }
    });

    if (testPattern.profile) {
      await removeAllRecords();
    }

    if (testPattern.prefs) {
      await SpecialPowers.popPrefEnv();
    }

    if (regionInfo) {
      Region._setCurrentRegion(regionInfo.home);
      Region._setHomeRegion(regionInfo.current);
    }
  }

  const only = patterns.find(pattern => !!pattern.only);
  if (only) {
    add_task(() => runTest(only));
    return;
  }

  patterns.forEach(testPattern => {
    add_task(() => runTest(testPattern));
  });
}

async function add_capture_heuristic_tests(patterns, fixturePathPrefix = "") {
  const oldValue = FormAutofillUtils.getOSAuthEnabled(
    FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF
  );

  FormAutofillUtils.setOSAuthEnabled(
    FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
    false
  );

  registerCleanupFunction(() => {
    FormAutofillUtils.setOSAuthEnabled(
      FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
      oldValue
    );
  });

  add_heuristic_tests(patterns, fixturePathPrefix, { testCapture: true });
}

async function add_autofill_heuristic_tests(patterns, fixturePathPrefix = "") {
  const oldValue = FormAutofillUtils.getOSAuthEnabled(
    FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF
  );

  FormAutofillUtils.setOSAuthEnabled(
    FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
    false
  );

  registerCleanupFunction(() => {
    FormAutofillUtils.setOSAuthEnabled(
      FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
      oldValue
    );
  });

  add_heuristic_tests(patterns, fixturePathPrefix, { testAutofill: true });
}

add_setup(function () {
  OSKeyStoreTestUtils.setup();
});

registerCleanupFunction(async () => {
  await removeAllRecords();
  await OSKeyStoreTestUtils.cleanup();
});

Messung V0.5
C=95 H=92 G=93

¤ Dauer der Verarbeitung: 0.10 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.