summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js')
-rw-r--r--browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js1596
1 files changed, 1596 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js
new file mode 100644
index 0000000000..92c3c7c95d
--- /dev/null
+++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js
@@ -0,0 +1,1596 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the buttons in the onboarding dialog for quick suggest/Firefox Suggest.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
+});
+
+const OTHER_DIALOG_URI = getRootDirectory(gTestPath) + "subdialog.xhtml";
+
+// Default-branch pref values in the offline scenario.
+const OFFLINE_DEFAULT_PREFS = {
+ "suggest.quicksuggest.nonsponsored": true,
+ "suggest.quicksuggest.sponsored": true,
+ "quicksuggest.dataCollection.enabled": false,
+};
+
+let gDefaultBranch = Services.prefs.getDefaultBranch("browser.urlbar.");
+let gUserBranch = Services.prefs.getBranch("browser.urlbar.");
+
+// Allow more time for Mac and Linux machines so they don't time out in verify mode.
+if (AppConstants.platform === "macosx") {
+ requestLongerTimeout(4);
+} else if (AppConstants.platform === "linux") {
+ requestLongerTimeout(2);
+}
+
+// Whether the tab key can move the focus. On macOS with full keyboard access
+// disabled (which is default), this will be false. See `canTabMoveFocus`.
+let gCanTabMoveFocus;
+add_setup(async function () {
+ gCanTabMoveFocus = await canTabMoveFocus();
+});
+
+// When the user has already enabled the data-collection pref, the dialog should
+// not appear.
+add_task(async function dataCollectionAlreadyEnabled() {
+ setDialogPrereqPrefs();
+ UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true);
+
+ info("Calling maybeShowOnboardingDialog");
+ let showed = await QuickSuggest.maybeShowOnboardingDialog();
+ Assert.ok(!showed, "The dialog was not shown");
+
+ UrlbarPrefs.clear("quicksuggest.dataCollection.enabled");
+});
+
+// When the current tab is about:welcome, the dialog should not appear.
+add_task(async function aboutWelcome() {
+ setDialogPrereqPrefs();
+ await BrowserTestUtils.withNewTab("about:welcome", async () => {
+ info("Calling maybeShowOnboardingDialog");
+ let showed = await QuickSuggest.maybeShowOnboardingDialog();
+ Assert.ok(!showed, "The dialog was not shown");
+ });
+});
+
+// The Escape key should dismiss the dialog without opting in. This task tests
+// when Escape is pressed while the focus is inside the dialog.
+add_task(async function escKey_focusInsideDialog() {
+ await doDialogTest({
+ callback: async () => {
+ const { maybeShowPromise } = await showOnboardingDialog({
+ skipIntroduction: true,
+ });
+
+ const tabCount = gBrowser.tabs.length;
+ Assert.ok(
+ document.activeElement.classList.contains("dialogFrame"),
+ "dialogFrame is focused in the browser window"
+ );
+
+ info("Close the dialog");
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ await maybeShowPromise;
+
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ "about:blank",
+ "Nothing loaded in the current tab"
+ );
+ Assert.equal(gBrowser.tabs.length, tabCount, "No news tabs were opened");
+ },
+ onboardingDialogVersion: JSON.stringify({ version: 1 }),
+ onboardingDialogChoice: "dismiss_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "dismiss_2",
+ },
+ ],
+ });
+});
+
+// The Escape key should dismiss the dialog without opting in. This task tests
+// when Escape is pressed while the focus is outside the dialog.
+add_task(async function escKey_focusOutsideDialog() {
+ await doDialogTest({
+ callback: async () => {
+ const { maybeShowPromise } = await showOnboardingDialog({
+ skipIntroduction: true,
+ });
+
+ document.documentElement.focus();
+ Assert.ok(
+ !document.activeElement.classList.contains("dialogFrame"),
+ "dialogFrame is not focused in the browser window"
+ );
+
+ info("Close the dialog");
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ await maybeShowPromise;
+ },
+ onboardingDialogVersion: JSON.stringify({ version: 1 }),
+ onboardingDialogChoice: "dismiss_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "dismiss_2",
+ },
+ ],
+ });
+});
+
+// The Escape key should dismiss the dialog without opting in when another
+// dialog is queued and shown before the onboarding. This task dismisses the
+// other dialog by pressing the Escape key.
+add_task(async function escKey_queued_esc() {
+ await doQueuedEscKeyTest("KEY_Escape");
+});
+
+// The Escape key should dismiss the dialog without opting in when another
+// dialog is queued and shown before the onboarding. This task dismisses the
+// other dialog by pressing the Enter key.
+add_task(async function escKey_queued_enter() {
+ await doQueuedEscKeyTest("KEY_Enter");
+});
+
+async function doQueuedEscKeyTest(otherDialogKey) {
+ await doDialogTest({
+ callback: async () => {
+ // Create promises that will resolve when each dialog is opened.
+ let uris = [OTHER_DIALOG_URI, QuickSuggest.ONBOARDING_URI];
+ let [otherOpenedPromise, onboardingOpenedPromise] = uris.map(uri =>
+ TestUtils.topicObserved(
+ "subdialog-loaded",
+ contentWin => contentWin.document.documentURI == uri
+ ).then(async ([contentWin]) => {
+ if (contentWin.document.readyState != "complete") {
+ await BrowserTestUtils.waitForEvent(contentWin, "load");
+ }
+ })
+ );
+
+ info("Queuing dialogs for opening");
+ let otherClosedPromise = gDialogBox.open(OTHER_DIALOG_URI);
+ let onboardingClosedPromise = QuickSuggest.maybeShowOnboardingDialog();
+
+ info("Waiting for the other dialog to open");
+ await otherOpenedPromise;
+
+ info(`Pressing ${otherDialogKey} and waiting for other dialog to close`);
+ EventUtils.synthesizeKey(otherDialogKey);
+ await otherClosedPromise;
+
+ info("Waiting for the onboarding dialog to open");
+ await onboardingOpenedPromise;
+
+ info("Pressing Escape and waiting for onboarding dialog to close");
+ EventUtils.synthesizeKey("KEY_Escape");
+ await onboardingClosedPromise;
+ },
+ onboardingDialogVersion: JSON.stringify({ version: 1 }),
+ onboardingDialogChoice: "dismiss_1",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "dismiss_1",
+ },
+ ],
+ });
+}
+
+// Tests `dismissed_other` by closing the dialog programmatically.
+add_task(async function dismissed_other_on_introduction() {
+ await doDialogTest({
+ callback: async () => {
+ const { maybeShowPromise } = await showOnboardingDialog();
+ gDialogBox._dialog.close();
+ await maybeShowPromise;
+ },
+ onboardingDialogVersion: JSON.stringify({ version: 1 }),
+ onboardingDialogChoice: "dismiss_1",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "dismiss_1",
+ },
+ ],
+ });
+});
+
+// The default is to wait for no browser restarts to show the onboarding dialog
+// on the first restart. This tests that we can override it by configuring the
+// `showOnboardingDialogOnNthRestart`
+add_task(async function nimbus_override_wait_after_n_restarts() {
+ UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);
+
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ quickSuggestScenario: "online",
+ // Wait for 1 browser restart
+ quickSuggestShowOnboardingDialogAfterNRestarts: 1,
+ },
+ callback: async () => {
+ let prefPromise = TestUtils.waitForPrefChange(
+ "browser.urlbar.quicksuggest.showedOnboardingDialog",
+ value => value === true
+ ).then(() => info("Saw pref change"));
+
+ // Simulate 2 restarts. this function is only called by BrowserGlue
+ // on startup, the first restart would be where MR1 was shown then
+ // we will show onboarding the 2nd restart after that.
+ info("Simulating first restart");
+ await QuickSuggest.maybeShowOnboardingDialog();
+
+ info("Simulating second restart");
+ const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ null,
+ QuickSuggest.ONBOARDING_URI,
+ { isSubDialog: true }
+ );
+ const maybeShowPromise = QuickSuggest.maybeShowOnboardingDialog();
+ const win = await dialogPromise;
+ if (win.document.readyState != "complete") {
+ await BrowserTestUtils.waitForEvent(win, "load");
+ }
+ // Close dialog.
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ info("Waiting for maybeShowPromise and pref change");
+ await Promise.all([maybeShowPromise, prefPromise]);
+ },
+ });
+});
+
+add_task(async function nimbus_skip_onboarding_dialog() {
+ UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);
+
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ quickSuggestScenario: "online",
+ quickSuggestShouldShowOnboardingDialog: false,
+ },
+ callback: async () => {
+ // Simulate 3 restarts.
+ for (let i = 0; i < 3; i++) {
+ info(`Simulating restart ${i + 1}`);
+ await QuickSuggest.maybeShowOnboardingDialog();
+ }
+ Assert.ok(
+ !Services.prefs.getBoolPref(
+ "browser.urlbar.quicksuggest.showedOnboardingDialog",
+ false
+ ),
+ "The showed onboarding dialog pref should not be set"
+ );
+ },
+ });
+});
+
+add_task(async function nimbus_exposure_event() {
+ const testData = [
+ {
+ experimentType: "modal",
+ expectedRecorded: true,
+ },
+ {
+ experimentType: "best-match",
+ expectedRecorded: false,
+ },
+ {
+ expectedRecorded: false,
+ },
+ ];
+
+ for (const { experimentType, expectedRecorded } of testData) {
+ info(`Nimbus exposure event test for type:[${experimentType}]`);
+ UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);
+
+ await QuickSuggestTestUtils.clearExposureEvent();
+
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ quickSuggestScenario: "online",
+ experimentType,
+ },
+ callback: async () => {
+ info("Calling showOnboardingDialog");
+ const { maybeShowPromise } = await showOnboardingDialog();
+ EventUtils.synthesizeKey("KEY_Escape");
+ await maybeShowPromise;
+
+ info("Check the event");
+ await QuickSuggestTestUtils.assertExposureEvent(expectedRecorded);
+ },
+ });
+ }
+});
+
+const LOGO_TYPE = {
+ FIREFOX: 1,
+ MAGGLASS: 2,
+ ANIMATION_MAGGLASS: 3,
+};
+
+const VARIATION_TEST_DATA = [
+ {
+ name: "A",
+ introductionSection: {
+ logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-1",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ defaultFocusOrder: [
+ "onboardingNext",
+ "onboardingClose",
+ "onboardingNext",
+ ],
+ actions: ["onboardingClose", "onboardingNext"],
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.MAGGLASS,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-1",
+ "main-description": "firefox-suggest-onboarding-main-description-1",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-1",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-1",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ defaultFocusOrder: [
+ "onboardingNext",
+ "onboardingAccept",
+ "onboardingLearnMore",
+ "onboardingReject",
+ "onboardingSkipLink",
+ "onboardingAccept",
+ ],
+ acceptFocusOrder: [
+ "onboardingAccept",
+ "onboardingLearnMore",
+ "onboardingSubmit",
+ "onboardingSkipLink",
+ "onboardingAccept",
+ ],
+ rejectFocusOrder: [
+ "onboardingReject",
+ "onboardingSubmit",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ "onboardingReject",
+ ],
+ actions: [
+ "onboardingAccept",
+ "onboardingReject",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ ],
+ },
+ },
+ {
+ // We don't need to test the focus order and actions because the layout of
+ // variation B-H is as same as A.
+ name: "B",
+ introductionSection: {
+ logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-2",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.MAGGLASS,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-2",
+ "main-description": "firefox-suggest-onboarding-main-description-2",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-1",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-1",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "C",
+ introductionSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-3",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-3",
+ "main-description": "firefox-suggest-onboarding-main-description-3",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-1",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-1",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "D",
+ introductionSection: {
+ logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-4",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.MAGGLASS,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-4",
+ "main-description": "firefox-suggest-onboarding-main-description-4",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-2",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-2",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "E",
+ introductionSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-5",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-5",
+ "main-description": "firefox-suggest-onboarding-main-description-5",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-2",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-2",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "F",
+ introductionSection: {
+ logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-2",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-6",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.MAGGLASS,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-6",
+ "main-description": "firefox-suggest-onboarding-main-description-6",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-2",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-2",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "G",
+ introductionSection: {
+ logoType: LOGO_TYPE.ANIMATION_MAGGLASS,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-7",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.MAGGLASS,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-7",
+ "main-description": "firefox-suggest-onboarding-main-description-7",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-2",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-2",
+ },
+ visibility: {
+ "#main-privacy-first": true,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "H",
+ introductionSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-1",
+ "introduction-title": "firefox-suggest-onboarding-introduction-title-2",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": false,
+ ".description-section": false,
+ ".pager": true,
+ },
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-8",
+ "main-description": "firefox-suggest-onboarding-main-description-8",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-1",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-1",
+ },
+ visibility: {
+ "#main-privacy-first": false,
+ ".description-section #onboardingLearnMore": false,
+ ".accept #onboardingLearnMore": true,
+ ".pager": true,
+ },
+ },
+ },
+ {
+ name: "100-A",
+ introductionSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ onboardingNext: "firefox-suggest-onboarding-introduction-next-button-3",
+ "introduction-title": "firefox-suggest-onboarding-main-title-9",
+ },
+ visibility: {
+ "#onboardingLearnMoreOnIntroduction": true,
+ ".description-section": true,
+ ".pager": true,
+ },
+ defaultFocusOrder: [
+ "onboardingNext",
+ "onboardingLearnMoreOnIntroduction",
+ "onboardingClose",
+ "onboardingNext",
+ ],
+ actions: [
+ "onboardingClose",
+ "onboardingNext",
+ "onboardingLearnMoreOnIntroduction",
+ ],
+ },
+ mainSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-9",
+ "main-description": "firefox-suggest-onboarding-main-description-9",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label-2",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-3",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label-2",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-3",
+ },
+ visibility: {
+ "#main-privacy-first": true,
+ ".description-section #onboardingLearnMore": true,
+ ".accept #onboardingLearnMore": false,
+ ".pager": false,
+ },
+ defaultFocusOrder: [
+ "onboardingNext",
+ "onboardingLearnMore",
+ "onboardingAccept",
+ "onboardingReject",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ ],
+ acceptFocusOrder: [
+ "onboardingAccept",
+ "onboardingSubmit",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ "onboardingAccept",
+ ],
+ rejectFocusOrder: [
+ "onboardingReject",
+ "onboardingSubmit",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ "onboardingReject",
+ ],
+ actions: [
+ "onboardingAccept",
+ "onboardingReject",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ ],
+ },
+ },
+ {
+ name: "100-B",
+ mainSection: {
+ logoType: LOGO_TYPE.FIREFOX,
+ l10n: {
+ "main-title": "firefox-suggest-onboarding-main-title-9",
+ "main-description": "firefox-suggest-onboarding-main-description-9",
+ "main-accept-option-label":
+ "firefox-suggest-onboarding-main-accept-option-label-2",
+ "main-accept-option-description":
+ "firefox-suggest-onboarding-main-accept-option-description-3",
+ "main-reject-option-label":
+ "firefox-suggest-onboarding-main-reject-option-label-2",
+ "main-reject-option-description":
+ "firefox-suggest-onboarding-main-reject-option-description-3",
+ },
+ visibility: {
+ "#main-privacy-first": true,
+ ".description-section #onboardingLearnMore": true,
+ ".accept #onboardingLearnMore": false,
+ ".pager": false,
+ },
+ // Layout of 100-B is same as 100-A, but since there is no the introduction
+ // pane, only the default focus order on the main pane is a bit diffrence.
+ defaultFocusOrder: [
+ "onboardingLearnMore",
+ "onboardingAccept",
+ "onboardingReject",
+ "onboardingSkipLink",
+ "onboardingLearnMore",
+ ],
+ },
+ },
+];
+
+/**
+ * This test checks for differences due to variations in logo type, l10n text,
+ * element visibility, order of focus, actions, etc. The designation is on
+ * VARIATION_TEST_DATA. The items that can be specified are below.
+ *
+ * name: Specify the variation name.
+ *
+ * The following items are specified for each section.
+ * (introductionSection, mainSection).
+ *
+ * logoType:
+ * Specify the expected logo type. Please refer to LOGO_TYPE about the type.
+ *
+ * l10n:
+ * Specify the expected l10n id applied to elements.
+ *
+ * visibility:
+ * Specify the expected visibility of elements. The way to specify the element
+ * is using selector.
+ *
+ * defaultFocusOrder:
+ * Specify the expected focus order right after the section is appeared. The
+ * way to specify the element is using id.
+ *
+ * acceptFocusOrder:
+ * Specify the expected focus order after selecting accept option.
+ *
+ * rejectFocusOrder:
+ * Specify the expected focus order after selecting reject option.
+ *
+ * actions:
+ * Specify the action we want to verify such as clicking the close button. The
+ * available actions are below.
+ * - onboardingClose:
+ * Action of the close button “x” by mouse/keyboard.
+ * - onboardingNext:
+ * Action of the next button that transits from the introduction section to
+ * the main section by mouse/keyboard.
+ * - onboardingAccept:
+ * Action of the submit button by mouse/keyboard after selecting accept
+ * option by mouse/keyboard.
+ * - onboardingReject:
+ * Action of the submit button by mouse/keyboard after selecting reject
+ * option by mouse/keyboard.
+ * - onboardingSkipLink:
+ * Action of the skip link by mouse/keyboard.
+ * - onboardingLearnMore:
+ * Action of the learn more link by mouse/keyboard.
+ * - onboardingLearnMoreOnIntroduction:
+ * Action of the learn more link on the introduction section by
+ * mouse/keyboard.
+ */
+add_task(async function variation_test() {
+ for (const variation of VARIATION_TEST_DATA) {
+ info(`Test for variation [${variation.name}]`);
+
+ info("Do layout test");
+ await doLayoutTest(variation);
+
+ for (const action of variation.introductionSection?.actions || []) {
+ info(
+ `${action} test on the introduction section for variation [${variation.name}]`
+ );
+ await this[action](variation);
+ }
+
+ for (const action of variation.mainSection?.actions || []) {
+ info(
+ `${action} test on the main section for variation [${variation.name}]`
+ );
+ await this[action](variation, !!variation.introductionSection);
+ }
+ }
+});
+
+async function doLayoutTest(variation) {
+ UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);
+
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ quickSuggestScenario: "online",
+ quickSuggestOnboardingDialogVariation: variation.name,
+ },
+ callback: async () => {
+ info("Calling showOnboardingDialog");
+ const { win, maybeShowPromise } = await showOnboardingDialog();
+
+ const introductionSection = win.document.getElementById(
+ "introduction-section"
+ );
+ const mainSection = win.document.getElementById("main-section");
+
+ if (variation.introductionSection) {
+ info("Check the section visibility");
+ Assert.ok(BrowserTestUtils.is_visible(introductionSection));
+ Assert.ok(BrowserTestUtils.is_hidden(mainSection));
+
+ info("Check the introduction section");
+ await assertSection(introductionSection, variation.introductionSection);
+
+ info("Transition to the main section");
+ win.document.getElementById("onboardingNext").click();
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ BrowserTestUtils.is_hidden(introductionSection) &&
+ BrowserTestUtils.is_visible(mainSection)
+ );
+ } else {
+ info("Check the section visibility");
+ Assert.ok(BrowserTestUtils.is_hidden(introductionSection));
+ Assert.ok(BrowserTestUtils.is_visible(mainSection));
+ }
+
+ info("Check the main section");
+ await assertSection(mainSection, variation.mainSection);
+
+ info("Close the dialog");
+ EventUtils.synthesizeKey("KEY_Escape", {}, win);
+ await maybeShowPromise;
+ },
+ });
+}
+
+async function assertSection(sectionElement, expectedSection) {
+ info("Check the logo");
+ assertLogo(sectionElement, expectedSection.logoType);
+
+ info("Check the l10n");
+ assertL10N(sectionElement, expectedSection.l10n);
+
+ info("Check the visibility");
+ assertVisibility(sectionElement, expectedSection.visibility);
+
+ if (!gCanTabMoveFocus) {
+ Assert.ok(true, "Tab key can't move focus, skipping test for focus order");
+ return;
+ }
+
+ if (expectedSection.defaultFocusOrder) {
+ info("Check the default focus order");
+ assertFocusOrder(sectionElement, expectedSection.defaultFocusOrder);
+ }
+
+ if (expectedSection.acceptFocusOrder) {
+ info("Check the focus order after selecting accept option");
+ sectionElement.querySelector("#onboardingAccept").focus();
+ EventUtils.synthesizeKey("VK_SPACE", {}, sectionElement.ownerGlobal);
+ assertFocusOrder(sectionElement, expectedSection.acceptFocusOrder);
+ }
+
+ if (expectedSection.rejectFocusOrder) {
+ info("Check the focus order after selecting reject option");
+ sectionElement.querySelector("#onboardingReject").focus();
+ EventUtils.synthesizeKey("VK_SPACE", {}, sectionElement.ownerGlobal);
+ assertFocusOrder(sectionElement, expectedSection.rejectFocusOrder);
+ }
+}
+
+function assertLogo(sectionElement, expectedLogoType) {
+ let expectedLogoImage;
+ switch (expectedLogoType) {
+ case LOGO_TYPE.FIREFOX: {
+ expectedLogoImage = 'url("chrome://branding/content/about-logo.svg")';
+ break;
+ }
+ case LOGO_TYPE.MAGGLASS: {
+ expectedLogoImage =
+ 'url("chrome://browser/content/urlbar/quicksuggestOnboarding_magglass.svg")';
+ break;
+ }
+ case LOGO_TYPE.ANIMATION_MAGGLASS: {
+ const mediaQuery = sectionElement.ownerGlobal.matchMedia(
+ "(prefers-reduced-motion: no-preference)"
+ );
+ expectedLogoImage = mediaQuery.matches
+ ? 'url("chrome://browser/content/urlbar/quicksuggestOnboarding_magglass_animation.svg")'
+ : 'url("chrome://browser/content/urlbar/quicksuggestOnboarding_magglass.svg")';
+ break;
+ }
+ default: {
+ Assert.ok(false, `Unexpected image type ${expectedLogoType}`);
+ break;
+ }
+ }
+
+ const logo = sectionElement.querySelector(".logo");
+ Assert.ok(BrowserTestUtils.is_visible(logo));
+ const logoImage =
+ sectionElement.ownerGlobal.getComputedStyle(logo).backgroundImage;
+ Assert.equal(logoImage, expectedLogoImage);
+}
+
+function assertL10N(sectionElement, expectedL10N) {
+ for (const [id, l10n] of Object.entries(expectedL10N)) {
+ const element = sectionElement.querySelector("#" + id);
+ Assert.equal(element.getAttribute("data-l10n-id"), l10n);
+ }
+}
+
+function assertVisibility(sectionElement, expectedVisibility) {
+ for (const [selector, visibility] of Object.entries(expectedVisibility)) {
+ const element = sectionElement.querySelector(selector);
+ if (visibility) {
+ Assert.ok(BrowserTestUtils.is_visible(element));
+ } else {
+ if (!element) {
+ Assert.ok(true);
+ return;
+ }
+ Assert.ok(BrowserTestUtils.is_hidden(element));
+ }
+ }
+}
+
+function assertFocusOrder(sectionElement, expectedFocusOrder) {
+ const win = sectionElement.ownerGlobal;
+
+ // Check initial active element.
+ Assert.equal(win.document.activeElement.id, expectedFocusOrder[0]);
+
+ for (const next of expectedFocusOrder.slice(1)) {
+ EventUtils.synthesizeKey("KEY_Tab", {}, win);
+ Assert.equal(win.document.activeElement.id, next);
+ }
+}
+
+async function onboardingClose(variation) {
+ await doActionTest({
+ callback: async (win, userAction, maybeShowPromise) => {
+ info("Check the status of the close button");
+ const closeButton = win.document.getElementById("onboardingClose");
+ Assert.ok(BrowserTestUtils.is_visible(closeButton));
+ Assert.equal(closeButton.getAttribute("title"), "Close");
+
+ info("Commit the close button");
+ userAction(closeButton);
+
+ info("Waiting for maybeShowOnboardingDialog to finish");
+ await maybeShowPromise;
+ },
+ variation,
+ onboardingDialogVersion: JSON.stringify({
+ version: 1,
+ variation: variation.name.toLowerCase(),
+ }),
+ onboardingDialogChoice: "close_1",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "close_1",
+ },
+ ],
+ });
+}
+
+async function onboardingNext(variation) {
+ await doActionTest({
+ callback: async (win, userAction, maybeShowPromise) => {
+ info("Check the status of the next button");
+ const nextButton = win.document.getElementById("onboardingNext");
+ Assert.ok(BrowserTestUtils.is_visible(nextButton));
+
+ info("Commit the next button");
+ userAction(nextButton);
+
+ const introductionSection = win.document.getElementById(
+ "introduction-section"
+ );
+ const mainSection = win.document.getElementById("main-section");
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ BrowserTestUtils.is_hidden(introductionSection) &&
+ BrowserTestUtils.is_visible(mainSection),
+ "Wait for the transition"
+ );
+
+ info("Exit");
+ EventUtils.synthesizeKey("KEY_Escape", {}, win);
+
+ info("Waiting for maybeShowOnboardingDialog to finish");
+ await maybeShowPromise;
+ },
+ variation,
+ onboardingDialogVersion: JSON.stringify({
+ version: 1,
+ variation: variation.name.toLowerCase(),
+ }),
+ onboardingDialogChoice: "dismiss_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "dismiss_2",
+ },
+ ],
+ });
+}
+
+async function onboardingAccept(variation, skipIntroduction) {
+ await doActionTest({
+ callback: async (win, userAction, maybeShowPromise) => {
+ info("Check the status of the accept option and submit button");
+ const acceptOption = win.document.getElementById("onboardingAccept");
+ const submitButton = win.document.getElementById("onboardingSubmit");
+ Assert.ok(acceptOption);
+ Assert.ok(submitButton.disabled);
+
+ info("Select the accept option");
+ userAction(acceptOption);
+
+ info("Commit the submit button");
+ Assert.ok(!submitButton.disabled);
+ userAction(submitButton);
+
+ info("Waiting for maybeShowOnboardingDialog to finish");
+ await maybeShowPromise;
+ },
+ variation,
+ skipIntroduction,
+ onboardingDialogVersion: JSON.stringify({
+ version: 1,
+ variation: variation.name.toLowerCase(),
+ }),
+ onboardingDialogChoice: "accept_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.onboardingDialogVersion": JSON.stringify({ version: 1 }),
+ "quicksuggest.dataCollection.enabled": true,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "enabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "accept_2",
+ },
+ ],
+ });
+}
+
+async function onboardingReject(variation, skipIntroduction) {
+ await doActionTest({
+ callback: async (win, userAction, maybeShowPromise) => {
+ info("Check the status of the reject option and submit button");
+ const rejectOption = win.document.getElementById("onboardingReject");
+ const submitButton = win.document.getElementById("onboardingSubmit");
+ Assert.ok(rejectOption);
+ Assert.ok(submitButton.disabled);
+
+ info("Select the reject option");
+ userAction(rejectOption);
+
+ info("Commit the submit button");
+ Assert.ok(!submitButton.disabled);
+ userAction(submitButton);
+
+ info("Waiting for maybeShowOnboardingDialog to finish");
+ await maybeShowPromise;
+ },
+ variation,
+ skipIntroduction,
+ onboardingDialogVersion: JSON.stringify({
+ version: 1,
+ variation: variation.name.toLowerCase(),
+ }),
+ onboardingDialogChoice: "reject_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "reject_2",
+ },
+ ],
+ });
+}
+
+async function onboardingSkipLink(variation, skipIntroduction) {
+ await doActionTest({
+ callback: async (win, userAction, maybeShowPromise) => {
+ info("Check the status of the skip link");
+ const skipLink = win.document.getElementById("onboardingSkipLink");
+ Assert.ok(BrowserTestUtils.is_visible(skipLink));
+
+ info("Commit the skip link");
+ const tabCount = gBrowser.tabs.length;
+ userAction(skipLink);
+
+ info("Waiting for maybeShowOnboardingDialog to finish");
+ await maybeShowPromise;
+
+ info("Check the current tab status");
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ "about:blank",
+ "Nothing loaded in the current tab"
+ );
+ Assert.equal(gBrowser.tabs.length, tabCount, "No news tabs were opened");
+ },
+ variation,
+ skipIntroduction,
+ onboardingDialogVersion: JSON.stringify({
+ version: 1,
+ variation: variation.name.toLowerCase(),
+ }),
+ onboardingDialogChoice: "not_now_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "not_now_2",
+ },
+ ],
+ });
+}
+
+async function onboardingLearnMore(variation, skipIntroduction) {
+ await doLearnMoreTest(
+ variation,
+ skipIntroduction,
+ "onboardingLearnMore",
+ "learn_more_2"
+ );
+}
+
+async function onboardingLearnMoreOnIntroduction(variation, skipIntroduction) {
+ await doLearnMoreTest(
+ variation,
+ skipIntroduction,
+ "onboardingLearnMoreOnIntroduction",
+ "learn_more_1"
+ );
+}
+
+async function doLearnMoreTest(variation, skipIntroduction, target, telemetry) {
+ await doActionTest({
+ callback: async (win, userAction, maybeShowPromise) => {
+ info("Check the status of the learn more link");
+ const learnMoreLink = win.document.getElementById(target);
+ Assert.ok(BrowserTestUtils.is_visible(learnMoreLink));
+
+ info("Commit the learn more link");
+ const loadPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ QuickSuggest.HELP_URL
+ ).then(tab => {
+ info("Saw new tab");
+ return tab;
+ });
+ userAction(learnMoreLink);
+
+ info("Waiting for maybeShowOnboardingDialog to finish");
+ await maybeShowPromise;
+
+ info("Waiting for new tab");
+ let tab = await loadPromise;
+
+ info("Check the current tab status");
+ Assert.equal(gBrowser.selectedTab, tab, "Current tab is the new tab");
+ Assert.equal(
+ gBrowser.currentURI.spec,
+ QuickSuggest.HELP_URL,
+ "Current tab is the support page"
+ );
+ BrowserTestUtils.removeTab(tab);
+ },
+ variation,
+ skipIntroduction,
+ onboardingDialogChoice: telemetry,
+ onboardingDialogVersion: JSON.stringify({
+ version: 1,
+ variation: variation.name.toLowerCase(),
+ }),
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: telemetry,
+ },
+ ],
+ });
+}
+
+async function doActionTest({
+ variation,
+ skipIntroduction,
+ callback,
+ onboardingDialogVersion,
+ onboardingDialogChoice,
+ expectedUserBranchPrefs,
+ telemetryEvents,
+}) {
+ const userClick = target => {
+ info("Click on the target");
+ target.click();
+ };
+ const userEnter = target => {
+ target.focus();
+ if (target.type === "radio") {
+ info("Space on the target");
+ EventUtils.synthesizeKey("VK_SPACE", {}, target.ownerGlobal);
+ } else {
+ info("Enter on the target");
+ EventUtils.synthesizeKey("KEY_Enter", {}, target.ownerGlobal);
+ }
+ };
+
+ for (const userAction of [userClick, userEnter]) {
+ UrlbarPrefs.clear("quicksuggest.shouldShowOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.showedOnboardingDialog");
+ UrlbarPrefs.clear("quicksuggest.seenRestarts", 0);
+
+ await QuickSuggestTestUtils.withExperiment({
+ valueOverrides: {
+ quickSuggestScenario: "online",
+ quickSuggestOnboardingDialogVariation: variation.name,
+ },
+ callback: async () => {
+ await doDialogTest({
+ callback: async () => {
+ info("Calling showOnboardingDialog");
+ const { win, maybeShowPromise } = await showOnboardingDialog({
+ skipIntroduction,
+ });
+
+ await callback(win, userAction, maybeShowPromise);
+ },
+ onboardingDialogVersion,
+ onboardingDialogChoice,
+ expectedUserBranchPrefs,
+ telemetryEvents,
+ });
+ },
+ });
+ }
+}
+
+async function doDialogTest({
+ callback,
+ onboardingDialogVersion,
+ onboardingDialogChoice,
+ telemetryEvents,
+ expectedUserBranchPrefs,
+}) {
+ setDialogPrereqPrefs();
+
+ // Set initial prefs on the default branch.
+ let initialDefaultBranch = OFFLINE_DEFAULT_PREFS;
+ let originalDefaultBranch = {};
+ for (let [name, value] of Object.entries(initialDefaultBranch)) {
+ originalDefaultBranch = gDefaultBranch.getBoolPref(name);
+ gDefaultBranch.setBoolPref(name, value);
+ gUserBranch.clearUserPref(name);
+ }
+
+ // Setting the prefs just now triggered telemetry events, so clear them
+ // before calling the callback.
+ Services.telemetry.clearEvents();
+
+ // Call the callback, which should trigger the dialog and interact with it.
+ await BrowserTestUtils.withNewTab("about:blank", async () => {
+ await callback();
+ });
+
+ // Now check all pref values on the default and user branches.
+ for (let [name, value] of Object.entries(initialDefaultBranch)) {
+ Assert.equal(
+ gDefaultBranch.getBoolPref(name),
+ value,
+ "Default-branch value for pref did not change after modal: " + name
+ );
+
+ let effectiveValue;
+ if (name in expectedUserBranchPrefs) {
+ effectiveValue = expectedUserBranchPrefs[name];
+ Assert.equal(
+ gUserBranch.getBoolPref(name),
+ effectiveValue,
+ "User-branch value for pref has expected value: " + name
+ );
+ } else {
+ effectiveValue = value;
+ Assert.ok(
+ !gUserBranch.prefHasUserValue(name),
+ "User-branch value for pref does not exist: " + name
+ );
+ }
+
+ // For good measure, check the value returned by UrlbarPrefs.
+ Assert.equal(
+ UrlbarPrefs.get(name),
+ effectiveValue,
+ "Effective value for pref is correct: " + name
+ );
+ }
+
+ Assert.equal(
+ UrlbarPrefs.get("quicksuggest.onboardingDialogVersion"),
+ onboardingDialogVersion,
+ "onboardingDialogVersion"
+ );
+ Assert.equal(
+ UrlbarPrefs.get("quicksuggest.onboardingDialogChoice"),
+ onboardingDialogChoice,
+ "onboardingDialogChoice"
+ );
+ Assert.equal(
+ TelemetryEnvironment.currentEnvironment.settings.userPrefs[
+ "browser.urlbar.quicksuggest.onboardingDialogChoice"
+ ],
+ onboardingDialogChoice,
+ "onboardingDialogChoice is correct in TelemetryEnvironment"
+ );
+
+ QuickSuggestTestUtils.assertEvents(telemetryEvents);
+
+ Assert.ok(
+ UrlbarPrefs.get("quicksuggest.showedOnboardingDialog"),
+ "quicksuggest.showedOnboardingDialog is true after showing dialog"
+ );
+
+ // Clean up.
+ for (let [name, value] of Object.entries(originalDefaultBranch)) {
+ gDefaultBranch.setBoolPref(name, value);
+ }
+ for (let name of Object.keys(expectedUserBranchPrefs)) {
+ gUserBranch.clearUserPref(name);
+ }
+}
+
+/**
+ * Show onbaording dialog.
+ *
+ * @param {object} [options]
+ * The object options.
+ * @param {boolean} [options.skipIntroduction]
+ * If true, return dialog with skipping the introduction section.
+ * @returns {{ window, maybeShowPromise: Promise }}
+ * win: window object of the dialog.
+ * maybeShowPromise: Promise of QuickSuggest.maybeShowOnboardingDialog().
+ */
+async function showOnboardingDialog({ skipIntroduction } = {}) {
+ const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ null,
+ QuickSuggest.ONBOARDING_URI,
+ { isSubDialog: true }
+ );
+
+ const maybeShowPromise = QuickSuggest.maybeShowOnboardingDialog();
+
+ const win = await dialogPromise;
+ if (win.document.readyState != "complete") {
+ await BrowserTestUtils.waitForEvent(win, "load");
+ }
+
+ // Wait until all listers on onboarding dialog are ready.
+ await window._quicksuggestOnboardingReady;
+
+ if (!skipIntroduction) {
+ return { win, maybeShowPromise };
+ }
+
+ // Trigger the transition by pressing Enter on the Next button.
+ EventUtils.synthesizeKey("KEY_Enter");
+
+ const introductionSection = win.document.getElementById(
+ "introduction-section"
+ );
+ const mainSection = win.document.getElementById("main-section");
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ BrowserTestUtils.is_hidden(introductionSection) &&
+ BrowserTestUtils.is_visible(mainSection)
+ );
+
+ return { win, maybeShowPromise };
+}
+
+/**
+ * Sets all the required prefs for showing the onboarding dialog except for the
+ * prefs that are set when the dialog is accepted.
+ */
+function setDialogPrereqPrefs() {
+ UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", true);
+ UrlbarPrefs.set("quicksuggest.showedOnboardingDialog", false);
+}
+
+/**
+ * This is a real hacky way of determining whether the tab key can move focus.
+ * Windows and Linux both support it but macOS does not unless full keyboard
+ * access is enabled, so practically this is only useful on macOS. Gecko seems
+ * to know whether full keyboard access is enabled because it affects focus in
+ * Firefox and some code in nsXULElement.cpp and other places mention it, but
+ * there doesn't seem to be a way to access that information from JS. There is
+ * `Services.focus.elementIsFocusable`, but it returns true regardless of
+ * whether full access is enabled.
+ *
+ * So what we do here is open the dialog and synthesize a tab key. If the focus
+ * doesn't change, then we assume moving the focus via the tab key is not
+ * supported.
+ *
+ * Why not just always skip the focus tasks on Mac? Because individual
+ * developers (like the one writing this comment) may be running macOS with full
+ * keyboard access enabled and want to actually run the tasks on their machines.
+ *
+ * @returns {boolean}
+ */
+async function canTabMoveFocus() {
+ if (AppConstants.platform != "macosx") {
+ return true;
+ }
+
+ let canMove = false;
+ await doDialogTest({
+ callback: async () => {
+ const { win, maybeShowPromise } = await showOnboardingDialog({
+ skipIntroduction: true,
+ });
+
+ let doc = win.document;
+ doc.getElementById("onboardingAccept").focus();
+ EventUtils.synthesizeKey("KEY_Tab");
+
+ // Whether or not the focus can move to the link.
+ canMove = doc.activeElement.id === "onboardingLearnMore";
+
+ EventUtils.synthesizeKey("KEY_Escape");
+ await maybeShowPromise;
+ },
+ onboardingDialogVersion: JSON.stringify({ version: 1 }),
+ onboardingDialogChoice: "dismiss_2",
+ expectedUserBranchPrefs: {
+ "quicksuggest.dataCollection.enabled": false,
+ },
+ telemetryEvents: [
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "data_collect_toggled",
+ object: "disabled",
+ },
+ {
+ category: QuickSuggest.TELEMETRY_EVENT_CATEGORY,
+ method: "opt_in_dialog",
+ object: "dismiss_2",
+ },
+ ],
+ });
+
+ return canMove;
+}