summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/migration/tests/browser')
-rw-r--r--browser/components/migration/tests/browser/browser.ini26
-rw-r--r--browser/components/migration/tests/browser/browser_aboutwelcome_behavior.js100
-rw-r--r--browser/components/migration/tests/browser/browser_dialog_cancel_close.js55
-rw-r--r--browser/components/migration/tests/browser/browser_dialog_open.js55
-rw-r--r--browser/components/migration/tests/browser/browser_dialog_resize.js29
-rw-r--r--browser/components/migration/tests/browser/browser_disabled_migrator.js131
-rw-r--r--browser/components/migration/tests/browser/browser_do_migration.js195
-rw-r--r--browser/components/migration/tests/browser/browser_entrypoint_telemetry.js105
-rw-r--r--browser/components/migration/tests/browser/browser_file_migration.js185
-rw-r--r--browser/components/migration/tests/browser/browser_ie_edge_bookmarks_success_strings.js89
-rw-r--r--browser/components/migration/tests/browser/browser_no_browsers_state.js92
-rw-r--r--browser/components/migration/tests/browser/browser_only_file_migrators.js71
-rw-r--r--browser/components/migration/tests/browser/browser_safari_passwords.js401
-rw-r--r--browser/components/migration/tests/browser/browser_safari_permissions.js133
-rw-r--r--browser/components/migration/tests/browser/dummy_file.csv1
-rw-r--r--browser/components/migration/tests/browser/head.js350
16 files changed, 2018 insertions, 0 deletions
diff --git a/browser/components/migration/tests/browser/browser.ini b/browser/components/migration/tests/browser/browser.ini
new file mode 100644
index 0000000000..e8ac0d5995
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+head = head.js
+prefs =
+ browser.migrate.content-modal.enabled=true
+ browser.migrate.internal-testing.enabled=true
+
+[browser_aboutwelcome_behavior.js]
+[browser_dialog_cancel_close.js]
+[browser_dialog_open.js]
+[browser_dialog_resize.js]
+[browser_disabled_migrator.js]
+[browser_do_migration.js]
+[browser_entrypoint_telemetry.js]
+[browser_file_migration.js]
+skip-if = os == "win" && debug # Bug 1827995
+support-files =
+ dummy_file.csv
+[browser_ie_edge_bookmarks_success_strings.js]
+[browser_no_browsers_state.js]
+[browser_only_file_migrators.js]
+[browser_safari_passwords.js]
+run-if =
+ os == "mac"
+[browser_safari_permissions.js]
+run-if =
+ os == "mac"
diff --git a/browser/components/migration/tests/browser/browser_aboutwelcome_behavior.js b/browser/components/migration/tests/browser/browser_aboutwelcome_behavior.js
new file mode 100644
index 0000000000..72c90851e2
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_aboutwelcome_behavior.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that if browser.migrate.content-modal.about-welcome-behavior
+ * is "autoclose", that closing the migration dialog when opened with the
+ * NEWTAB entrypoint (which currently only occurs from about:welcome),
+ * will result in the about:preferences tab closing too.
+ */
+add_task(async function test_autoclose_from_welcome() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.migrate.content-modal.about-welcome-behavior", "autoclose"],
+ ],
+ });
+
+ let migrationDialogPromise = waitForMigrationWizardDialogTab();
+ MigrationUtils.showMigrationWizard(window, {
+ entrypoint: MigrationUtils.MIGRATION_ENTRYPOINTS.NEWTAB,
+ });
+
+ let prefsBrowser = await migrationDialogPromise;
+ let prefsTab = gBrowser.getTabForBrowser(prefsBrowser);
+
+ let tabClosed = BrowserTestUtils.waitForTabClosing(prefsTab);
+
+ let dialog = prefsBrowser.contentDocument.querySelector(
+ "#migrationWizardDialog"
+ );
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, prefsBrowser);
+ await dialogClosed;
+ await tabClosed;
+ Assert.ok(true, "Preferences tab closed with autoclose behavior.");
+});
+
+/**
+ * Tests that if browser.migrate.content-modal.about-welcome-behavior
+ * is "default", that closing the migration dialog when opened with the
+ * NEWTAB entrypoint (which currently only occurs from about:welcome),
+ * will result in the about:preferences tab still staying open.
+ */
+add_task(async function test_no_autoclose_from_welcome() {
+ // Create a new blank tab which about:preferences will open into.
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.migrate.content-modal.about-welcome-behavior", "default"]],
+ });
+
+ let migrationDialogPromise = waitForMigrationWizardDialogTab();
+ MigrationUtils.showMigrationWizard(window, {
+ entrypoint: MigrationUtils.MIGRATION_ENTRYPOINTS.NEWTAB,
+ });
+
+ let prefsBrowser = await migrationDialogPromise;
+ let prefsTab = gBrowser.getTabForBrowser(prefsBrowser);
+
+ let dialog = prefsBrowser.contentDocument.querySelector(
+ "#migrationWizardDialog"
+ );
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, prefsBrowser);
+ await dialogClosed;
+ Assert.ok(!prefsTab.closing, "about:preferences tab is not closing.");
+
+ BrowserTestUtils.removeTab(prefsTab);
+});
+
+/**
+ * Tests that if browser.migrate.content-modal.about-welcome-behavior
+ * is "standalone", that opening the migration wizard from the NEWTAB
+ * entrypoint opens the migration wizard in a standalone top-level
+ * window.
+ */
+add_task(async function test_no_autoclose_from_welcome() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.migrate.content-modal.about-welcome-behavior", "standalone"],
+ ],
+ });
+
+ let windowOpened = BrowserTestUtils.domWindowOpened();
+ MigrationUtils.showMigrationWizard(window, {
+ entrypoint: MigrationUtils.MIGRATION_ENTRYPOINTS.NEWTAB,
+ });
+ let dialogWin = await windowOpened;
+ Assert.ok(dialogWin, "Top-level dialog window opened for the migrator.");
+ await BrowserTestUtils.waitForEvent(dialogWin, "MigrationWizard:Ready");
+
+ let dialogClosed = BrowserTestUtils.domWindowClosed(dialogWin);
+ dialogWin.close();
+ await dialogClosed;
+});
diff --git a/browser/components/migration/tests/browser/browser_dialog_cancel_close.js b/browser/components/migration/tests/browser/browser_dialog_cancel_close.js
new file mode 100644
index 0000000000..87f27cdb8d
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_dialog_cancel_close.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that pressing "Cancel" from the selection page of the migration
+ * dialog closes the dialog when opened in about:preferences as an HTML5
+ * dialog.
+ */
+add_task(async function test_cancel_close() {
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let wizard = dialog.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let cancelButton = shadow.querySelector(
+ 'div[name="page-selection"] .cancel-close'
+ );
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ cancelButton.click();
+ await dialogClosed;
+ Assert.ok(true, "Clicking the cancel button closed the dialog.");
+ });
+});
+
+/**
+ * Tests that pressing "Cancel" from the selection page of the migration
+ * dialog closes the dialog when opened in stand-alone window.
+ */
+add_task(async function test_cancel_close() {
+ let promiseWinLoaded = BrowserTestUtils.domWindowOpened().then(win => {
+ return BrowserTestUtils.waitForEvent(win, "MigrationWizard:Ready");
+ });
+
+ let win = Services.ww.openWindow(
+ window,
+ DIALOG_URL,
+ "_blank",
+ "dialog,centerscreen",
+ { onResize: () => {} }
+ );
+ await promiseWinLoaded;
+
+ win.sizeToContent();
+ let wizard = win.document.querySelector("#wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let cancelButton = shadow.querySelector(
+ 'div[name="page-selection"] .cancel-close'
+ );
+
+ let windowClosed = BrowserTestUtils.windowClosed(win);
+ cancelButton.click();
+ await windowClosed;
+ Assert.ok(true, "Window was closed.");
+});
diff --git a/browser/components/migration/tests/browser/browser_dialog_open.js b/browser/components/migration/tests/browser/browser_dialog_open.js
new file mode 100644
index 0000000000..e332e1ed4d
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_dialog_open.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that we can open the migration dialog in an about:preferences
+ * HTML5 dialog when calling MigrationUtils.showMigrationWizard within a
+ * tabbrowser window execution context.
+ */
+add_task(async function test_migration_dialog_open_in_tab_dialog_box() {
+ let migrationDialogPromise = waitForMigrationWizardDialogTab();
+ MigrationUtils.showMigrationWizard(window, {});
+ let prefsBrowser = await migrationDialogPromise;
+ Assert.ok(true, "Migration dialog was opened");
+ let dialog = prefsBrowser.contentDocument.querySelector(
+ "#migrationWizardDialog"
+ );
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ await BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, prefsBrowser);
+ await dialogClosed;
+ BrowserTestUtils.loadURIString(prefsBrowser, "about:blank");
+ await BrowserTestUtils.browserLoaded(prefsBrowser);
+});
+
+/**
+ * Tests that we can open the migration dialog in a stand-alone window
+ * when calling MigrationUtils.showMigrationWizard with a null opener
+ * argument, or a non-tabbrowser window context.
+ */
+add_task(async function test_migration_dialog_open_in_xul_window() {
+ let firstWindowOpened = BrowserTestUtils.domWindowOpened();
+ MigrationUtils.showMigrationWizard(null, {});
+ let firstDialogWin = await firstWindowOpened;
+
+ await BrowserTestUtils.waitForEvent(firstDialogWin, "MigrationWizard:Ready");
+
+ Assert.ok(true, "Migration dialog was opened");
+
+ // Now open a second migration dialog, using the first as the window
+ // argument.
+
+ let secondWindowOpened = BrowserTestUtils.domWindowOpened();
+ MigrationUtils.showMigrationWizard(firstDialogWin, {});
+ let secondDialogWin = await secondWindowOpened;
+
+ await BrowserTestUtils.waitForEvent(secondDialogWin, "MigrationWizard:Ready");
+
+ for (let dialogWin of [firstDialogWin, secondDialogWin]) {
+ let dialogClosed = BrowserTestUtils.domWindowClosed(dialogWin);
+ dialogWin.close();
+ await dialogClosed;
+ }
+});
diff --git a/browser/components/migration/tests/browser/browser_dialog_resize.js b/browser/components/migration/tests/browser/browser_dialog_resize.js
new file mode 100644
index 0000000000..8fb05faf2c
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_dialog_resize.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that if the MigrationWizard resizes when opened inside of a
+ * XUL window, that it causes the containing XUL window to resize
+ * appropriately.
+ */
+add_task(async function test_migration_dialog_resize_in_xul_window() {
+ let windowOpened = BrowserTestUtils.domWindowOpened();
+ MigrationUtils.showMigrationWizard(null, {});
+ let dialogWin = await windowOpened;
+
+ await BrowserTestUtils.waitForEvent(dialogWin, "MigrationWizard:Ready");
+
+ let wizard = dialogWin.document.body.querySelector("#wizard");
+ let height = wizard.getBoundingClientRect().height;
+
+ let windowResizePromise = BrowserTestUtils.waitForEvent(dialogWin, "resize");
+ wizard.style.height = height + 100 + "px";
+ await windowResizePromise;
+ Assert.ok(true, "Migration dialog window resized.");
+
+ let dialogClosed = BrowserTestUtils.domWindowClosed(dialogWin);
+ dialogWin.close();
+ await dialogClosed;
+});
diff --git a/browser/components/migration/tests/browser/browser_disabled_migrator.js b/browser/components/migration/tests/browser/browser_disabled_migrator.js
new file mode 100644
index 0000000000..a1c8540c35
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_disabled_migrator.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { MigratorBase } = ChromeUtils.importESModule(
+ "resource:///modules/MigratorBase.sys.mjs"
+);
+
+/**
+ * Tests that the InternalTestingProfileMigrator is listed in
+ * the new migration wizard selector when enabled.
+ */
+add_task(async function test_enabled_migrator() {
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let wizard = dialog.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let selector = shadow.querySelector("#browser-profile-selector");
+ selector.click();
+
+ await new Promise(resolve => {
+ wizard
+ .querySelector("panel-list")
+ .addEventListener("shown", resolve, { once: true });
+ });
+
+ let panelItem = wizard.querySelector(
+ `panel-item[key="${InternalTestingProfileMigrator.key}"]`
+ );
+
+ Assert.ok(
+ panelItem,
+ "The InternalTestingProfileMigrator panel-item exists."
+ );
+ panelItem.click();
+
+ Assert.ok(
+ selector.innerText.includes("Internal Testing Migrator"),
+ "Testing for enabled internal testing migrator"
+ );
+ });
+});
+
+/**
+ * Tests that the InternalTestingProfileMigrator is not listed in
+ * the new migration wizard selector when disabled.
+ */
+add_task(async function test_disabling_migrator() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.migrate.internal-testing.enabled", false]],
+ });
+
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ let internalTestingMigrator = new InternalTestingProfileMigrator();
+
+ // We create a fake migrator that we know will still be present after
+ // disabling the InternalTestingProfileMigrator so that we don't switch
+ // the wizard to the NO_BROWSERS_FOUND page, which we're not testing here.
+ let fakeMigrator = new FakeMigrator();
+
+ let getMigratorStub = sandbox.stub(MigrationUtils, "getMigrator");
+ getMigratorStub
+ .withArgs("internal-testing")
+ .resolves(internalTestingMigrator);
+ getMigratorStub.withArgs("fake-migrator").resolves(fakeMigrator);
+
+ sandbox.stub(MigrationUtils, "availableMigratorKeys").get(() => {
+ return ["internal-testing", "fake-migrator"];
+ });
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let wizard = dialog.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let selector = shadow.querySelector("#browser-profile-selector");
+ selector.click();
+
+ await new Promise(resolve => {
+ wizard
+ .querySelector("panel-list")
+ .addEventListener("shown", resolve, { once: true });
+ });
+
+ let panelItem = wizard.querySelector(
+ `panel-item[key="${InternalTestingProfileMigrator.key}"]`
+ );
+
+ Assert.ok(
+ !panelItem,
+ "The panel-item for the InternalTestingProfileMigrator does not exist"
+ );
+ });
+
+ sandbox.restore();
+});
+
+/**
+ * A stub of a migrator used for automated testing only.
+ */
+class FakeMigrator extends MigratorBase {
+ static get key() {
+ return "fake-migrator";
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-firefox";
+ }
+
+ // We will create a single MigratorResource for each resource type that
+ // just immediately reports a successful migration.
+ getResources() {
+ return Object.values(MigrationUtils.resourceTypes).map(type => {
+ return {
+ type,
+ migrate: callback => {
+ callback(true /* success */);
+ },
+ };
+ });
+ }
+
+ // We need to override enabled() to always return true for testing purposes.
+ get enabled() {
+ return true;
+ }
+}
diff --git a/browser/components/migration/tests/browser/browser_do_migration.js b/browser/components/migration/tests/browser/browser_do_migration.js
new file mode 100644
index 0000000000..b1a5e9ad60
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_do_migration.js
@@ -0,0 +1,195 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that the MigrationWizard can be used to successfully migrate
+ * using the InternalTestingProfileMigrator in a few scenarios.
+ */
+add_task(async function test_successful_migrations() {
+ // Scenario 1: A single resource type is available.
+ let migration = waitForTestMigration(
+ [MigrationUtils.resourceTypes.BOOKMARKS],
+ [MigrationUtils.resourceTypes.BOOKMARKS],
+ InternalTestingProfileMigrator.testProfile
+ );
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let selector = shadow.querySelector("#browser-profile-selector");
+
+ await new Promise(resolve => prefsWin.requestAnimationFrame(resolve));
+ Assert.equal(shadow.activeElement, selector, "Selector should be focused.");
+
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+ selectResourceTypesAndStartMigration(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ ]);
+ await migration;
+ await wizardDone;
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+
+ await new Promise(resolve => prefsWin.requestAnimationFrame(resolve));
+ Assert.equal(
+ shadow.activeElement,
+ doneButton,
+ "Done button should be focused."
+ );
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ doneButton.click();
+ await dialogClosed;
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ ]);
+ });
+
+ // Scenario 2: Several resource types are available, but only 1
+ // is checked / expected.
+ migration = waitForTestMigration(
+ [
+ MigrationUtils.resourceTypes.BOOKMARKS,
+ MigrationUtils.resourceTypes.PASSWORDS,
+ ],
+ [MigrationUtils.resourceTypes.PASSWORDS],
+ InternalTestingProfileMigrator.testProfile
+ );
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let selector = shadow.querySelector("#browser-profile-selector");
+
+ await new Promise(resolve => prefsWin.requestAnimationFrame(resolve));
+ Assert.equal(shadow.activeElement, selector, "Selector should be focused.");
+
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+ selectResourceTypesAndStartMigration(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ ]);
+ await migration;
+ await wizardDone;
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+
+ await new Promise(resolve => prefsWin.requestAnimationFrame(resolve));
+ Assert.equal(
+ shadow.activeElement,
+ doneButton,
+ "Done button should be focused."
+ );
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ doneButton.click();
+ await dialogClosed;
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ ]);
+ });
+
+ // Scenario 3: Several resource types are available, all are checked.
+ let allResourceTypeStrs = Object.values(
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES
+ );
+ let allResourceTypes = allResourceTypeStrs.map(resourceTypeStr => {
+ return MigrationUtils.resourceTypes[resourceTypeStr];
+ });
+
+ migration = waitForTestMigration(
+ allResourceTypes,
+ allResourceTypes,
+ InternalTestingProfileMigrator.testProfile
+ );
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let selector = shadow.querySelector("#browser-profile-selector");
+
+ await new Promise(resolve => prefsWin.requestAnimationFrame(resolve));
+ Assert.equal(shadow.activeElement, selector, "Selector should be focused.");
+
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+ selectResourceTypesAndStartMigration(wizard, allResourceTypeStrs);
+ await migration;
+ await wizardDone;
+ assertQuantitiesShown(wizard, allResourceTypeStrs);
+ });
+});
+
+/**
+ * Tests that if somehow the Migration Wizard requests to import a
+ * resource type that the migrator doesn't have the ability to import,
+ * that it's ignored and the migration completes normally.
+ */
+add_task(async function test_invalid_resource_type() {
+ let migration = waitForTestMigration(
+ [MigrationUtils.resourceTypes.BOOKMARKS],
+ [MigrationUtils.resourceTypes.BOOKMARKS],
+ InternalTestingProfileMigrator.testProfile
+ );
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+
+ // The Migration Wizard _shouldn't_ display anything except BOOKMARKS,
+ // since that's the only resource type that the selected migrator is
+ // supposed to currently support, but we'll check the other checkboxes
+ // even though they're hidden just to see what happens.
+ selectResourceTypesAndStartMigration(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA,
+ ]);
+ await migration;
+ await wizardDone;
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+
+ await new Promise(resolve => prefsWin.requestAnimationFrame(resolve));
+ Assert.equal(
+ shadow.activeElement,
+ doneButton,
+ "Done button should be focused."
+ );
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ ]);
+
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ doneButton.click();
+ await dialogClosed;
+ });
+});
diff --git a/browser/components/migration/tests/browser/browser_entrypoint_telemetry.js b/browser/components/migration/tests/browser/browser_entrypoint_telemetry.js
new file mode 100644
index 0000000000..bdeca0fdb5
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_entrypoint_telemetry.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+const CONTENT_MODAL_ENABLED_PREF = "browser.migrate.content-modal.enabled";
+const HISTOGRAM_ID = "FX_MIGRATION_ENTRY_POINT_CATEGORICAL";
+const LEGACY_HISTOGRAM_ID = "FX_MIGRATION_ENTRY_POINT";
+
+async function showThenCloseMigrationWizardViaEntrypoint(entrypoint) {
+ let openedPromise = BrowserTestUtils.waitForMigrationWizard(window);
+
+ // On some platforms, this call blocks, so in order to let the test proceed, we
+ // run it on the next tick of the event loop.
+ executeSoon(() => {
+ MigrationUtils.showMigrationWizard(window, {
+ entrypoint,
+ });
+ });
+
+ let wizard = await openedPromise;
+ Assert.ok(wizard, "Migration wizard opened.");
+ await BrowserTestUtils.closeMigrationWizard(wizard);
+}
+
+add_setup(async () => {
+ // Load the initial tab at example.com. This makes it so that if
+ // we're using the new migration wizard, we'll load the about:preferences
+ // page in a new tab rather than overtaking the initial one. This
+ // makes it easier to be consistent with closing and opening
+ // behaviours between the two kinds of migration wizards.
+ let browser = gBrowser.selectedBrowser;
+ BrowserTestUtils.loadURIString(browser, "https://example.com");
+ await BrowserTestUtils.browserLoaded(browser);
+});
+
+/**
+ * Tests that the entrypoint passed to MigrationUtils.showMigrationWizard gets
+ * written to both the FX_MIGRATION_ENTRY_POINT_CATEGORICAL histogram, as well
+ * as the legacy FX_MIGRATION_ENTRY_POINT histogram (but only if using the old
+ * wizard window).
+ */
+add_task(async function test_legacy_wizard() {
+ for (let contentModalEnabled of [true, false]) {
+ info("Testing with content modal enabled: " + contentModalEnabled);
+ await SpecialPowers.pushPrefEnv({
+ set: [[CONTENT_MODAL_ENABLED_PREF, contentModalEnabled]],
+ });
+
+ let histogram = TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_ID);
+ let legacyHistogram =
+ TelemetryTestUtils.getAndClearHistogram(LEGACY_HISTOGRAM_ID);
+
+ // Let's arbitrarily pick the "Bookmarks" entrypoint, and make sure this
+ // is recorded.
+ await showThenCloseMigrationWizardViaEntrypoint(
+ MigrationUtils.MIGRATION_ENTRYPOINTS.BOOKMARKS
+ );
+ let entrypointId = MigrationUtils.getLegacyMigrationEntrypoint(
+ MigrationUtils.MIGRATION_ENTRYPOINTS.BOOKMARKS
+ );
+
+ TelemetryTestUtils.assertHistogram(histogram, entrypointId, 1);
+
+ if (!contentModalEnabled) {
+ TelemetryTestUtils.assertHistogram(legacyHistogram, entrypointId, 1);
+ }
+
+ histogram = TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_ID);
+ legacyHistogram =
+ TelemetryTestUtils.getAndClearHistogram(LEGACY_HISTOGRAM_ID);
+
+ // Now let's pick the "Preferences" entrypoint, and make sure this
+ // is recorded.
+ await showThenCloseMigrationWizardViaEntrypoint(
+ MigrationUtils.MIGRATION_ENTRYPOINTS.PREFERENCES
+ );
+ entrypointId = MigrationUtils.getLegacyMigrationEntrypoint(
+ MigrationUtils.MIGRATION_ENTRYPOINTS.PREFERENCES
+ );
+
+ TelemetryTestUtils.assertHistogram(histogram, entrypointId, 1);
+ if (!contentModalEnabled) {
+ TelemetryTestUtils.assertHistogram(legacyHistogram, entrypointId, 1);
+ }
+
+ histogram = TelemetryTestUtils.getAndClearHistogram(HISTOGRAM_ID);
+ legacyHistogram =
+ TelemetryTestUtils.getAndClearHistogram(LEGACY_HISTOGRAM_ID);
+
+ // Finally, check the fallback by passing in something invalid as an entrypoint.
+ await showThenCloseMigrationWizardViaEntrypoint(undefined);
+ entrypointId = MigrationUtils.getLegacyMigrationEntrypoint(
+ MigrationUtils.MIGRATION_ENTRYPOINTS.UNKNOWN
+ );
+
+ TelemetryTestUtils.assertHistogram(histogram, entrypointId, 1);
+ if (!contentModalEnabled) {
+ TelemetryTestUtils.assertHistogram(legacyHistogram, entrypointId, 1);
+ }
+ }
+});
diff --git a/browser/components/migration/tests/browser/browser_file_migration.js b/browser/components/migration/tests/browser/browser_file_migration.js
new file mode 100644
index 0000000000..774ff25425
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_file_migration.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { FileMigratorBase } = ChromeUtils.importESModule(
+ "resource:///modules/FileMigrators.sys.mjs"
+);
+
+const DUMMY_FILEMIGRATOR_KEY = "dummy-file-migrator";
+const DUMMY_FILEPICKER_TITLE = "Some dummy file picker title";
+const DUMMY_FILTER_TITLE = "Some file type";
+const DUMMY_EXTENSION_PATTERN = "*.test";
+const TEST_FILE_PATH = getTestFilePath("dummy_file.csv");
+
+/**
+ * A subclass of FileMigratorBase that doesn't do anything, but
+ * is useful for testing.
+ *
+ * Notably, the `migrate` method is not overridden here. Tests that
+ * use this class should use Sinon to stub out the migrate method.
+ */
+class DummyFileMigrator extends FileMigratorBase {
+ static get key() {
+ return DUMMY_FILEMIGRATOR_KEY;
+ }
+
+ static get displayNameL10nID() {
+ return "migration-wizard-migrator-display-name-file-password-csv";
+ }
+
+ static get brandImage() {
+ return "chrome://branding/content/document.ico";
+ }
+
+ get enabled() {
+ return true;
+ }
+
+ get progressHeaderL10nID() {
+ return "migration-passwords-from-file-progress-header";
+ }
+
+ get successHeaderL10nID() {
+ return "migration-passwords-from-file-success-header";
+ }
+
+ async getFilePickerConfig() {
+ return Promise.resolve({
+ title: DUMMY_FILEPICKER_TITLE,
+ filters: [
+ {
+ title: DUMMY_FILTER_TITLE,
+ extensionPattern: DUMMY_EXTENSION_PATTERN,
+ },
+ ],
+ });
+ }
+
+ get displayedResourceTypes() {
+ return [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS];
+ }
+}
+
+/**
+ * Tests the flow of selecting a file migrator (in this case,
+ * the DummyFileMigrator), getting the file picker opened for it,
+ * and then passing the path of the selected file to the migrator.
+ */
+add_task(async function test_file_migration() {
+ let migrator = new DummyFileMigrator();
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ // First, use Sinon to insert our DummyFileMigrator as the only available
+ // file migrator.
+ sandbox.stub(MigrationUtils, "getFileMigrator").callsFake(() => {
+ return migrator;
+ });
+ sandbox.stub(MigrationUtils, "availableFileMigrators").get(() => {
+ return [migrator];
+ });
+
+ // This is the expected success state that our DummyFileMigrator will
+ // return as the final progress update to the migration wizard.
+ const SUCCESS_STATE = {
+ [MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES.PASSWORDS_NEW]:
+ "2 added",
+ [MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES.PASSWORDS_UPDATED]:
+ "1 updated",
+ };
+
+ let migrateStub = sandbox.stub(migrator, "migrate").callsFake(filePath => {
+ Assert.equal(filePath, TEST_FILE_PATH);
+ return SUCCESS_STATE;
+ });
+
+ // We use MockFilePicker to simulate a native file picker, and prepare it
+ // to return a dummy file pointed at TEST_FILE_PATH. The file at
+ // TEST_FILE_PATH is not required (nor expected) to exist.
+ const { MockFilePicker } = SpecialPowers;
+ MockFilePicker.init(window);
+ registerCleanupFunction(() => {
+ MockFilePicker.cleanup();
+ });
+
+ let dummyFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dummyFile.initWithPath(TEST_FILE_PATH);
+ let filePickerShownPromise = new Promise(resolve => {
+ MockFilePicker.showCallback = () => {
+ Assert.ok(true, "Filepicker shown.");
+ MockFilePicker.setFiles([dummyFile]);
+ resolve();
+ };
+ });
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+
+ // Now select our DummyFileMigrator from the list.
+ let selector = shadow.querySelector("#browser-profile-selector");
+ selector.click();
+
+ info("Waiting for panel-list shown");
+ await new Promise(resolve => {
+ wizard
+ .querySelector("panel-list")
+ .addEventListener("shown", resolve, { once: true });
+ });
+
+ info("Panel list shown. Clicking on panel-item");
+ let panelItem = wizard.querySelector(
+ `panel-item[key="${DUMMY_FILEMIGRATOR_KEY}"]`
+ );
+ panelItem.click();
+
+ // Selecting a file migrator from the selector should automatically
+ // open the file picker, so we await it here. Once the file is
+ // selected, migration should begin immediately.
+
+ info("Waiting for file picker");
+ await filePickerShownPromise;
+ await wizardDone;
+ Assert.ok(migrateStub.called, "Migrate on DummyFileMigrator was called.");
+
+ // At this point, with migration having completed, we should be showing
+ // the PROGRESS page with the SUCCESS_STATE represented.
+ let deck = shadow.querySelector("#wizard-deck");
+ Assert.equal(
+ deck.selectedViewName,
+ `page-${MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS}`
+ );
+
+ // We expect only the displayed resource types in SUCCESS_STATE are
+ // displayed now.
+ let progressGroups = shadow.querySelectorAll(
+ "div[name='page-page-file-import-progress'] .resource-progress-group"
+ );
+ for (let progressGroup of progressGroups) {
+ let expectedSuccessText =
+ SUCCESS_STATE[progressGroup.dataset.resourceType];
+ if (expectedSuccessText) {
+ let successText =
+ progressGroup.querySelector(".success-text").textContent;
+ Assert.equal(successText, expectedSuccessText);
+ } else {
+ Assert.ok(
+ BrowserTestUtils.is_hidden(progressGroup),
+ `Resource progress group for ${progressGroup.dataset.resourceType}` +
+ ` should be hidden.`
+ );
+ }
+ }
+ });
+});
diff --git a/browser/components/migration/tests/browser/browser_ie_edge_bookmarks_success_strings.js b/browser/components/migration/tests/browser/browser_ie_edge_bookmarks_success_strings.js
new file mode 100644
index 0000000000..ab0b705678
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_ie_edge_bookmarks_success_strings.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that the progress strings that the Migration Wizard shows
+ * during migrations for IE and Edge uses the term "Favorites" rather
+ * then "Bookmarks".
+ */
+add_task(async function test_ie_edge_bookmarks_success_strings() {
+ for (let key of ["ie", "edge", "internal-testing"]) {
+ let sandbox = sinon.createSandbox();
+
+ sandbox.stub(InternalTestingProfileMigrator, "key").get(() => {
+ return key;
+ });
+
+ sandbox.stub(MigrationUtils, "availableMigratorKeys").get(() => {
+ return key;
+ });
+
+ let testingMigrator = new InternalTestingProfileMigrator();
+ sandbox.stub(MigrationUtils, "getMigrator").callsFake(() => {
+ return Promise.resolve(testingMigrator);
+ });
+
+ let migration = waitForTestMigration(
+ [MigrationUtils.resourceTypes.BOOKMARKS],
+ [MigrationUtils.resourceTypes.BOOKMARKS],
+ InternalTestingProfileMigrator.testProfile
+ );
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+ selectResourceTypesAndStartMigration(
+ wizard,
+ [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS],
+ key
+ );
+ await migration;
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ // If we were using IE or Edge (EdgeHTLM), then the success message should
+ // include the word "favorites". Otherwise, we expect it to include
+ // the word "bookmarks".
+ let bookmarksProgressGroup = shadow.querySelector(
+ `.resource-progress-group[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS}"`
+ );
+ let successTextElement =
+ bookmarksProgressGroup.querySelector(".success-text");
+
+ await BrowserTestUtils.waitForCondition(() => {
+ return successTextElement.textContent.trim();
+ });
+
+ let successText = successTextElement.textContent.toLowerCase();
+
+ if (key == "internal-testing") {
+ Assert.ok(
+ successText.includes("bookmarks"),
+ `Success test should refer to bookmarks: ${successText}.`
+ );
+ } else {
+ Assert.ok(
+ successText.includes("favorites"),
+ `Success test should refer to favorites: ${successText}`
+ );
+ }
+
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+ doneButton.click();
+ await dialogClosed;
+ await wizardDone;
+ });
+
+ sandbox.restore();
+ }
+});
diff --git a/browser/components/migration/tests/browser/browser_no_browsers_state.js b/browser/components/migration/tests/browser/browser_no_browsers_state.js
new file mode 100644
index 0000000000..cd4677f31d
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_no_browsers_state.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that the wizard switches to the NO_BROWSERS_FOUND page
+ * when no migrators are detected.
+ */
+add_task(async function test_browser_no_programs() {
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ sandbox.stub(MigrationUtils, "availableMigratorKeys").get(() => {
+ return [];
+ });
+
+ // Let's enable the Passwords CSV import by default so that it appears
+ // as a file migrator.
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.management.page.fileImport.enabled", true]],
+ });
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let wizard = dialog.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let deck = shadow.querySelector("#wizard-deck");
+
+ await BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" + MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND
+ );
+ }
+ );
+
+ Assert.ok(
+ true,
+ "Went to no browser page after attempting to search for migrators."
+ );
+ let chooseImportFromFile = shadow.querySelector("#choose-import-from-file");
+ Assert.ok(
+ !chooseImportFromFile.hidden,
+ "Selecting a file migrator should still be possible."
+ );
+ });
+
+ // Now disable all file migrators to make sure that the "Import from file"
+ // button is hidden.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.management.page.fileImport.enabled", false],
+ ["browser.migrate.bookmarks-file.enabled", false],
+ ],
+ });
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let wizard = dialog.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let deck = shadow.querySelector("#wizard-deck");
+
+ await BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" + MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND
+ );
+ }
+ );
+
+ Assert.ok(
+ true,
+ "Went to no browser page after attempting to search for migrators."
+ );
+ let chooseImportFromFile = shadow.querySelector("#choose-import-from-file");
+ Assert.ok(
+ chooseImportFromFile.hidden,
+ "Selecting a file migrator should not be possible."
+ );
+ });
+
+ sandbox.restore();
+});
diff --git a/browser/components/migration/tests/browser/browser_only_file_migrators.js b/browser/components/migration/tests/browser/browser_only_file_migrators.js
new file mode 100644
index 0000000000..80c09e9a09
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_only_file_migrators.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that the NO_BROWSERS_FOUND page has a button to redirect to the
+ * selection page when only file migrators are found.
+ */
+add_task(async function test_only_file_migrators() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.management.page.fileImport.enabled", true]],
+ });
+
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ sandbox.stub(MigrationUtils, "availableMigratorKeys").get(() => {
+ return [];
+ });
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let wizard = dialog.querySelector("migration-wizard");
+ let shadow = wizard.openOrClosedShadowRoot;
+ let deck = shadow.querySelector("#wizard-deck");
+
+ await BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" + MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND
+ );
+ }
+ );
+
+ let chooseImportFileButton = shadow.querySelector(
+ "#choose-import-from-file"
+ );
+
+ let changedToSelectionPage = BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" + MigrationWizardConstants.PAGES.SELECTION
+ );
+ }
+ );
+ chooseImportFileButton.click();
+ await changedToSelectionPage;
+
+ // No browser migrators should be listed.
+ let browserMigratorItems = wizard.querySelectorAll(
+ `panel-item[type="${MigrationWizardConstants.MIGRATOR_TYPES.BROWSER}"]`
+ );
+ Assert.ok(!browserMigratorItems.length, "No browser migrators listed.");
+
+ // Check to make sure there's at least one file migrator listed.
+ let fileMigratorItems = wizard.querySelectorAll(
+ `panel-item[type="${MigrationWizardConstants.MIGRATOR_TYPES.FILE}"]`
+ );
+
+ Assert.ok(!!fileMigratorItems.length, "Listed at least one file migrator.");
+ });
+});
diff --git a/browser/components/migration/tests/browser/browser_safari_passwords.js b/browser/components/migration/tests/browser/browser_safari_passwords.js
new file mode 100644
index 0000000000..299695f9e3
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_safari_passwords.js
@@ -0,0 +1,401 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SafariProfileMigrator } = ChromeUtils.importESModule(
+ "resource:///modules/SafariProfileMigrator.sys.mjs"
+);
+const { LoginCSVImport } = ChromeUtils.importESModule(
+ "resource://gre/modules/LoginCSVImport.sys.mjs"
+);
+
+const TEST_FILE_PATH = getTestFilePath("dummy_file.csv");
+
+// We use MockFilePicker to simulate a native file picker, and prepare it
+// to return a dummy file pointed at TEST_FILE_PATH. The file at
+// TEST_FILE_PATH is not required (nor expected) to exist.
+const { MockFilePicker } = SpecialPowers;
+
+add_setup(async function () {
+ MockFilePicker.init(window);
+ registerCleanupFunction(() => {
+ MockFilePicker.cleanup();
+ });
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.management.page.fileImport.enabled", true]],
+ });
+});
+
+/**
+ * A helper function that does most of the heavy lifting for the tests in
+ * this file. Specfically, it takes care of:
+ *
+ * 1. Stubbing out the various hunks of the SafariProfileMigrator in order
+ * to simulate a migration without actually performing one, since the
+ * migrator itself isn't being tested here.
+ * 2. Stubbing out parts of MigrationUtils and LoginCSVImport to have a
+ * consistent reporting on how many things are imported.
+ * 3. Setting up the MockFilePicker if expectsFilePicker is true to return
+ * the TEST_FILE_PATH.
+ * 4. Opens up the migration wizard, and chooses to import both BOOKMARKS
+ * and PASSWORDS, and then clicks "Import".
+ * 5. Waits for the migration wizard to show the Safari password import
+ * instructions.
+ * 6. Runs taskFn
+ * 7. Closes the migration dialog.
+ *
+ * @param {boolean} expectsFilePicker
+ * True if the MockFilePicker should be set up to return TEST_FILE_PATH.
+ * @param {boolean} migrateBookmarks
+ * True if bookmarks should be migrated alongside passwords. If not, only
+ * passwords will be migrated.
+ * @param {Function} taskFn
+ * An asynchronous function that takes the following parameters in this
+ * order:
+ *
+ * {Element} wizard
+ * The opened migration wizard
+ * {Promise} filePickerShownPromise
+ * A Promise that resolves once the MockFilePicker has closed. This is
+ * undefined if expectsFilePicker was false.
+ * {object} importFromCSVStub
+ * The Sinon stub object for LoginCSVImport.importFromCSV. This can be
+ * used to check to see whether it was called.
+ * {Promise} didMigration
+ * A Promise that resolves to true once the migration completes.
+ * {Promise} wizardDone
+ * A Promise that resolves once the migration wizard reports that a
+ * migration has completed.
+ * @returns {Promise<undefined>}
+ */
+async function testSafariPasswordHelper(
+ expectsFilePicker,
+ migrateBookmarks,
+ taskFn
+) {
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ let safariMigrator = new SafariProfileMigrator();
+ sandbox.stub(MigrationUtils, "getMigrator").resolves(safariMigrator);
+
+ // We're not testing the permission flow here, so let's pretend that we
+ // always have permission to read resources from the disk.
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "hasPermissions")
+ .resolves(true);
+
+ // Have the migrator claim that only BOOKMARKS are only available.
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "getMigrateData")
+ .resolves(MigrationUtils.resourceTypes.BOOKMARKS);
+
+ let migrateStub;
+ let didMigration = new Promise(resolve => {
+ migrateStub = sandbox
+ .stub(SafariProfileMigrator.prototype, "migrate")
+ .callsFake((aResourceTypes, aStartup, aProfile, aProgressCallback) => {
+ if (!migrateBookmarks) {
+ Assert.ok(
+ false,
+ "Should not have called migrate when only migrating Safari passwords."
+ );
+ }
+
+ Assert.ok(
+ !aStartup,
+ "Migrator should not have been called as a startup migration."
+ );
+ Assert.ok(
+ aResourceTypes & MigrationUtils.resourceTypes.BOOKMARKS,
+ "Should have requested to migrate the BOOKMARKS resource."
+ );
+ Assert.ok(
+ !(aResourceTypes & MigrationUtils.resourceTypes.PASSWORDS),
+ "Should not have requested to migrate the PASSWORDS resource."
+ );
+
+ aProgressCallback(MigrationUtils.resourceTypes.BOOKMARKS);
+ Services.obs.notifyObservers(null, "Migration:Ended");
+ resolve();
+ });
+ });
+
+ // We'll pretend we added EXPECTED_QUANTITY passwords from the Safari
+ // password file.
+ let results = [];
+ for (let i = 0; i < EXPECTED_QUANTITY; ++i) {
+ results.push({ result: "added" });
+ }
+ let importFromCSVStub = sandbox
+ .stub(LoginCSVImport, "importFromCSV")
+ .resolves(results);
+
+ sandbox.stub(MigrationUtils, "_importQuantities").value({
+ bookmarks: EXPECTED_QUANTITY,
+ });
+
+ let filePickerShownPromise;
+
+ if (expectsFilePicker) {
+ MockFilePicker.reset();
+
+ let dummyFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dummyFile.initWithPath(TEST_FILE_PATH);
+ filePickerShownPromise = new Promise(resolve => {
+ MockFilePicker.showCallback = () => {
+ Assert.ok(true, "Filepicker shown.");
+ MockFilePicker.setFiles([dummyFile]);
+ resolve();
+ };
+ });
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ }
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ info("Choosing Safari");
+ let panelItem = wizard.querySelector(
+ `panel-item[key="${SafariProfileMigrator.key}"]`
+ );
+ panelItem.click();
+
+ let resourceTypeList = shadow.querySelector("#resource-type-list");
+
+ // Let's choose whether to import BOOKMARKS first.
+ let bookmarksNode = resourceTypeList.querySelector(
+ `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS}"]`
+ );
+ bookmarksNode.control.checked = migrateBookmarks;
+
+ // Let's make sure that PASSWORDS is displayed despite the migrator only
+ // (currently) returning BOOKMARKS as an available resource to migrate.
+ let passwordsNode = resourceTypeList.querySelector(
+ `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"]`
+ );
+ Assert.ok(
+ !passwordsNode.hidden,
+ "PASSWORDS should be available to import from."
+ );
+ passwordsNode.control.checked = true;
+
+ let deck = shadow.querySelector("#wizard-deck");
+ let switchedToSafariPermissionPage =
+ BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" + MigrationWizardConstants.PAGES.SAFARI_PASSWORD_PERMISSION
+ );
+ }
+ );
+
+ let importButton = shadow.querySelector("#import");
+ importButton.click();
+ await switchedToSafariPermissionPage;
+ Assert.ok(true, "Went to Safari permission page after attempting import.");
+
+ await taskFn(
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ );
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+
+ doneButton.click();
+ await dialogClosed;
+ });
+
+ sandbox.restore();
+ MockFilePicker.reset();
+}
+
+/**
+ * Tests the flow of importing passwords from Safari via an
+ * exported CSV file.
+ */
+add_task(async function test_safari_password_do_import() {
+ await testSafariPasswordHelper(
+ true,
+ true,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let safariPasswordImportSelect = shadow.querySelector(
+ "#safari-password-import-select"
+ );
+ safariPasswordImportSelect.click();
+ await filePickerShownPromise;
+ Assert.ok(true, "File picker was shown.");
+
+ await didMigration;
+ Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
+
+ await wizardDone;
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ ]);
+ }
+ );
+});
+
+/**
+ * Tests that only passwords get imported if the user only opts
+ * to import passwords, and that nothing else gets imported.
+ */
+add_task(async function test_safari_password_only_do_import() {
+ await testSafariPasswordHelper(
+ true,
+ false,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let safariPasswordImportSelect = shadow.querySelector(
+ "#safari-password-import-select"
+ );
+ safariPasswordImportSelect.click();
+ await filePickerShownPromise;
+ Assert.ok(true, "File picker was shown.");
+
+ await wizardDone;
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ ]);
+
+ Assert.ok(importFromCSVStub.called, "Importing from CSV was called.");
+ Assert.ok(
+ !migrateStub.called,
+ "SafariProfileMigrator.migrate was not called."
+ );
+ }
+ );
+});
+
+/**
+ * Tests that the user can skip importing passwords from Safari.
+ */
+add_task(async function test_safari_password_skip() {
+ await testSafariPasswordHelper(
+ false,
+ true,
+ async (
+ wizard,
+ filePickerShownPromise,
+ importFromCSVStub,
+ didMigration,
+ migrateStub,
+ wizardDone
+ ) => {
+ let shadow = wizard.openOrClosedShadowRoot;
+ let safariPasswordImportSkip = shadow.querySelector(
+ "#safari-password-import-skip"
+ );
+ safariPasswordImportSkip.click();
+
+ await didMigration;
+ Assert.ok(!MockFilePicker.shown, "Never showed the file picker.");
+ Assert.ok(
+ !importFromCSVStub.called,
+ "Importing from CSV was never called."
+ );
+
+ await wizardDone;
+
+ assertQuantitiesShown(wizard, [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ ]);
+ }
+ );
+});
+
+/**
+ * Tests that importing from passwords for Safari doesn't exist if
+ * signon.management.page.fileImport.enabled is false.
+ */
+add_task(async function test_safari_password_disabled() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.management.page.fileImport.enabled", false]],
+ });
+
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ let safariMigrator = new SafariProfileMigrator();
+ sandbox.stub(MigrationUtils, "getMigrator").resolves(safariMigrator);
+
+ // We're not testing the permission flow here, so let's pretend that we
+ // always have permission to read resources from the disk.
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "hasPermissions")
+ .resolves(true);
+
+ // Have the migrator claim that only BOOKMARKS are only available.
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "getMigrateData")
+ .resolves(MigrationUtils.resourceTypes.BOOKMARKS);
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ info("Choosing Safari");
+ let panelItem = wizard.querySelector(
+ `panel-item[key="${SafariProfileMigrator.key}"]`
+ );
+ panelItem.click();
+
+ let resourceTypeList = shadow.querySelector("#resource-type-list");
+
+ // Let's make sure that PASSWORDS is displayed despite the migrator only
+ // (currently) returning BOOKMARKS as an available resource to migrate.
+ let passwordsNode = resourceTypeList.querySelector(
+ `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"]`
+ );
+ Assert.ok(
+ passwordsNode.hidden,
+ "PASSWORDS should not be available to import from."
+ );
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/migration/tests/browser/browser_safari_permissions.js b/browser/components/migration/tests/browser/browser_safari_permissions.js
new file mode 100644
index 0000000000..924d6cc4d4
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_safari_permissions.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SafariProfileMigrator } = ChromeUtils.importESModule(
+ "resource:///modules/SafariProfileMigrator.sys.mjs"
+);
+
+/**
+ * Tests that if we don't have permission to read the contents
+ * of ~/Library/Safari, that we can ask for permission to do that.
+ *
+ * This involves presenting the user with some instructions, and then
+ * showing a native folder picker for the user to select the
+ * ~/Library/Safari folder. This seems to give us read access to the
+ * folder contents.
+ *
+ * Revoking permissions for reading the ~/Library/Safari folder is
+ * not something that we know how to do just yet. It seems to be
+ * something involving macOS's System Integrity Protection. This test
+ * mocks out and simulates the actual permissions mechanism to make
+ * this test run reliably and repeatably.
+ */
+add_task(async function test_safari_permissions() {
+ let sandbox = sinon.createSandbox();
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+
+ let safariMigrator = new SafariProfileMigrator();
+ sandbox.stub(MigrationUtils, "getMigrator").resolves(safariMigrator);
+
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "hasPermissions")
+ .onFirstCall()
+ .resolves(false)
+ .onSecondCall()
+ .resolves(true);
+
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "getPermissions")
+ .resolves(true);
+
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "getResources")
+ .callsFake(() => {
+ return Promise.resolve([
+ {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+ migrate: () => {},
+ },
+ ]);
+ });
+
+ let didMigration = new Promise(resolve => {
+ sandbox
+ .stub(SafariProfileMigrator.prototype, "migrate")
+ .callsFake((aResourceTypes, aStartup, aProfile, aProgressCallback) => {
+ Assert.ok(
+ !aStartup,
+ "Migrator should not have been called as a startup migration."
+ );
+
+ aProgressCallback(MigrationUtils.resourceTypes.BOOKMARKS);
+ Services.obs.notifyObservers(null, "Migration:Ended");
+ resolve();
+ });
+ });
+
+ await withMigrationWizardDialog(async prefsWin => {
+ let dialogBody = prefsWin.document.body;
+ let wizard = dialogBody.querySelector("migration-wizard");
+ let wizardDone = BrowserTestUtils.waitForEvent(
+ wizard,
+ "MigrationWizard:DoneMigration"
+ );
+
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ info("Choosing Safari");
+ let panelItem = wizard.querySelector(
+ `panel-item[key="${SafariProfileMigrator.key}"]`
+ );
+ panelItem.click();
+
+ // Let's just choose "Bookmarks" for now.
+ let resourceTypeList = shadow.querySelector("#resource-type-list");
+ let resourceNodes = resourceTypeList.querySelectorAll(
+ `label[data-resource-type]`
+ );
+ for (let resourceNode of resourceNodes) {
+ resourceNode.control.checked =
+ resourceNode.dataset.resourceType ==
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS;
+ }
+
+ let deck = shadow.querySelector("#wizard-deck");
+ let switchedToSafariPermissionPage =
+ BrowserTestUtils.waitForMutationCondition(
+ deck,
+ { attributeFilter: ["selected-view"] },
+ () => {
+ return (
+ deck.getAttribute("selected-view") ==
+ "page-" + MigrationWizardConstants.PAGES.SAFARI_PERMISSION
+ );
+ }
+ );
+
+ let importButton = shadow.querySelector("#import");
+ importButton.click();
+ await switchedToSafariPermissionPage;
+ Assert.ok(true, "Went to Safari permission page after attempting import.");
+
+ let requestPermissions = shadow.querySelector(
+ "#safari-request-permissions"
+ );
+ requestPermissions.click();
+ await didMigration;
+ Assert.ok(true, "Completed migration");
+
+ let dialog = prefsWin.document.querySelector("#migrationWizardDialog");
+ let doneButton = shadow.querySelector(
+ "div[name='page-progress'] .done-button"
+ );
+ let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "close");
+
+ doneButton.click();
+ await dialogClosed;
+ await wizardDone;
+ });
+});
diff --git a/browser/components/migration/tests/browser/dummy_file.csv b/browser/components/migration/tests/browser/dummy_file.csv
new file mode 100644
index 0000000000..48a099ab76
--- /dev/null
+++ b/browser/components/migration/tests/browser/dummy_file.csv
@@ -0,0 +1 @@
+This file intentionally left blank. \ No newline at end of file
diff --git a/browser/components/migration/tests/browser/head.js b/browser/components/migration/tests/browser/head.js
new file mode 100644
index 0000000000..772dd8bd29
--- /dev/null
+++ b/browser/components/migration/tests/browser/head.js
@@ -0,0 +1,350 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+const { MigrationWizardConstants } = ChromeUtils.importESModule(
+ "chrome://browser/content/migration/migration-wizard-constants.mjs"
+);
+const { InternalTestingProfileMigrator } = ChromeUtils.importESModule(
+ "resource:///modules/InternalTestingProfileMigrator.sys.mjs"
+);
+
+const DIALOG_URL =
+ "chrome://browser/content/migration/migration-dialog-window.html";
+
+/**
+ * We'll have this be our magic number of quantities of various imports.
+ * We will use Sinon to prepare MigrationUtils to presume that this was
+ * how many of each quantity-supported resource type was imported.
+ */
+const EXPECTED_QUANTITY = 123;
+
+/**
+ * These are the resource types that currently display their import success
+ * message with a quantity.
+ */
+const RESOURCE_TYPES_WITH_QUANTITIES = [
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA,
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS,
+];
+
+/**
+ * The withMigrationWizardDialog callback, called after the
+ * dialog has loaded and the wizard is ready.
+ *
+ * @callback withMigrationWizardDialogCallback
+ * @param {DOMWindow} window
+ * The content window of the migration wizard subdialog frame.
+ * @returns {Promise<undefined>}
+ */
+
+/**
+ * Opens the migration wizard HTML5 dialog in about:preferences in the
+ * current window's selected tab, runs an async taskFn, and then
+ * cleans up by loading about:blank in the tab before resolving.
+ *
+ * @param {withMigrationWizardDialogCallback} taskFn
+ * An async test function to be called while the migration wizard
+ * dialog is open.
+ * @returns {Promise<undefined>}
+ */
+async function withMigrationWizardDialog(taskFn) {
+ let migrationDialogPromise = waitForMigrationWizardDialogTab();
+ await MigrationUtils.showMigrationWizard(window, {});
+ let prefsBrowser = await migrationDialogPromise;
+
+ try {
+ await taskFn(prefsBrowser.contentWindow);
+ } finally {
+ if (gBrowser.tabs.length > 1) {
+ BrowserTestUtils.removeTab(gBrowser.getTabForBrowser(prefsBrowser));
+ } else {
+ BrowserTestUtils.loadURIString(prefsBrowser, "about:blank");
+ await BrowserTestUtils.browserLoaded(prefsBrowser);
+ }
+ }
+}
+
+/**
+ * Returns a Promise that resolves when an about:preferences tab opens
+ * in the current window which loads the migration wizard dialog.
+ * The Promise will wait until the migration wizard reports that it
+ * is ready with the "MigrationWizard:Ready" event.
+ *
+ * @returns {Promise<browser>}
+ * Resolves with the about:preferences browser element.
+ */
+async function waitForMigrationWizardDialogTab() {
+ let wizardReady = BrowserTestUtils.waitForEvent(
+ window,
+ "MigrationWizard:Ready"
+ );
+
+ let tab;
+ if (gBrowser.selectedTab.isEmpty) {
+ tab = gBrowser.selectedTab;
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => {
+ return url.startsWith("about:preferences");
+ });
+ } else {
+ tab = await BrowserTestUtils.waitForNewTab(gBrowser, url => {
+ return url.startsWith("about:preferences");
+ });
+ }
+
+ await wizardReady;
+ info("Done waiting - migration subdialog loaded and ready.");
+
+ return tab.linkedBrowser;
+}
+
+/**
+ * A helper function that prepares the InternalTestingProfileMigrator
+ * with some set of fake available resources, and resolves a Promise
+ * when the InternalTestingProfileMigrator is used for a migration.
+ *
+ * @param {number[]} availableResourceTypes
+ * An array of resource types from MigrationUtils.resourcesTypes.
+ * A single MigrationResource will be created per type, with a
+ * no-op migrate function.
+ * @param {number[]} expectedResourceTypes
+ * An array of resource types from MigrationUtils.resourceTypes.
+ * These are the resource types that are expected to be passed
+ * to the InternalTestingProfileMigrator.migrate function.
+ * @param {object|string} expectedProfile
+ * The profile object or string that is expected to be passed
+ * to the InternalTestingProfileMigrator.migrate function.
+ * @returns {Promise<undefined>}
+ */
+async function waitForTestMigration(
+ availableResourceTypes,
+ expectedResourceTypes,
+ expectedProfile
+) {
+ let sandbox = sinon.createSandbox();
+
+ // Fake out the getResources method of the migrator so that we return
+ // a single fake MigratorResource per availableResourceType.
+ sandbox
+ .stub(InternalTestingProfileMigrator.prototype, "getResources")
+ .callsFake(aProfile => {
+ Assert.deepEqual(
+ aProfile,
+ expectedProfile,
+ "Should have gotten the expected profile."
+ );
+ return Promise.resolve(
+ availableResourceTypes.map(resourceType => {
+ return {
+ type: resourceType,
+ migrate: () => {},
+ };
+ })
+ );
+ });
+
+ sandbox.stub(MigrationUtils, "_importQuantities").value({
+ bookmarks: EXPECTED_QUANTITY,
+ history: EXPECTED_QUANTITY,
+ logins: EXPECTED_QUANTITY,
+ cards: EXPECTED_QUANTITY,
+ });
+
+ // Fake out the migrate method of the migrator and assert that the
+ // next time it's called, its arguments match our expectations.
+ return new Promise(resolve => {
+ sandbox
+ .stub(InternalTestingProfileMigrator.prototype, "migrate")
+ .callsFake((aResourceTypes, aStartup, aProfile, aProgressCallback) => {
+ Assert.ok(
+ !aStartup,
+ "Migrator should not have been called as a startup migration."
+ );
+
+ let bitMask = 0;
+ for (let resourceType of expectedResourceTypes) {
+ bitMask |= resourceType;
+ }
+
+ Assert.deepEqual(
+ aResourceTypes,
+ bitMask,
+ "Got the expected resource types"
+ );
+ Assert.deepEqual(
+ aProfile,
+ expectedProfile,
+ "Got the expected profile object"
+ );
+
+ for (let resourceType of expectedResourceTypes) {
+ aProgressCallback(resourceType);
+ }
+ Services.obs.notifyObservers(null, "Migration:Ended");
+ resolve();
+ });
+ }).finally(async () => {
+ sandbox.restore();
+
+ // MigratorBase caches resources fetched by the getResources method
+ // as a performance optimization. In order to allow different tests
+ // to have different available resources, we call into a special
+ // method of InternalTestingProfileMigrator that clears that
+ // cache.
+ let migrator = await MigrationUtils.getMigrator(
+ InternalTestingProfileMigrator.key
+ );
+ migrator.flushResourceCache();
+ });
+}
+
+/**
+ * Takes a MigrationWizard element and chooses the
+ * InternalTestingProfileMigrator as the browser to migrate from. Then, it
+ * checks the checkboxes associated with the selectedResourceTypes and
+ * unchecks the rest before clicking the "Import" button.
+ *
+ * @param {Element} wizard
+ * The MigrationWizard element.
+ * @param {string[]} selectedResourceTypes
+ * An array of resource type strings from
+ * MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
+ * @param {string} [migratorKey=InternalTestingProfileMigrator.key]
+ * The key for the migrator to use. Defaults to the
+ * InternalTestingProfileMigrator.
+ */
+async function selectResourceTypesAndStartMigration(
+ wizard,
+ selectedResourceTypes,
+ migratorKey = InternalTestingProfileMigrator.key
+) {
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ // First, select the InternalTestingProfileMigrator browser.
+ let selector = shadow.querySelector("#browser-profile-selector");
+ selector.click();
+
+ await new Promise(resolve => {
+ wizard
+ .querySelector("panel-list")
+ .addEventListener("shown", resolve, { once: true });
+ });
+
+ let panelItem = wizard.querySelector(`panel-item[key="${migratorKey}"]`);
+ panelItem.click();
+
+ // And then check the right checkboxes for the resource types.
+ let resourceTypeList = shadow.querySelector("#resource-type-list");
+ for (let resourceType in MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES) {
+ let node = resourceTypeList.querySelector(
+ `label[data-resource-type="${resourceType}"]`
+ );
+ node.control.checked = selectedResourceTypes.includes(resourceType);
+ }
+
+ let importButton = shadow.querySelector("#import");
+ importButton.click();
+}
+
+/**
+ * Assert that the resource types passed in expectedResourceTypes are
+ * showing a success state after a migration, and if they are part of
+ * the RESOURCE_TYPES_WITH_QUANTITIES group, that they're showing the
+ * EXPECTED_QUANTITY magic number in their success message. Otherwise,
+ * we (currently) check that they show the empty string.
+ *
+ * @param {Element} wizard
+ * The MigrationWizard element.
+ * @param {string[]} expectedResourceTypes
+ * An array of resource type strings from
+ * MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
+ */
+function assertQuantitiesShown(wizard, expectedResourceTypes) {
+ let shadow = wizard.openOrClosedShadowRoot;
+
+ // Make sure that we're showing the progress page first.
+ let deck = shadow.querySelector("#wizard-deck");
+ Assert.equal(
+ deck.selectedViewName,
+ `page-${MigrationWizardConstants.PAGES.PROGRESS}`
+ );
+
+ // Go through each displayed resource and make sure that only the
+ // ones that are expected are shown, and are showing the right
+ // success message.
+
+ let progressGroups = shadow.querySelectorAll(".resource-progress-group");
+ for (let progressGroup of progressGroups) {
+ if (expectedResourceTypes.includes(progressGroup.dataset.resourceType)) {
+ let progressIcon = progressGroup.querySelector(".progress-icon");
+ let successText =
+ progressGroup.querySelector(".success-text").textContent;
+
+ Assert.ok(
+ progressIcon.classList.contains("completed"),
+ "Should be showing completed state."
+ );
+
+ if (
+ RESOURCE_TYPES_WITH_QUANTITIES.includes(
+ progressGroup.dataset.resourceType
+ )
+ ) {
+ if (
+ progressGroup.dataset.resourceType ==
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY
+ ) {
+ // HISTORY is a special case that doesn't show the number of imported
+ // history entries, but instead shows the maximum number of days of history
+ // that might have been imported.
+ Assert.notEqual(
+ successText.indexOf(MigrationUtils.HISTORY_MAX_AGE_IN_DAYS),
+ -1,
+ `Found expected maximum number of days of history: ${successText}`
+ );
+ } else if (
+ progressGroup.dataset.resourceType ==
+ MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA
+ ) {
+ // FORMDATA is another special case, because we simply show "Form history" as
+ // the success string, rather than a particular quantity.
+ Assert.equal(
+ successText,
+ "Form history",
+ `Found expected form data string: ${successText}`
+ );
+ } else {
+ Assert.notEqual(
+ successText.indexOf(EXPECTED_QUANTITY),
+ -1,
+ `Found expected quantity in success string: ${successText}`
+ );
+ }
+ } else {
+ // If you've found yourself here, and this is failing, it's probably because you've
+ // updated MigrationWizardParent.#getStringForImportQuantity to return a string for
+ // a resource type that's not in RESOURCE_TYPES_WITH_QUANTITIES, and you'll need
+ // to modify this function to check for that string.
+ Assert.equal(
+ successText,
+ "",
+ "Expected the empty string if the resource type " +
+ "isn't in RESOURCE_TYPES_WITH_QUANTITIES"
+ );
+ }
+ } else {
+ Assert.ok(
+ BrowserTestUtils.is_hidden(progressGroup),
+ `Resource progress group for ${progressGroup.dataset.resourceType}` +
+ ` should be hidden.`
+ );
+ }
+ }
+}