diff options
Diffstat (limited to 'browser/components/urlbar/tests/engagementTelemetry')
43 files changed, 5831 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml b/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml new file mode 100644 index 0000000000..68a7881399 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml @@ -0,0 +1,87 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +support-files = [ + "head.js", + "head-search_engine_default_id.js", + "head-exposure.js", + "head-groups.js", + "head-interaction.js", + "head-n_chars_n_words.js", + "head-sap.js", + "head-search_mode.js", + "../../browser-tips/head.js", +] +prefs = ["browser.bookmarks.testing.skipDefaultBookmarksImport=true"] + +["browser_glean_telemetry_abandonment_groups.js"] + +["browser_glean_telemetry_abandonment_interaction.js"] + +["browser_glean_telemetry_abandonment_interaction_persisted_search_terms_disabled.js"] + +["browser_glean_telemetry_abandonment_interaction_persisted_search_terms_enabled.js"] + +["browser_glean_telemetry_abandonment_n_chars_n_words.js"] + +["browser_glean_telemetry_abandonment_sap.js"] + +["browser_glean_telemetry_abandonment_search_engine_default_id.js"] + +["browser_glean_telemetry_abandonment_search_mode.js"] + +["browser_glean_telemetry_abandonment_tips.js"] + +["browser_glean_telemetry_engagement_edge_cases.js"] + +["browser_glean_telemetry_engagement_groups.js"] + +["browser_glean_telemetry_engagement_interaction.js"] + +["browser_glean_telemetry_engagement_interaction_persisted_search_terms_disabled.js"] + +["browser_glean_telemetry_engagement_interaction_persisted_search_terms_enabled.js"] + +["browser_glean_telemetry_engagement_n_chars_n_words.js"] + +["browser_glean_telemetry_engagement_sap.js"] + +["browser_glean_telemetry_engagement_search_engine_default_id.js"] + +["browser_glean_telemetry_engagement_search_mode.js"] + +["browser_glean_telemetry_engagement_selected_result.js"] +support-files = ["../../../../search/test/browser/trendingSuggestionEngine.sjs"] +skip-if = ["verify"] # Bug 1852375 - MerinoTestUtils.initWeather() doesn't play well with pushPrefEnv() + +["browser_glean_telemetry_engagement_tips.js"] + +["browser_glean_telemetry_engagement_type.js"] + +["browser_glean_telemetry_exposure.js"] + +["browser_glean_telemetry_exposure_edge_cases.js"] + +["browser_glean_telemetry_impression_groups.js"] + +["browser_glean_telemetry_impression_interaction.js"] + +["browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js"] + +["browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js"] + +["browser_glean_telemetry_impression_n_chars_n_words.js"] + +["browser_glean_telemetry_impression_preferences.js"] + +["browser_glean_telemetry_impression_sap.js"] + +["browser_glean_telemetry_impression_search_engine_default_id.js"] + +["browser_glean_telemetry_impression_search_mode.js"] + +["browser_glean_telemetry_impression_timing.js"] + +["browser_glean_telemetry_record_preferences.js"] diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js new file mode 100644 index 0000000000..ce69d30517 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js @@ -0,0 +1,235 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - groups +// - results +// - n_results + +// This test has many subtests and can time out in verify mode. +requestLongerTimeout(5); + +add_setup(async function () { + await initGroupTest(); +}); + +add_task(async function heuristics() { + await doHeuristicsTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { groups: "heuristic", results: "search_engine" }, + ]), + }); +}); + +add_task(async function adaptive_history() { + await doAdaptiveHistoryTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,adaptive_history", + results: "search_engine,history", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function search_history() { + await doSearchHistoryTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,search_history,search_history", + results: "search_engine,search_history,search_history", + n_results: 3, + }, + ]), + }); +}); + +add_task(async function recent_search() { + await doRecentSearchTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "recent_search,suggested_index", + results: "recent_search,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function search_suggest() { + await doSearchSuggestTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,search_suggest,search_suggest", + results: "search_engine,search_suggest,search_suggest", + n_results: 3, + }, + ]), + }); + + await doTailSearchSuggestTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,search_suggest", + results: "search_engine,search_suggest", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function top_pick() { + await doTopPickTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,top_pick,search_suggest,search_suggest", + results: + "search_engine,merino_top_picks,search_suggest,search_suggest", + n_results: 4, + }, + ]), + }); +}); + +add_task(async function top_site() { + await doTopSiteTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "top_site,suggested_index", + results: "top_site,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function clipboard() { + await doClipboardTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "general,suggested_index", + results: "clipboard,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function remote_tab() { + await doRemoteTabTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,remote_tab", + results: "search_engine,remote_tab", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function addon() { + await doAddonTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "addon", + results: "addon", + n_results: 1, + }, + ]), + }); +}); + +add_task(async function general() { + await doGeneralBookmarkTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,suggested_index,general", + results: "search_engine,action,bookmark", + n_results: 3, + }, + ]), + }); + + await doGeneralHistoryTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,general", + results: "search_engine,history", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function suggest() { + await doSuggestTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,suggest", + results: UrlbarPrefs.get("quickSuggestRustEnabled") + ? "search_engine,rust_adm_nonsponsored" + : "search_engine,rs_adm_nonsponsored", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function about_page() { + await doAboutPageTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,about_page,about_page", + results: "search_engine,history,history", + n_results: 3, + }, + ]), + }); +}); + +add_task(async function suggested_index() { + await doSuggestedIndexTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([ + { + groups: "heuristic,suggested_index", + results: "search_engine,unit", + n_results: 2, + }, + ]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction.js new file mode 100644 index 0000000000..73820be059 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - interaction + +add_setup(async function () { + await initInteractionTest(); +}); + +add_task(async function topsites() { + await doTopsitesTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ interaction: "topsites" }]), + }); +}); + +add_task(async function typed() { + await doTypedTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ interaction: "typed" }]), + }); + + await doTypedWithResultsPopupTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ interaction: "typed" }]), + }); +}); + +add_task(async function pasted() { + await doPastedTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ interaction: "pasted" }]), + }); + + await doPastedWithResultsPopupTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ interaction: "pasted" }]), + }); +}); + +add_task(async function topsite_search() { + await doTopsitesSearchTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([{ interaction: "topsite_search" }]), + }); +}); + +add_task(async function returned_restarted_refined() { + await doReturnedRestartedRefinedTest({ + trigger: () => doBlur(), + assert: expected => + assertAbandonmentTelemetry([ + { interaction: "typed" }, + { interaction: expected }, + ]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction_persisted_search_terms_disabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction_persisted_search_terms_disabled.js new file mode 100644 index 0000000000..68799544b0 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction_persisted_search_terms_disabled.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test abandonment telemetry with persisted search terms disabled. + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.showSearchTerms.featureGate", false]], + }); +}); + +add_task(async function persisted_search_terms() { + await doPersistedSearchTermsTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ interaction: "typed" }]), + }); +}); + +add_task(async function persisted_search_terms_restarted_refined() { + await doPersistedSearchTermsRestartedRefinedTest({ + enabled: false, + trigger: () => doBlur(), + assert: expected => assertAbandonmentTelemetry([{ interaction: expected }]), + }); +}); + +add_task( + async function persisted_search_terms_restarted_refined_via_abandonment() { + await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled: false, + trigger: () => doBlur(), + assert: expected => + assertAbandonmentTelemetry([ + { interaction: "typed" }, + { interaction: expected }, + ]), + }); + } +); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction_persisted_search_terms_enabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction_persisted_search_terms_enabled.js new file mode 100644 index 0000000000..f0a217805f --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_interaction_persisted_search_terms_enabled.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test abandonment telemetry with persisted search terms enabled. + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.showSearchTerms.featureGate", true], + ["browser.urlbar.showSearchTerms.enabled", true], + ["browser.search.widget.inNavBar", false], + ], + }); +}); + +add_task(async function persisted_search_terms() { + await doPersistedSearchTermsTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([{ interaction: "persisted_search_terms" }]), + }); +}); + +add_task(async function persisted_search_terms_restarted_refined() { + await doPersistedSearchTermsRestartedRefinedTest({ + enabled: true, + trigger: () => doBlur(), + assert: expected => assertAbandonmentTelemetry([{ interaction: expected }]), + }); +}); + +add_task( + async function persisted_search_terms_restarted_refined_via_abandonment() { + await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled: true, + trigger: () => doBlur(), + assert: expected => + assertAbandonmentTelemetry([ + { interaction: "persisted_search_terms_restarted" }, + { interaction: expected }, + ]), + }); + } +); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_n_chars_n_words.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_n_chars_n_words.js new file mode 100644 index 0000000000..7427db8cbf --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_n_chars_n_words.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - n_chars +// - n_words + +add_setup(async function () { + await initNCharsAndNWordsTest(); +}); + +add_task(async function n_chars() { + await doNCharsTest({ + trigger: () => doBlur(), + assert: nChars => assertAbandonmentTelemetry([{ n_chars: nChars }]), + }); + + await doNCharsWithOverMaxTextLengthCharsTest({ + trigger: () => doBlur(), + assert: nChars => assertAbandonmentTelemetry([{ n_chars: nChars }]), + }); +}); + +add_task(async function n_words() { + await doNWordsTest({ + trigger: () => doBlur(), + assert: nWords => assertAbandonmentTelemetry([{ n_words: nWords }]), + }); + + await doNWordsWithOverMaxTextLengthCharsTest({ + trigger: () => doBlur(), + assert: nWords => assertAbandonmentTelemetry([{ n_words: nWords }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_sap.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_sap.js new file mode 100644 index 0000000000..3d0af65379 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_sap.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - sap + +add_setup(async function () { + await initSapTest(); +}); + +add_task(async function urlbar_newtab() { + await doUrlbarNewTabTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ sap: "urlbar_newtab" }]), + }); +}); + +add_task(async function urlbar() { + await doUrlbarTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ sap: "urlbar" }]), + }); +}); + +add_task(async function handoff() { + await doHandoffTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ sap: "handoff" }]), + }); +}); + +add_task(async function urlbar_addonpage() { + await doUrlbarAddonpageTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ sap: "urlbar_addonpage" }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_engine_default_id.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_engine_default_id.js new file mode 100644 index 0000000000..d64b540d25 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_engine_default_id.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - search_engine_default_id + +add_setup(async function () { + await initSearchEngineDefaultIdTest(); +}); + +add_task(async function basic() { + await doSearchEngineDefaultIdTest({ + trigger: () => doBlur(), + assert: engineId => + assertAbandonmentTelemetry([{ search_engine_default_id: engineId }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js new file mode 100644 index 0000000000..7edcc47a30 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js @@ -0,0 +1,54 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - search_mode + +add_setup(async function () { + await initSearchModeTest(); +}); + +add_task(async function not_search_mode() { + await doNotSearchModeTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ search_mode: "" }]), + }); +}); + +add_task(async function search_engine() { + await doSearchEngineTest({ + trigger: () => doBlur(), + assert: () => + assertAbandonmentTelemetry([{ search_mode: "search_engine" }]), + }); +}); + +add_task(async function bookmarks() { + await doBookmarksTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ search_mode: "bookmarks" }]), + }); +}); + +add_task(async function history() { + await doHistoryTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ search_mode: "history" }]), + }); +}); + +add_task(async function tabs() { + await doTabTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ search_mode: "tabs" }]), + }); +}); + +add_task(async function actions() { + await doActionsTest({ + trigger: () => doBlur(), + assert: () => assertAbandonmentTelemetry([{ search_mode: "actions" }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js new file mode 100644 index 0000000000..71087d03d0 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for abandonment telemetry for tips using Glean. + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser-tips/head.js", + this +); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.searchTips.test.ignoreShowLimits", true], + ["browser.urlbar.showSearchTerms.featureGate", true], + ], + }); + const engine = await SearchTestUtils.promiseNewSearchEngine({ + url: "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.xml", + }); + const originalDefaultEngine = await Services.search.getDefault(); + await Services.search.setDefault( + engine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + await Services.search.moveEngine(engine, 0); + + registerCleanupFunction(async function () { + await SpecialPowers.popPrefEnv(); + await Services.search.setDefault( + originalDefaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + resetSearchTipsProvider(); + }); +}); + +add_task(async function tip_persist() { + await doTest(async browser => { + await showPersistSearchTip("test"); + gURLBar.focus(); + await UrlbarTestUtils.promisePopupClose(window, () => { + gURLBar.blur(); + }); + + assertAbandonmentTelemetry([{ results: "tip_persist" }]); + }); +}); + +add_task(async function mouse_down_with_tip() { + await doTest(async browser => { + await showPersistSearchTip("test"); + await UrlbarTestUtils.promisePopupClose(window, () => { + // We intentionally turn off this a11y check, because the following click + // is sent to test the telemetry behavior using an alternative way of the + // urlbar dismissal, where other ways are accessible, therefore this test + // can be ignored. + AccessibilityUtils.setEnv({ + mustHaveAccessibleRule: false, + }); + EventUtils.synthesizeMouseAtCenter(browser, {}); + AccessibilityUtils.resetEnv(); + }); + + assertAbandonmentTelemetry([{ results: "tip_persist" }]); + }); +}); + +add_task(async function mouse_down_without_tip() { + await doTest(async browser => { + // We intentionally turn off this a11y check, because the following click + // is sent to test the telemetry behavior using an alternative way of the + // urlbar dismissal, where other ways are accessible, therefore this test + // can be ignored. + AccessibilityUtils.setEnv({ + mustHaveAccessibleRule: false, + }); + EventUtils.synthesizeMouseAtCenter(browser, {}); + AccessibilityUtils.resetEnv(); + + assertAbandonmentTelemetry([]); + }); +}); + +async function showPersistSearchTip(word) { + await openPopup(word); + await doEnter(); + await BrowserTestUtils.waitForCondition(async () => { + for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) { + const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i); + if (detail.result.payload?.type === "searchTip_persist") { + return true; + } + } + return false; + }); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js new file mode 100644 index 0000000000..fcac924879 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js @@ -0,0 +1,221 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test edge cases for engagement. + +add_setup(async function () { + await setup(); +}); + +/** + * UrlbarProvider that does not add any result. + */ +class NoResponseTestProvider extends UrlbarTestUtils.TestProvider { + constructor() { + super({ name: "TestProviderNoResponse ", results: [] }); + this.#deferred = Promise.withResolvers(); + } + + get type() { + return UrlbarUtils.PROVIDER_TYPE.HEURISTIC; + } + + async startQuery(context, addCallback) { + await this.#deferred.promise; + } + + done() { + this.#deferred.resolve(); + } + + #deferred = null; +} +const noResponseProvider = new NoResponseTestProvider(); + +/** + * UrlbarProvider that adds a heuristic result immediately as usual. + */ +class AnotherHeuristicProvider extends UrlbarTestUtils.TestProvider { + constructor({ results }) { + super({ name: "TestProviderAnotherHeuristic ", results }); + this.#deferred = Promise.withResolvers(); + } + + get type() { + return UrlbarUtils.PROVIDER_TYPE.HEURISTIC; + } + + async startQuery(context, addCallback) { + for (const result of this.results) { + addCallback(this, result); + } + + this.#deferred.resolve(context); + } + + onQueryStarted() { + return this.#deferred.promise; + } + + #deferred = null; +} +const anotherHeuristicProvider = new AnotherHeuristicProvider({ + results: [ + Object.assign( + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + { url: "https://example.com/immediate" } + ), + { heuristic: true } + ), + ], +}); + +add_task(async function engagement_before_showing_results() { + await SpecialPowers.pushPrefEnv({ + // Avoid showing search tip. + set: [["browser.urlbar.tipShownCount.searchTip_onboard", 999]], + }); + + // Increase chunk delays to delay the call to notifyResults. + let originalChunkTimeout = UrlbarProvidersManager.CHUNK_RESULTS_DELAY_MS; + UrlbarProvidersManager.CHUNK_RESULTS_DELAY_MS = 1000000; + + // Add a provider that waits forever in startQuery() to avoid fireing + // heuristicProviderTimer. + UrlbarProvidersManager.registerProvider(noResponseProvider); + + // Add a provider that add a result immediately as usual. + UrlbarProvidersManager.registerProvider(anotherHeuristicProvider); + + const cleanup = () => { + UrlbarProvidersManager.unregisterProvider(noResponseProvider); + UrlbarProvidersManager.unregisterProvider(anotherHeuristicProvider); + UrlbarProvidersManager.CHUNK_RESULTS_DELAY_MS = originalChunkTimeout; + }; + registerCleanupFunction(cleanup); + + await doTest(async browser => { + // Try to show the results. + await UrlbarTestUtils.inputIntoURLBar(window, "exam"); + + // Wait until starting the query and filling expected results. + const context = await anotherHeuristicProvider.onQueryStarted(); + const query = UrlbarProvidersManager.queries.get(context); + await BrowserTestUtils.waitForCondition( + () => + query.unsortedResults.some( + r => r.providerName === "HeuristicFallback" + ) && + query.unsortedResults.some( + r => r.providerName === anotherHeuristicProvider.name + ) + ); + + // Type Enter key before showing any results. + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "input_field", + selected_result_subtype: "", + provider: undefined, + results: "", + groups: "", + }, + ]); + + // Clear the pending query. + noResponseProvider.done(); + }); + + cleanup(); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function engagement_after_closing_results() { + const TRIGGERS = [ + () => EventUtils.synthesizeKey("KEY_Escape"), + () => { + // We intentionally turn off this a11y check, because the following click + // is sent to test the telemetry behavior using an alternative way of the + // urlbar dismissal, where other ways are accessible (and tested above), + // therefore this test can be ignored. + AccessibilityUtils.setEnv({ + mustHaveAccessibleRule: false, + }); + EventUtils.synthesizeMouseAtCenter( + document.getElementById("customizableui-special-spring2"), + {} + ); + AccessibilityUtils.resetEnv(); + }, + ]; + + for (const trigger of TRIGGERS) { + await doTest(async browser => { + await openPopup("test"); + await UrlbarTestUtils.promisePopupClose(window, () => { + trigger(); + }); + Assert.equal( + gURLBar.value, + "test", + "The inputted text remains even if closing the results" + ); + // The tested trigger should not record abandonment event. + assertAbandonmentTelemetry([]); + + // Endgagement. + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "input_field", + selected_result_subtype: "", + provider: undefined, + results: "", + groups: "", + }, + ]); + }); + } +}); + +add_task(async function enter_to_reload_current_url() { + await doTest(async browser => { + // Open a URL once. + await openPopup("https://example.com"); + await doEnter(); + + // Focus the urlbar. + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); + await BrowserTestUtils.waitForCondition( + () => window.document.activeElement === gURLBar.inputField + ); + await UrlbarTestUtils.promiseSearchComplete(window); + + // Press Enter key to reload the page without selecting any suggestions. + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "url", + selected_result_subtype: "", + provider: "HeuristicFallback", + results: "url", + groups: "heuristic", + }, + { + selected_result: "input_field", + selected_result_subtype: "", + provider: undefined, + results: "action", + groups: "suggested_index", + }, + ]); + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js new file mode 100644 index 0000000000..8779487960 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js @@ -0,0 +1,292 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - groups +// - results +// - n_results + +// This test has many subtests and can time out in verify mode. +requestLongerTimeout(5); + +add_setup(async function () { + await initGroupTest(); +}); + +add_task(async function heuristics() { + await doHeuristicsTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { groups: "heuristic", results: "search_engine" }, + ]), + }); +}); + +add_task(async function adaptive_history() { + await doAdaptiveHistoryTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,adaptive_history", + results: "search_engine,history", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function search_history() { + await doSearchHistoryTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,search_history,search_history", + results: "search_engine,search_history,search_history", + n_results: 3, + }, + ]), + }); +}); + +add_task(async function recent_search() { + await doRecentSearchTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "recent_search,suggested_index", + results: "recent_search,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function search_suggest() { + await doSearchSuggestTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,search_suggest,search_suggest", + results: "search_engine,search_suggest,search_suggest", + n_results: 3, + }, + ]), + }); + + await doTailSearchSuggestTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,search_suggest", + results: "search_engine,search_suggest", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function top_pick() { + await doTopPickTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,top_pick,search_suggest,search_suggest", + results: + "search_engine,merino_top_picks,search_suggest,search_suggest", + n_results: 4, + }, + ]), + }); +}); + +add_task(async function top_site() { + await doTopSiteTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "top_site,suggested_index", + results: "top_site,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function clipboard() { + await doClipboardTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "general,suggested_index", + results: "clipboard,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function remote_tab() { + await doRemoteTabTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,remote_tab", + results: "search_engine,remote_tab", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function addon() { + await doAddonTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "addon", + results: "addon", + n_results: 1, + }, + ]), + }); +}); + +add_task(async function general() { + await doGeneralBookmarkTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,suggested_index,general", + results: "search_engine,action,bookmark", + n_results: 3, + }, + ]), + }); + + await doGeneralHistoryTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,general", + results: "search_engine,history", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function suggest() { + await doSuggestTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,suggest", + results: UrlbarPrefs.get("quickSuggestRustEnabled") + ? "search_engine,rust_adm_nonsponsored" + : "search_engine,rs_adm_nonsponsored", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function about_page() { + await doAboutPageTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,about_page,about_page", + results: "search_engine,history,history", + n_results: 3, + }, + ]), + }); +}); + +add_task(async function suggested_index() { + await doSuggestedIndexTest({ + trigger: () => + SimpleTest.promiseClipboardChange("100 cm", () => { + EventUtils.synthesizeKey("KEY_Enter"); + }), + assert: () => + assertEngagementTelemetry([ + { + groups: "heuristic,suggested_index", + results: "search_engine,unit", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function always_empty_if_drop_go() { + const expected = [ + { + engagement_type: "drop_go", + groups: "", + results: "", + n_results: 0, + }, + ]; + + await doTest(async browser => { + await doDropAndGo("example.com"); + + assertEngagementTelemetry(expected); + }); + + await doTest(async browser => { + // Open the results view once. + await showResultByArrowDown(); + await UrlbarTestUtils.promisePopupClose(window); + + await doDropAndGo("example.com"); + + assertEngagementTelemetry(expected); + }); +}); + +add_task(async function always_empty_if_paste_go() { + const expected = [ + { + engagement_type: "paste_go", + groups: "", + results: "", + n_results: 0, + }, + ]; + + await doTest(async browser => { + await doPasteAndGo("example.com"); + + assertEngagementTelemetry(expected); + }); + + await doTest(async browser => { + // Open the results view once. + await showResultByArrowDown(); + await UrlbarTestUtils.promisePopupClose(window); + + await doPasteAndGo("example.com"); + + assertEngagementTelemetry(expected); + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js new file mode 100644 index 0000000000..f4880d2205 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - interaction + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); +}); + +add_task(async function topsites() { + await doTopsitesTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ interaction: "topsites" }]), + }); +}); + +add_task(async function typed() { + await doTypedTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ interaction: "typed" }]), + }); + + await doTypedWithResultsPopupTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ interaction: "typed" }]), + }); +}); + +add_task(async function dropped() { + await doTest(async browser => { + await doDropAndGo("example.com"); + + assertEngagementTelemetry([{ interaction: "dropped" }]); + }); + + await doTest(async browser => { + await showResultByArrowDown(); + await doDropAndGo("example.com"); + + assertEngagementTelemetry([{ interaction: "dropped" }]); + }); +}); + +add_task(async function pasted() { + await doPastedTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ interaction: "pasted" }]), + }); + + await doPastedWithResultsPopupTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ interaction: "pasted" }]), + }); + + await doTest(async browser => { + await doPasteAndGo("www.example.com"); + + assertEngagementTelemetry([{ interaction: "pasted" }]); + }); + + await doTest(async browser => { + await showResultByArrowDown(); + await doPasteAndGo("www.example.com"); + + assertEngagementTelemetry([{ interaction: "pasted" }]); + }); +}); + +add_task(async function topsite_search() { + await doTopsitesSearchTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([{ interaction: "topsite_search" }]), + }); +}); + +add_task(async function returned_restarted_refined() { + await doReturnedRestartedRefinedTest({ + trigger: () => doEnter(), + assert: expected => assertEngagementTelemetry([{ interaction: expected }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction_persisted_search_terms_disabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction_persisted_search_terms_disabled.js new file mode 100644 index 0000000000..1bdb4f0b61 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction_persisted_search_terms_disabled.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test engagement telemetry with persisted search terms disabled. + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js", + this +); + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.showSearchTerms.featureGate", false]], + }); +}); + +add_task(async function persisted_search_terms() { + await doPersistedSearchTermsTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { interaction: "typed" }, + { interaction: "typed" }, + ]), + }); +}); + +add_task(async function persisted_search_terms_restarted_refined() { + await doPersistedSearchTermsRestartedRefinedTest({ + enabled: false, + trigger: () => doEnter(), + assert: expected => + assertEngagementTelemetry([ + { interaction: "typed" }, + { interaction: expected }, + ]), + }); +}); + +add_task( + async function persisted_search_terms_restarted_refined_via_abandonment() { + await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled: false, + trigger: () => doEnter(), + assert: expected => + assertEngagementTelemetry([ + { interaction: "typed" }, + { interaction: expected }, + ]), + }); + } +); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction_persisted_search_terms_enabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction_persisted_search_terms_enabled.js new file mode 100644 index 0000000000..33a01fdd22 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction_persisted_search_terms_enabled.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test engagement telemetry with persisted search terms enabled. + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.showSearchTerms.featureGate", true], + ["browser.urlbar.showSearchTerms.enabled", true], + ["browser.search.widget.inNavBar", false], + ], + }); +}); + +add_task(async function persisted_search_terms() { + await doPersistedSearchTermsTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([ + { interaction: "typed" }, + { interaction: "persisted_search_terms" }, + ]), + }); +}); + +add_task(async function persisted_search_terms_restarted_refined() { + await doPersistedSearchTermsRestartedRefinedTest({ + enabled: true, + trigger: () => doEnter(), + assert: expected => + assertEngagementTelemetry([ + { interaction: "typed" }, + { interaction: expected }, + ]), + }); +}); + +add_task( + async function persisted_search_terms_restarted_refined_via_abandonment() { + await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled: true, + trigger: () => doEnter(), + assert: expected => + assertEngagementTelemetry([ + { interaction: "typed" }, + { interaction: expected }, + ]), + }); + } +); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_n_chars_n_words.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_n_chars_n_words.js new file mode 100644 index 0000000000..498ffd9532 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_n_chars_n_words.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - n_chars +// - n_words + +add_setup(async function () { + await initNCharsAndNWordsTest(); +}); + +add_task(async function n_chars() { + await doNCharsTest({ + trigger: () => doEnter(), + assert: nChars => assertEngagementTelemetry([{ n_chars: nChars }]), + }); + + await doNCharsWithOverMaxTextLengthCharsTest({ + trigger: () => doEnter(), + assert: nChars => assertEngagementTelemetry([{ n_chars: nChars }]), + }); +}); + +add_task(async function n_words() { + await doNWordsTest({ + trigger: () => doEnter(), + assert: nWords => assertEngagementTelemetry([{ n_words: nWords }]), + }); + + await doNWordsWithOverMaxTextLengthCharsTest({ + trigger: () => doEnter(), + assert: nWords => assertEngagementTelemetry([{ n_words: nWords }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_sap.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_sap.js new file mode 100644 index 0000000000..d361d70229 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_sap.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - sap + +add_setup(async function () { + await initSapTest(); +}); + +add_task(async function urlbar() { + await doUrlbarTest({ + trigger: () => doEnter(), + assert: () => + assertEngagementTelemetry([{ sap: "urlbar_newtab" }, { sap: "urlbar" }]), + }); +}); + +add_task(async function handoff() { + await doHandoffTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ sap: "handoff" }]), + }); +}); + +add_task(async function urlbar_addonpage() { + await doUrlbarAddonpageTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ sap: "urlbar_addonpage" }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_engine_default_id.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_engine_default_id.js new file mode 100644 index 0000000000..60331ff53b --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_engine_default_id.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - search_engine_default_id + +add_setup(async function () { + await initSearchEngineDefaultIdTest(); +}); + +add_task(async function basic() { + await doSearchEngineDefaultIdTest({ + trigger: () => doEnter(), + assert: engineId => + assertEngagementTelemetry([{ search_engine_default_id: engineId }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js new file mode 100644 index 0000000000..013bef1904 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - search_mode + +add_setup(async function () { + await initSearchModeTest(); +}); + +add_task(async function not_search_mode() { + await doNotSearchModeTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ search_mode: "" }]), + }); +}); + +add_task(async function search_engine() { + await doSearchEngineTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ search_mode: "search_engine" }]), + }); +}); + +add_task(async function bookmarks() { + await doBookmarksTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ search_mode: "bookmarks" }]), + }); +}); + +add_task(async function history() { + await doHistoryTest({ + trigger: () => doEnter(), + assert: () => assertEngagementTelemetry([{ search_mode: "history" }]), + }); +}); + +add_task(async function tabs() { + await doTabTest({ + trigger: async () => { + const currentTab = gBrowser.selectedTab; + EventUtils.synthesizeKey("KEY_Enter"); + await BrowserTestUtils.waitForCondition( + () => gBrowser.selectedTab !== currentTab + ); + }, + assert: () => assertEngagementTelemetry([{ search_mode: "tabs" }]), + }); +}); + +add_task(async function actions() { + await doActionsTest({ + trigger: async () => { + const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + doClickSubButton(".urlbarView-quickaction-button[data-key=addons]"); + await onLoad; + }, + assert: () => assertEngagementTelemetry([{ search_mode: "actions" }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js new file mode 100644 index 0000000000..6a3422d939 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js @@ -0,0 +1,974 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - selected_result +// - selected_result_subtype +// - selected_position +// - provider +// - results + +ChromeUtils.defineESModuleGetters(this, { + UrlbarProviderClipboard: + "resource:///modules/UrlbarProviderClipboard.sys.mjs", +}); + +// This test has many subtests and can time out in verify mode. +requestLongerTimeout(5); + +add_setup(async function () { + await setup(); +}); + +add_task(async function selected_result_autofill_about() { + await doTest(async browser => { + await openPopup("about:about"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "autofill_about", + selected_result_subtype: "", + selected_position: 1, + provider: "Autofill", + results: "autofill_about", + }, + ]); + }); +}); + +add_task(async function selected_result_autofill_adaptive() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.autoFill.adaptiveHistory.enabled", true]], + }); + + await doTest(async browser => { + await PlacesTestUtils.addVisits("https://example.com/test"); + await UrlbarUtils.addToInputHistory("https://example.com/test", "exa"); + await openPopup("exa"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "autofill_adaptive", + selected_result_subtype: "", + selected_position: 1, + provider: "Autofill", + results: "autofill_adaptive", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_autofill_origin() { + await doTest(async browser => { + await PlacesTestUtils.addVisits("https://example.com/test"); + await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); + await openPopup("exa"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "autofill_origin", + selected_result_subtype: "", + selected_position: 1, + provider: "Autofill", + results: "autofill_origin,history", + }, + ]); + }); +}); + +add_task(async function selected_result_autofill_url() { + await doTest(async browser => { + await PlacesTestUtils.addVisits("https://example.com/test"); + await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); + await openPopup("https://example.com/test"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "autofill_url", + selected_result_subtype: "", + selected_position: 1, + provider: "Autofill", + results: "autofill_url", + }, + ]); + }); +}); + +add_task(async function selected_result_bookmark() { + await doTest(async browser => { + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "https://example.com/bookmark", + title: "bookmark", + }); + + await openPopup("bookmark"); + await selectRowByURL("https://example.com/bookmark"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "bookmark", + selected_result_subtype: "", + selected_position: 3, + provider: "Places", + results: "search_engine,action,bookmark", + }, + ]); + }); +}); + +add_task(async function selected_result_history() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.autoFill", false]], + }); + + await doTest(async browser => { + await PlacesTestUtils.addVisits("https://example.com/test"); + + await openPopup("example"); + await selectRowByURL("https://example.com/test"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "history", + selected_result_subtype: "", + selected_position: 2, + provider: "Places", + results: "search_engine,history", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_keyword() { + await doTest(async browser => { + await PlacesUtils.keywords.insert({ + keyword: "keyword", + url: "https://example.com/?q=%s", + }); + + await openPopup("keyword test"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "keyword", + selected_result_subtype: "", + selected_position: 1, + provider: "BookmarkKeywords", + results: "keyword", + }, + ]); + + await PlacesUtils.keywords.remove("keyword"); + }); +}); + +add_task(async function selected_result_search_engine() { + await doTest(async browser => { + await openPopup("x"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "search_engine", + selected_result_subtype: "", + selected_position: 1, + provider: "HeuristicFallback", + results: "search_engine", + }, + ]); + }); +}); + +add_task(async function selected_result_search_suggest() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.maxHistoricalSearchSuggestions", 2], + ], + }); + + await doTest(async browser => { + await openPopup("foo"); + await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "search_suggest", + selected_result_subtype: "", + selected_position: 2, + provider: "SearchSuggestions", + results: "search_engine,search_suggest,search_suggest", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_search_history() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.maxHistoricalSearchSuggestions", 2], + ], + }); + + await doTest(async browser => { + await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]); + + await openPopup("foo"); + await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "search_history", + selected_result_subtype: "", + selected_position: 3, + provider: "SearchSuggestions", + results: "search_engine,search_history,search_history", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_url() { + await doTest(async browser => { + await openPopup("https://example.com/"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "url", + selected_result_subtype: "", + selected_position: 1, + provider: "HeuristicFallback", + results: "url", + }, + ]); + }); +}); + +add_task(async function selected_result_action() { + await doTest(async browser => { + await showResultByArrowDown(); + await selectRowByProvider("quickactions"); + const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + doClickSubButton(".urlbarView-quickaction-button[data-key=addons]"); + await onLoad; + + assertEngagementTelemetry([ + { + selected_result: "action", + selected_result_subtype: "addons", + selected_position: 1, + provider: "quickactions", + results: "action", + }, + ]); + }); +}); + +add_task(async function selected_result_tab() { + const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/"); + + await doTest(async browser => { + await openPopup("example"); + await selectRowByProvider("Places"); + EventUtils.synthesizeKey("KEY_Enter"); + await BrowserTestUtils.waitForCondition(() => gBrowser.selectedTab === tab); + + assertEngagementTelemetry([ + { + selected_result: "tab", + selected_result_subtype: "", + selected_position: 4, + provider: "Places", + results: "search_engine,search_suggest,search_suggest,tab", + }, + ]); + }); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function selected_result_remote_tab() { + const remoteTab = await loadRemoteTab("https://example.com"); + + await doTest(async browser => { + await openPopup("example"); + await selectRowByProvider("RemoteTabs"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "remote_tab", + selected_result_subtype: "", + selected_position: 2, + provider: "RemoteTabs", + results: "search_engine,remote_tab", + }, + ]); + }); + + await remoteTab.unload(); +}); + +add_task(async function selected_result_addon() { + const addon = loadOmniboxAddon({ keyword: "omni" }); + await addon.startup(); + + await doTest(async browser => { + await openPopup("omni test"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "addon", + selected_result_subtype: "", + selected_position: 1, + provider: "Omnibox", + results: "addon", + }, + ]); + }); + + await addon.unload(); +}); + +add_task(async function selected_result_tab_to_search() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.tabToSearch.onboard.interactionsLeft", 0]], + }); + + await SearchTestUtils.installSearchExtension({ + name: "mozengine", + search_url: "https://mozengine/", + }); + + await doTest(async browser => { + for (let i = 0; i < 3; i++) { + await PlacesTestUtils.addVisits(["https://mozengine/"]); + } + + await openPopup("moze"); + await selectRowByProvider("TabToSearch"); + const onComplete = UrlbarTestUtils.promiseSearchComplete(window); + EventUtils.synthesizeKey("KEY_Enter"); + await onComplete; + + assertEngagementTelemetry([ + { + selected_result: "tab_to_search", + selected_result_subtype: "", + selected_position: 2, + provider: "TabToSearch", + results: "search_engine,tab_to_search,history", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_top_site() { + await doTest(async browser => { + await addTopSites("https://example.com/"); + await showResultByArrowDown(); + await selectRowByURL("https://example.com/"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "top_site", + selected_result_subtype: "", + selected_position: 1, + provider: "UrlbarProviderTopSites", + results: "top_site,action", + }, + ]); + }); +}); + +add_task(async function selected_result_calc() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.suggest.calculator", true]], + }); + + await doTest(async browser => { + await openPopup("8*8"); + await selectRowByProvider("calculator"); + await SimpleTest.promiseClipboardChange("64", () => { + EventUtils.synthesizeKey("KEY_Enter"); + }); + + assertEngagementTelemetry([ + { + selected_result: "calc", + selected_result_subtype: "", + selected_position: 2, + provider: "calculator", + results: "search_engine,calc", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_clipboard() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.clipboard.featureGate", true], + ["browser.urlbar.suggest.clipboard", true], + ], + }); + SpecialPowers.clipboardCopyString( + "https://example.com/selected_result_clipboard" + ); + + await doTest(async browser => { + await openPopup(""); + await selectRowByProvider("UrlbarProviderClipboard"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "clipboard", + selected_result_subtype: "", + selected_position: 1, + provider: "UrlbarProviderClipboard", + results: "clipboard,action", + }, + ]); + }); + + SpecialPowers.clipboardCopyString(""); + UrlbarProviderClipboard.setPreviousClipboardValue(""); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_unit() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.unitConversion.enabled", true]], + }); + + await doTest(async browser => { + await openPopup("1m to cm"); + await selectRowByProvider("UnitConversion"); + await SimpleTest.promiseClipboardChange("100 cm", () => { + EventUtils.synthesizeKey("KEY_Enter"); + }); + + assertEngagementTelemetry([ + { + selected_result: "unit", + selected_result_subtype: "", + selected_position: 2, + provider: "UnitConversion", + results: "search_engine,unit", + }, + ]); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_site_specific_contextual_search() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.contextualSearch.enabled", true]], + }); + + await doTest(async browser => { + const extension = await SearchTestUtils.installSearchExtension( + { + name: "Contextual", + search_url: "https://example.com/browser", + }, + { skipUnload: true } + ); + const onLoaded = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + "https://example.com/" + ); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "https://example.com/" + ); + await onLoaded; + + await openPopup("search"); + await selectRowByProvider("UrlbarProviderContextualSearch"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "site_specific_contextual_search", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderContextualSearch", + results: "search_engine,site_specific_contextual_search", + }, + ]); + + await extension.unload(); + }); + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_rs_adm_sponsored() { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + prefs: [["quicksuggest.rustEnabled", false]], + }); + + await doTest(async browser => { + await openPopup("sponsored"); + await selectRowByURL("https://example.com/sponsored"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "rs_adm_sponsored", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,rs_adm_sponsored", + }, + ]); + }); + + await cleanupQuickSuggest(); +}); + +add_task(async function selected_result_rs_adm_nonsponsored() { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + prefs: [["quicksuggest.rustEnabled", false]], + }); + + await doTest(async browser => { + await openPopup("nonsponsored"); + await selectRowByURL("https://example.com/nonsponsored"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "rs_adm_nonsponsored", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,rs_adm_nonsponsored", + }, + ]); + }); + + await cleanupQuickSuggest(); +}); + +add_task(async function selected_result_input_field() { + const expected = [ + { + selected_result: "input_field", + selected_result_subtype: "", + selected_position: 0, + provider: null, + results: "", + }, + ]; + + await doTest(async browser => { + await doDropAndGo("example.com"); + + assertEngagementTelemetry(expected); + }); + + await doTest(async browser => { + await doPasteAndGo("example.com"); + + assertEngagementTelemetry(expected); + }); +}); + +add_task(async function selected_result_weather() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.quickactions.enabled", false]], + }); + + const cleanupQuickSuggest = await ensureQuickSuggestInit(); + await MerinoTestUtils.initWeather(); + + let provider = UrlbarPrefs.get("quickSuggestRustEnabled") + ? "UrlbarProviderQuickSuggest" + : "Weather"; + await doTest(async browser => { + await openPopup(MerinoTestUtils.WEATHER_KEYWORD); + await selectRowByProvider(provider); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "weather", + selected_result_subtype: "", + selected_position: 2, + provider, + results: "search_engine,weather", + }, + ]); + }); + + await cleanupQuickSuggest(); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_navigational() { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + merinoSuggestions: [ + { + title: "Navigational suggestion", + url: "https://example.com/navigational-suggestion", + provider: "top_picks", + is_sponsored: false, + score: 0.25, + block_id: 0, + is_top_pick: true, + }, + ], + }); + + await doTest(async browser => { + await openPopup("only match the Merino suggestion"); + await selectRowByProvider("UrlbarProviderQuickSuggest"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "merino_top_picks", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,merino_top_picks", + }, + ]); + }); + + await cleanupQuickSuggest(); +}); + +add_task(async function selected_result_dynamic_wikipedia() { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + merinoSuggestions: [ + { + block_id: 1, + url: "https://example.com/dynamic-wikipedia", + title: "Dynamic Wikipedia suggestion", + click_url: "https://example.com/click", + impression_url: "https://example.com/impression", + advertiser: "dynamic-wikipedia", + provider: "wikipedia", + iab_category: "5 - Education", + }, + ], + }); + + await doTest(async browser => { + await openPopup("only match the Merino suggestion"); + await selectRowByProvider("UrlbarProviderQuickSuggest"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "merino_wikipedia", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,merino_wikipedia", + }, + ]); + }); + + await cleanupQuickSuggest(); +}); + +add_task(async function selected_result_search_shortcut_button() { + await doTest(async browser => { + const oneOffSearchButtons = UrlbarTestUtils.getOneOffSearchButtons(window); + await openPopup("x"); + Assert.ok(!oneOffSearchButtons.selectedButton); + + // Select oneoff button added for test in setup(). + for (;;) { + EventUtils.synthesizeKey("KEY_ArrowDown"); + if (!oneOffSearchButtons.selectedButton) { + continue; + } + + if ( + oneOffSearchButtons.selectedButton.engine.name.includes( + "searchSuggestionEngine.xml" + ) + ) { + break; + } + } + + // Search immediately. + await doEnter({ shiftKey: true }); + + assertEngagementTelemetry([ + { + selected_result: "search_shortcut_button", + selected_result_subtype: "", + selected_position: 0, + provider: null, + results: "search_engine", + }, + ]); + }); +}); + +add_task(async function selected_result_trending() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.trending.featureGate", true], + ["browser.urlbar.trending.requireSearchMode", false], + ["browser.urlbar.trending.maxResultsNoSearchMode", 1], + ["browser.urlbar.weather.featureGate", false], + ], + }); + + let defaultEngine = await Services.search.getDefault(); + let extension = await SearchTestUtils.installSearchExtension( + { + name: "mozengine", + search_url: "https://example.org/", + }, + { setAsDefault: true, skipUnload: true } + ); + + SearchTestUtils.useMockIdleService(); + await SearchTestUtils.updateRemoteSettingsConfig([ + { + webExtension: { id: "mozengine@tests.mozilla.org" }, + urls: { + trending: { + fullPath: + "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + query: "", + }, + }, + appliesTo: [{ included: { everywhere: true } }], + default: "yes", + }, + ]); + + await doTest(async browser => { + await openPopup(""); + await selectRowByProvider("SearchSuggestions"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "trending_search", + selected_result_subtype: "", + selected_position: 1, + provider: "SearchSuggestions", + results: "trending_search", + }, + ]); + }); + + await extension.unload(); + await Services.search.setDefault( + defaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + let settingsWritten = SearchTestUtils.promiseSearchNotification( + "write-settings-to-disk-complete" + ); + await SearchTestUtils.updateRemoteSettingsConfig(); + await settingsWritten; + await PlacesUtils.history.clear(); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_trending_rich() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.richSuggestions.featureGate", true], + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.trending.featureGate", true], + ["browser.urlbar.trending.requireSearchMode", false], + ["browser.urlbar.trending.maxResultsNoSearchMode", 1], + ["browser.urlbar.weather.featureGate", false], + ], + }); + + let defaultEngine = await Services.search.getDefault(); + let extension = await SearchTestUtils.installSearchExtension( + { + name: "mozengine", + search_url: "https://example.org/", + }, + { setAsDefault: true, skipUnload: true } + ); + + SearchTestUtils.useMockIdleService(); + await SearchTestUtils.updateRemoteSettingsConfig([ + { + webExtension: { id: "mozengine@tests.mozilla.org" }, + urls: { + trending: { + fullPath: + "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs?richsuggestions=true", + query: "", + }, + }, + appliesTo: [{ included: { everywhere: true } }], + default: "yes", + }, + ]); + + await doTest(async browser => { + await openPopup(""); + await selectRowByProvider("SearchSuggestions"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "trending_search_rich", + selected_result_subtype: "", + selected_position: 1, + provider: "SearchSuggestions", + results: "trending_search_rich", + }, + ]); + }); + + await extension.unload(); + await Services.search.setDefault( + defaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + let settingsWritten = SearchTestUtils.promiseSearchNotification( + "write-settings-to-disk-complete" + ); + await SearchTestUtils.updateRemoteSettingsConfig(); + await settingsWritten; + await PlacesUtils.history.clear(); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_addons() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.addons.featureGate", true], + ["browser.urlbar.suggest.searches", false], + ], + }); + + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + merinoSuggestions: [ + { + provider: "amo", + icon: "https://example.com/good-addon.svg", + url: "https://example.com/good-addon", + title: "Good Addon", + description: "This is a good addon", + custom_details: { + amo: { + rating: "4.8", + number_of_ratings: "1234567", + guid: "good@addon", + }, + }, + is_top_pick: true, + }, + ], + }); + + await doTest(async browser => { + await openPopup("only match the Merino suggestion"); + await selectRowByProvider("UrlbarProviderQuickSuggest"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "merino_amo", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,merino_amo", + }, + ]); + }); + + await cleanupQuickSuggest(); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function selected_result_rust_adm_sponsored() { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + prefs: [["quicksuggest.rustEnabled", true]], + }); + + await doTest(async browser => { + await openPopup("sponsored"); + await selectRowByURL("https://example.com/sponsored"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "rust_adm_sponsored", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,rust_adm_sponsored", + }, + ]); + }); + + await cleanupQuickSuggest(); +}); + +add_task(async function selected_result_rust_adm_nonsponsored() { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + prefs: [["quicksuggest.rustEnabled", true]], + }); + + await doTest(async browser => { + await openPopup("nonsponsored"); + await selectRowByURL("https://example.com/nonsponsored"); + await doEnter(); + + assertEngagementTelemetry([ + { + selected_result: "rust_adm_nonsponsored", + selected_result_subtype: "", + selected_position: 2, + provider: "UrlbarProviderQuickSuggest", + results: "search_engine,rust_adm_nonsponsored", + }, + ]); + }); + + await cleanupQuickSuggest(); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js new file mode 100644 index 0000000000..2b38631747 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js @@ -0,0 +1,173 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for engagement telemetry for tips using Glean. + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser-tips/head.js", + this +); + +add_setup(async function () { + makeProfileResettable(); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.quickactions.enabled", false]], + }); + + registerCleanupFunction(async function () { + await SpecialPowers.popPrefEnv(); + }); +}); + +add_task(async function selected_result_tip() { + const testData = [ + { + type: "searchTip_onboard", + expected: "tip_onboard", + }, + { + type: "searchTip_persist", + expected: "tip_persist", + }, + { + type: "searchTip_redirect", + expected: "tip_redirect", + }, + { + type: "test", + expected: "tip_unknown", + }, + ]; + + for (const { type, expected } of testData) { + const deferred = Promise.withResolvers(); + const provider = new UrlbarTestUtils.TestProvider({ + results: [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.TIP, + UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + { + type, + helpUrl: "https://example.com/", + titleL10n: { id: "urlbar-search-tips-confirm" }, + buttons: [ + { + url: "https://example.com/", + l10n: { id: "urlbar-search-tips-confirm" }, + }, + ], + } + ), + ], + priority: 1, + onEngagement: () => { + deferred.resolve(); + }, + }); + UrlbarProvidersManager.registerProvider(provider); + + await doTest(async browser => { + await openPopup("example"); + await selectRowByType(type); + EventUtils.synthesizeKey("VK_RETURN"); + await deferred.promise; + + assertEngagementTelemetry([ + { + selected_result: expected, + results: expected, + }, + ]); + }); + + UrlbarProvidersManager.unregisterProvider(provider); + } +}); + +add_task(async function selected_result_intervention_clear() { + let useOldClearHistoryDialog = Services.prefs.getBoolPref( + "privacy.sanitize.useOldClearHistoryDialog" + ); + let dialogURL = useOldClearHistoryDialog + ? "chrome://browser/content/sanitize.xhtml" + : "chrome://browser/content/sanitize_v2.xhtml"; + await doInterventionTest( + SEARCH_STRINGS.CLEAR, + "intervention_clear", + dialogURL, + [ + { + selected_result: "intervention_clear", + results: "search_engine,intervention_clear", + }, + ] + ); +}); + +add_task(async function selected_result_intervention_refresh() { + await doInterventionTest( + SEARCH_STRINGS.REFRESH, + "intervention_refresh", + "chrome://global/content/resetProfile.xhtml", + [ + { + selected_result: "intervention_refresh", + results: "search_engine,intervention_refresh", + }, + ] + ); +}); + +add_task(async function selected_result_intervention_update() { + // Updates are disabled for MSIX packages, this test is irrelevant for them. + if ( + AppConstants.platform === "win" && + Services.sysinfo.getProperty("hasWinPackageId") + ) { + return; + } + await UpdateUtils.setAppUpdateAutoEnabled(false); + await initUpdate({ queryString: "&noUpdates=1" }); + UrlbarProviderInterventions.checkForBrowserUpdate(true); + await processUpdateSteps([ + { + panelId: "checkingForUpdates", + checkActiveUpdate: null, + continueFile: CONTINUE_CHECK, + }, + { + panelId: "noUpdatesFound", + checkActiveUpdate: null, + continueFile: null, + }, + ]); + + await doInterventionTest( + SEARCH_STRINGS.UPDATE, + "intervention_update_refresh", + "chrome://global/content/resetProfile.xhtml", + [ + { + selected_result: "intervention_update", + results: "search_engine,intervention_update", + }, + ] + ); +}); + +async function doInterventionTest(keyword, type, dialog, expectedTelemetry) { + await doTest(async browser => { + await openPopup(keyword); + await selectRowByType(type); + const onDialog = BrowserTestUtils.promiseAlertDialog("cancel", dialog, { + isSubDialog: true, + }); + EventUtils.synthesizeKey("VK_RETURN"); + await onDialog; + + assertEngagementTelemetry(expectedTelemetry); + }); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js new file mode 100644 index 0000000000..5972dd331d --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of engagement telemetry. +// - engagement_type + +// This test has many subtests and can time out in verify mode. +requestLongerTimeout(5); + +add_setup(async function () { + await setup(); +}); + +add_task(async function engagement_type_click() { + await doTest(async browser => { + await openPopup("x"); + await doClick(); + + assertEngagementTelemetry([{ engagement_type: "click" }]); + }); +}); + +add_task(async function engagement_type_enter() { + await doTest(async browser => { + await openPopup("x"); + await doEnter(); + + assertEngagementTelemetry([{ engagement_type: "enter" }]); + }); +}); + +add_task(async function engagement_type_go_button() { + await doTest(async browser => { + await openPopup("x"); + EventUtils.synthesizeMouseAtCenter(gURLBar.goButton, {}); + + assertEngagementTelemetry([{ engagement_type: "go_button" }]); + }); +}); + +add_task(async function engagement_type_drop_go() { + await doTest(async browser => { + await doDropAndGo("example.com"); + + assertEngagementTelemetry([{ engagement_type: "drop_go" }]); + }); +}); + +add_task(async function engagement_type_paste_go() { + await doTest(async browser => { + await doPasteAndGo("www.example.com"); + + assertEngagementTelemetry([{ engagement_type: "paste_go" }]); + }); +}); + +add_task(async function engagement_type_dismiss() { + const cleanupQuickSuggest = await ensureQuickSuggestInit(); + + await doTest(async browser => { + await openPopup("sponsored"); + + const originalResultCount = UrlbarTestUtils.getResultCount(window); + await selectRowByURL("https://example.com/sponsored"); + UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "D"); + await BrowserTestUtils.waitForCondition( + () => originalResultCount != UrlbarTestUtils.getResultCount(window) + ); + + assertEngagementTelemetry([{ engagement_type: "dismiss" }]); + + // The view should stay open after dismissing the result. Now pick the + // heuristic result. Another "click" engagement event should be recorded. + Assert.ok( + gURLBar.view.isOpen, + "View should remain open after dismissing result" + ); + await doClick(); + assertEngagementTelemetry([ + { engagement_type: "dismiss" }, + { engagement_type: "click", interaction: "typed" }, + ]); + }); + + await doTest(async browser => { + await openPopup("sponsored"); + + const originalResultCount = UrlbarTestUtils.getResultCount(window); + await selectRowByURL("https://example.com/sponsored"); + EventUtils.synthesizeKey("KEY_Delete", { shiftKey: true }); + await BrowserTestUtils.waitForCondition( + () => originalResultCount != UrlbarTestUtils.getResultCount(window) + ); + + assertEngagementTelemetry([{ engagement_type: "dismiss" }]); + }); + + await cleanupQuickSuggest(); +}); + +add_task(async function engagement_type_help() { + const cleanupQuickSuggest = await ensureQuickSuggestInit(); + + await doTest(async browser => { + await openPopup("sponsored"); + await selectRowByURL("https://example.com/sponsored"); + const onTabOpened = BrowserTestUtils.waitForNewTab(gBrowser); + UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "L"); + const tab = await onTabOpened; + BrowserTestUtils.removeTab(tab); + + assertEngagementTelemetry([{ engagement_type: "help" }]); + }); + + await cleanupQuickSuggest(); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js new file mode 100644 index 0000000000..07e8b9b360 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js @@ -0,0 +1,136 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const SPONSORED_QUERY = "sponsored"; +const NONSPONSORED_QUERY = "nonsponsored"; + +// test for exposure events +add_setup(async function () { + await initExposureTest(); +}); + +add_task(async function exposureSponsoredOnEngagement() { + await doExposureTest({ + prefs: [ + ["browser.urlbar.exposureResults", suggestResultType("adm_sponsored")], + ["browser.urlbar.showExposureResults", true], + ], + query: SPONSORED_QUERY, + trigger: () => doClick(), + assert: () => + assertExposureTelemetry([ + { results: suggestResultType("adm_sponsored") }, + ]), + }); +}); + +add_task(async function exposureSponsoredOnAbandonment() { + await doExposureTest({ + prefs: [ + ["browser.urlbar.exposureResults", suggestResultType("adm_sponsored")], + ["browser.urlbar.showExposureResults", true], + ], + query: SPONSORED_QUERY, + trigger: () => doBlur(), + assert: () => + assertExposureTelemetry([ + { results: suggestResultType("adm_sponsored") }, + ]), + }); +}); + +add_task(async function exposureFilter() { + await doExposureTest({ + prefs: [ + ["browser.urlbar.exposureResults", suggestResultType("adm_sponsored")], + ["browser.urlbar.showExposureResults", false], + ], + query: SPONSORED_QUERY, + select: async () => { + // assert that the urlbar has no results + Assert.equal( + await getResultByType(suggestResultType("adm_sponsored")), + null + ); + }, + trigger: () => doBlur(), + assert: () => + assertExposureTelemetry([ + { results: suggestResultType("adm_sponsored") }, + ]), + }); +}); + +add_task(async function innerQueryExposure() { + await doExposureTest({ + prefs: [ + ["browser.urlbar.exposureResults", suggestResultType("adm_sponsored")], + ["browser.urlbar.showExposureResults", true], + ], + query: NONSPONSORED_QUERY, + select: () => {}, + trigger: async () => { + // delete the old query + gURLBar.select(); + EventUtils.synthesizeKey("KEY_Backspace"); + await openPopup(SPONSORED_QUERY); + await defaultSelect(SPONSORED_QUERY); + await doClick(); + }, + assert: () => + assertExposureTelemetry([ + { results: suggestResultType("adm_sponsored") }, + ]), + }); +}); + +add_task(async function innerQueryInvertedExposure() { + await doExposureTest({ + prefs: [ + ["browser.urlbar.exposureResults", suggestResultType("adm_sponsored")], + ["browser.urlbar.showExposureResults", true], + ], + query: SPONSORED_QUERY, + select: () => {}, + trigger: async () => { + // delete the old query + gURLBar.select(); + EventUtils.synthesizeKey("KEY_Backspace"); + await openPopup(NONSPONSORED_QUERY); + await defaultSelect(SPONSORED_QUERY); + await doClick(); + }, + assert: () => + assertExposureTelemetry([ + { results: suggestResultType("adm_sponsored") }, + ]), + }); +}); + +add_task(async function multipleProviders() { + await doExposureTest({ + prefs: [ + [ + "browser.urlbar.exposureResults", + [ + suggestResultType("adm_sponsored"), + suggestResultType("adm_nonsponsored"), + ].join(","), + ], + ["browser.urlbar.showExposureResults", true], + ], + query: NONSPONSORED_QUERY, + trigger: () => doClick(), + assert: () => + assertExposureTelemetry([ + { results: suggestResultType("adm_nonsponsored") }, + ]), + }); +}); + +function suggestResultType(typeWithoutSource) { + let source = UrlbarPrefs.get("quickSuggestRustEnabled") ? "rust" : "rs"; + return `${source}_${typeWithoutSource}`; +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js new file mode 100644 index 0000000000..d28352b417 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js @@ -0,0 +1,539 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests edge cases related to the exposure event and view updates. + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs", + UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs", + UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs", + UrlbarView: "resource:///modules/UrlbarView.sys.mjs", +}); + +const MAX_RESULT_COUNT = 10; + +let gProvider; + +add_setup(async function () { + await initExposureTest(); + + await SpecialPowers.pushPrefEnv({ + set: [ + // Make absolutely sure the panel stays open during the test. There are + // spurious blurs on WebRender TV tests as the test starts that cause the + // panel to close and the query to be canceled, resulting in intermittent + // failures without this. + ["ui.popup.disable_autohide", true], + + // Set maxRichResults for sanity. + ["browser.urlbar.maxRichResults", MAX_RESULT_COUNT], + ], + }); + + await Services.fog.testFlushAllChildren(); + Services.fog.testResetFOG(); + + gProvider = new TestProvider(); + UrlbarProvidersManager.registerProvider(gProvider); + + // Increase the timeout of the stale-rows timer so it doesn't interfere with + // this test, which specifically tests what happens before the timer fires. + let originalRemoveStaleRowsTimeout = UrlbarView.removeStaleRowsTimeout; + UrlbarView.removeStaleRowsTimeout = 30000; + + registerCleanupFunction(() => { + UrlbarView.removeStaleRowsTimeout = originalRemoveStaleRowsTimeout; + UrlbarProvidersManager.unregisterProvider(gProvider); + }); +}); + +// Does one query that fills up the view with search suggestions, starts a +// second query that returns a history result, and cancels it before it can +// finish but after the view is updated. Regardless of `showExposureResults`, +// the history result should not trigger an exposure since it never had a chance +// to be visible in the view. +add_task(async function noExposure() { + for (let showExposureResults of [true, false]) { + await do_noExposure(showExposureResults); + } +}); + +async function do_noExposure(showExposureResults) { + info("Starting do_noExposure: " + JSON.stringify({ showExposureResults })); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.exposureResults", "history"], + ["browser.urlbar.showExposureResults", showExposureResults], + ], + }); + + // Make the provider return enough search suggestions to fill the view. + gProvider.results = []; + for (let i = 0; i < MAX_RESULT_COUNT; i++) { + gProvider.results.push( + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + suggestion: "suggestion " + i, + engine: Services.search.defaultEngine.name, + } + ) + ); + } + + // Do the first query to fill the view with search suggestions. + info("Doing first query"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 1", + }); + + // Now make the provider return a history result and bookmark. If + // `showExposureResults` is true, the history result will be added to the view + // but it should be hidden since the view is already full. If it's false, it + // shouldn't be added at all. The bookmark will always be added, which will + // tell us when the view has been updated either way. (It also will be hidden + // since the view is already full.) + let historyUrl = "https://example.com/history"; + let bookmarkUrl = "https://example.com/bookmark"; + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.HISTORY, + { url: historyUrl } + ), + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + { url: bookmarkUrl } + ), + ]; + + // When the provider's `startQuery()` is called, let it add its results but + // don't let it return. That will cause the view to be updated with the new + // results but prevent it from showing hidden rows since the query won't + // finish. + let queryResolver = Promise.withResolvers(); + gProvider.finishQueryPromise = queryResolver.promise; + + // Observe when the view appends the bookmark row. This will tell us when the + // view has been updated with the provider's new results. The bookmark row + // will be hidden since the view is already full with search suggestions. + let lastRowPromise = promiseLastRowAppended( + row => row.result.payload.url == bookmarkUrl + ); + + // Now start the second query but don't await it. + info("Starting second query"); + let queryPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 2", + reopenOnBlur: false, + }); + + // Wait for the view to be updated. + info("Waiting for last row"); + let lastRow = await lastRowPromise; + info("Done waiting for last row"); + + Assert.ok( + BrowserTestUtils.isHidden(lastRow), + "The new bookmark row should be hidden since the view is full" + ); + + // Make sure the view is full of visible rows as expected, plus the one or two + // hidden rows for the history and/or bookmark results. + let rows = UrlbarTestUtils.getResultsContainer(window); + let expectedCount = MAX_RESULT_COUNT + 1; + if (showExposureResults) { + expectedCount++; + } + Assert.equal( + rows.children.length, + expectedCount, + "The view has the expected number of rows" + ); + + // Check the visible rows. + for (let i = 0; i < MAX_RESULT_COUNT; i++) { + let row = rows.children[i]; + Assert.ok(BrowserTestUtils.isVisible(row), `rows[${i}] should be visible`); + Assert.ok( + row.result.type == UrlbarUtils.RESULT_TYPE.SEARCH, + `rows[${i}].result.type should be SEARCH` + ); + // The heuristic won't have a suggestion so skip it. + if (i > 0) { + Assert.ok( + row.result.payload.suggestion, + `rows[${i}] should have a suggestion` + ); + } + } + + // Check the hidden history and/or bookmark rows. + let expected = [ + { source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS, url: bookmarkUrl }, + ]; + if (showExposureResults) { + expected.unshift({ + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + url: historyUrl, + }); + } + for (let i = 0; i < expected.length; i++) { + let { source, url } = expected[i]; + let row = rows.children[MAX_RESULT_COUNT + i]; + Assert.ok(row, `rows[${i}] should exist`); + Assert.ok(BrowserTestUtils.isHidden(row), `rows[${i}] should be hidden`); + Assert.equal( + row.result.type, + UrlbarUtils.RESULT_TYPE.URL, + `rows[${i}].result.type should be URL` + ); + Assert.equal( + row.result.source, + source, + `rows[${i}].result.source should be as expected` + ); + Assert.equal( + row.result.payload.url, + url, + `rows[${i}] URL should be as expected` + ); + } + + // Close the view. Blur the urlbar to end the session. + info("Closing view and blurring"); + await UrlbarTestUtils.promisePopupClose(window); + gURLBar.blur(); + + // No exposure should have been recorded since the history result was never + // visible. + assertExposureTelemetry([]); + + // Clean up. + queryResolver.resolve(); + await queryPromise; + await SpecialPowers.popPrefEnv(); + Services.fog.testResetFOG(); +} + +// Does one query that underfills the view and then a second query that returns +// a search suggestion. The search suggestion should be appended and trigger an +// exposure. When `showExposureResults` is true, it should also be shown. After +// the view is updated, it shouldn't matter whether or not the second query is +// canceled. +add_task(async function exposure_append() { + for (let showExposureResults of [true, false]) { + for (let cancelSecondQuery of [true, false]) { + await do_exposure_append({ + showExposureResults, + cancelSecondQuery, + }); + } + } +}); + +async function do_exposure_append({ showExposureResults, cancelSecondQuery }) { + info( + "Starting do_exposure_append: " + + JSON.stringify({ showExposureResults, cancelSecondQuery }) + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.exposureResults", "search_suggest"], + ["browser.urlbar.showExposureResults", showExposureResults], + ], + }); + + // Make the provider return no results at first. + gProvider.results = []; + + // Do the first query to open the view. + info("Doing first query"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 1", + }); + + // Now make the provider return a search suggestion and a bookmark. If + // `showExposureResults` is true, the suggestion should be added to the view + // and be visible immediately. If it's false, it shouldn't be added at + // all. The bookmark will always be added, which will tell us when the view + // has been updated either way. + let newSuggestion = "new suggestion"; + let bookmarkUrl = "https://example.com/bookmark"; + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + suggestion: newSuggestion, + engine: Services.search.defaultEngine.name, + } + ), + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + { url: bookmarkUrl } + ), + ]; + + // When the provider's `startQuery()` is called, let it add its results but + // don't let it return. That will cause the view to be updated with the new + // results but let us test the specific case where the query doesn't finish. + let queryResolver = Promise.withResolvers(); + gProvider.finishQueryPromise = queryResolver.promise; + + // Observe when the view appends the bookmark row. This will tell us when the + // view has been updated with the provider's new results. + let lastRowPromise = promiseLastRowAppended( + row => row.result.payload.url == bookmarkUrl + ); + + // Now start the second query but don't await it. + info("Starting second query"); + let queryPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 2", + reopenOnBlur: false, + }); + + // Wait for the view to be updated. + info("Waiting for last row"); + let lastRow = await lastRowPromise; + info("Done waiting for last row"); + + Assert.ok( + BrowserTestUtils.isVisible(lastRow), + "The new bookmark row should be visible since the view is not full" + ); + + // Check the new suggestion row. + let rows = UrlbarTestUtils.getResultsContainer(window); + let newSuggestionRow = [...rows.children].find( + r => r.result.payload.suggestion == newSuggestion + ); + if (showExposureResults) { + Assert.ok( + newSuggestionRow, + "The new suggestion row should have been added" + ); + Assert.ok( + BrowserTestUtils.isVisible(newSuggestionRow), + "The new suggestion row should be visible" + ); + } else { + Assert.ok( + !newSuggestionRow, + "The new suggestion row should not have been added" + ); + } + + if (!cancelSecondQuery) { + // Finish the query. + queryResolver.resolve(); + await queryPromise; + } + + // Close the view. Blur the urlbar to end the session. + info("Closing view and blurring"); + await UrlbarTestUtils.promisePopupClose(window); + gURLBar.blur(); + + // If `showExposureResults` is true, the new search suggestion should have + // been shown; if it's false, it would have been shown. Either way, it should + // have triggered an exposure. + assertExposureTelemetry([{ results: "search_suggest" }]); + + // Clean up. + queryResolver.resolve(); + await queryPromise; + await SpecialPowers.popPrefEnv(); + Services.fog.testResetFOG(); +} + +// Does one query that returns a search suggestion and then a second query that +// returns a new search suggestion. The new search suggestion can replace the +// old one, so it should trigger an exposure. When `showExposureResults` is +// true, it should actually replace it. After the view is updated, it shouldn't +// matter whether or not the second query is canceled. +add_task(async function exposure_replace() { + for (let showExposureResults of [true, false]) { + for (let cancelSecondQuery of [true, false]) { + await do_exposure_replace({ showExposureResults, cancelSecondQuery }); + } + } +}); + +async function do_exposure_replace({ showExposureResults, cancelSecondQuery }) { + info( + "Starting do_exposure_replace: " + + JSON.stringify({ showExposureResults, cancelSecondQuery }) + ); + + // Make the provider return a search suggestion. + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + suggestion: "suggestion", + engine: Services.search.defaultEngine.name, + } + ), + ]; + + // Do the first query to show the suggestion. + info("Doing first query"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 1", + }); + + // Set exposure results to search suggestions and hide them. We can't do this + // before now because that would hide the search suggestions in the first + // query, and here we're specifically testing the case where a new row + // replaces an old row, which is only allowed for rows of the same type. + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.exposureResults", "search_suggest"], + ["browser.urlbar.showExposureResults", showExposureResults], + ], + }); + + // Now make the provider return another search suggestion and a bookmark. If + // `showExposureResults` is true, the new suggestion should replace the old + // one in the view and be visible immediately. If it's false, it shouldn't be + // added at all. The bookmark will always be added, which will tell us when + // the view has been updated either way. + let newSuggestion = "new suggestion"; + let bookmarkUrl = "https://example.com/bookmark"; + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + suggestion: newSuggestion, + engine: Services.search.defaultEngine.name, + } + ), + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + { url: bookmarkUrl } + ), + ]; + + // When the provider's `startQuery()` is called, let it add its results but + // don't let it return. That will cause the view to be updated with the new + // results but let us test the specific case where the query doesn't finish. + let queryResolver = Promise.withResolvers(); + gProvider.finishQueryPromise = queryResolver.promise; + + // Observe when the view appends the bookmark row. This will tell us when the + // view has been updated with the provider's new results. + let lastRowPromise = promiseLastRowAppended( + row => row.result.payload.url == bookmarkUrl + ); + + // Now start the second query but don't await it. + info("Starting second query"); + let queryPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 2", + reopenOnBlur: false, + }); + + // Wait for the view to be updated. + info("Waiting for last row"); + let lastRow = await lastRowPromise; + info("Done waiting for last row"); + + Assert.ok( + BrowserTestUtils.isVisible(lastRow), + "The new bookmark row should be visible since the view is not full" + ); + + // Check the new suggestion row. + let rows = UrlbarTestUtils.getResultsContainer(window); + let newSuggestionRow = [...rows.children].find( + r => r.result.payload.suggestion == newSuggestion + ); + if (showExposureResults) { + Assert.ok( + newSuggestionRow, + "The new suggestion row should have replaced the old one" + ); + Assert.ok( + BrowserTestUtils.isVisible(newSuggestionRow), + "The new suggestion row should be visible" + ); + } else { + Assert.ok( + !newSuggestionRow, + "The new suggestion row should not have been added" + ); + } + + if (!cancelSecondQuery) { + // Finish the query. + queryResolver.resolve(); + await queryPromise; + } + + // Close the view. Blur the urlbar to end the session. + info("Closing view and blurring"); + await UrlbarTestUtils.promisePopupClose(window); + gURLBar.blur(); + + // If `showExposureResults` is true, the new search suggestion should have + // been shown; if it's false, it would have been shown. Either way, it should + // have triggered an exposure. + assertExposureTelemetry([{ results: "search_suggest" }]); + + // Clean up. + queryResolver.resolve(); + await queryPromise; + await SpecialPowers.popPrefEnv(); + Services.fog.testResetFOG(); +} + +/** + * A test provider that doesn't finish startQuery() until `finishQueryPromise` + * is resolved. + */ +class TestProvider extends UrlbarTestUtils.TestProvider { + finishQueryPromise = null; + + async startQuery(context, addCallback) { + for (let result of this.results) { + addCallback(this, result); + } + await this.finishQueryPromise; + } +} + +function promiseLastRowAppended(predicate) { + return new Promise(resolve => { + let rows = UrlbarTestUtils.getResultsContainer(window); + let observer = new MutationObserver(mutations => { + let lastRow = rows.children[rows.children.length - 1]; + info( + "Observed mutation, lastRow.result is: " + + JSON.stringify(lastRow.result) + ); + if (predicate(lastRow)) { + observer.disconnect(); + resolve(lastRow); + } + }); + observer.observe(rows, { childList: true }); + }); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js new file mode 100644 index 0000000000..354876e512 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js @@ -0,0 +1,258 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of impression telemetry. +// - groups +// - results +// - n_results + +// This test has many subtests and can time out in verify mode. +requestLongerTimeout(5); + +add_setup(async function () { + await initGroupTest(); + // Increase the pausing time to ensure to ready for all suggestions. + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", + 500, + ], + ], + }); +}); + +add_task(async function heuristics() { + await doHeuristicsTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause", groups: "heuristic", results: "search_engine" }, + ]), + }); +}); + +add_task(async function adaptive_history() { + await doAdaptiveHistoryTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,adaptive_history", + results: "search_engine,history", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function search_history() { + await doSearchHistoryTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,search_history,search_history", + results: "search_engine,search_history,search_history", + n_results: 3, + }, + ]), + }); +}); + +add_task(async function recent_search() { + await doRecentSearchTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "recent_search,suggested_index", + results: "recent_search,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function search_suggest() { + await doSearchSuggestTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,search_suggest,search_suggest", + results: "search_engine,search_suggest,search_suggest", + n_results: 3, + }, + ]), + }); + + await doTailSearchSuggestTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,search_suggest", + results: "search_engine,search_suggest", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function top_pick() { + await doTopPickTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,top_pick,search_suggest,search_suggest", + results: + "search_engine,merino_top_picks,search_suggest,search_suggest", + n_results: 4, + }, + ]), + }); +}); + +add_task(async function top_site() { + await doTopSiteTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "top_site,suggested_index", + results: "top_site,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function clipboard() { + await doClipboardTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "general,suggested_index", + results: "clipboard,action", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function remote_tab() { + await doRemoteTabTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,remote_tab", + results: "search_engine,remote_tab", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function addon() { + await doAddonTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "addon", + results: "addon", + n_results: 1, + }, + ]), + }); +}); + +add_task(async function general() { + await doGeneralBookmarkTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,suggested_index,general", + results: "search_engine,action,bookmark", + n_results: 3, + }, + ]), + }); + + await doGeneralHistoryTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,general", + results: "search_engine,history", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function suggest() { + await doSuggestTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + groups: "heuristic,suggest", + results: UrlbarPrefs.get("quickSuggestRustEnabled") + ? "search_engine,rust_adm_nonsponsored" + : "search_engine,rs_adm_nonsponsored", + n_results: 2, + }, + ]), + }); +}); + +add_task(async function about_page() { + await doAboutPageTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,about_page,about_page", + results: "search_engine,history,history", + n_results: 3, + }, + ]), + }); +}); + +add_task(async function suggested_index() { + await doSuggestedIndexTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { + reason: "pause", + groups: "heuristic,suggested_index", + results: "search_engine,unit", + n_results: 2, + }, + ]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js new file mode 100644 index 0000000000..a16b55cac6 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of impression telemetry. +// - interaction + +add_setup(async function () { + await initInteractionTest(); +}); + +add_task(async function topsites() { + await doTopsitesTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", interaction: "topsites" }]), + }); +}); + +add_task(async function typed() { + await doTypedTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", interaction: "typed" }]), + }); + + await doTypedWithResultsPopupTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", interaction: "typed" }]), + }); +}); + +add_task(async function pasted() { + await doPastedTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", interaction: "pasted" }]), + }); + + await doPastedWithResultsPopupTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", interaction: "pasted" }]), + }); +}); + +add_task(async function topsite_search() { + await doTopsitesSearchTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause", interaction: "topsite_search" }, + ]), + }); +}); + +add_task(async function returned_restarted_refined() { + await doReturnedRestartedRefinedTest({ + trigger: () => waitForPauseImpression(), + assert: expected => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause", interaction: expected }, + ]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js new file mode 100644 index 0000000000..af7134b3a0 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test impression telemetry with persisted search terms disabled. + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.showSearchTerms.featureGate", false]], + }); +}); + +add_task(async function persisted_search_terms() { + await doPersistedSearchTermsTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause", interaction: "typed" }, + ]), + }); +}); + +add_task(async function persisted_search_terms_restarted_refined() { + await doPersistedSearchTermsRestartedRefinedTest({ + enabled: false, + trigger: () => waitForPauseImpression(), + assert: expected => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause", interaction: expected }, + ]), + }); +}); + +add_task( + async function persisted_search_terms_restarted_refined_via_abandonment() { + await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled: false, + trigger: () => waitForPauseImpression(), + assert: expected => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause" }, + { reason: "pause", interaction: expected }, + ]), + }); + } +); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js new file mode 100644 index 0000000000..a29ff98b78 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test impression telemetry with persisted search terms enabled. + +// Allow more time for Mac machines so they don't time out in verify mode. +if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); +} + +add_setup(async function () { + await initInteractionTest(); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.showSearchTerms.featureGate", true], + ["browser.urlbar.showSearchTerms.enabled", true], + ["browser.search.widget.inNavBar", false], + ], + }); +}); + +add_task(async function interaction_persisted_search_terms() { + await doPersistedSearchTermsTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause", interaction: "persisted_search_terms" }, + ]), + }); +}); + +add_task(async function interaction_persisted_search_terms_restarted_refined() { + await doPersistedSearchTermsRestartedRefinedTest({ + enabled: true, + trigger: () => waitForPauseImpression(), + assert: expected => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause", interaction: expected }, + ]), + }); +}); + +add_task( + async function interaction_persisted_search_terms_restarted_refined_via_abandonment() { + await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled: true, + trigger: () => waitForPauseImpression(), + assert: expected => + assertImpressionTelemetry([ + { reason: "pause" }, + { reason: "pause" }, + { reason: "pause", interaction: expected }, + ]), + }); + } +); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js new file mode 100644 index 0000000000..528cc318e0 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of impression telemetry. +// - n_chars +// - n_words + +add_setup(async function () { + await initNCharsAndNWordsTest(); +}); + +add_task(async function n_chars() { + await doNCharsTest({ + trigger: () => waitForPauseImpression(), + assert: nChars => + assertImpressionTelemetry([{ reason: "pause", n_chars: nChars }]), + }); + + await doNCharsWithOverMaxTextLengthCharsTest({ + trigger: () => waitForPauseImpression(), + assert: nChars => + assertImpressionTelemetry([{ reason: "pause", n_chars: nChars }]), + }); +}); + +add_task(async function n_words() { + await doNWordsTest({ + trigger: () => waitForPauseImpression(), + assert: nWords => + assertImpressionTelemetry([{ reason: "pause", n_words: nWords }]), + }); + + await doNWordsWithOverMaxTextLengthCharsTest({ + trigger: () => waitForPauseImpression(), + assert: nWords => + assertImpressionTelemetry([{ reason: "pause", n_words: nWords }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js new file mode 100644 index 0000000000..344e238e24 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the impression telemetry behavior with its preferences. + +add_setup(async function () { + await setup(); +}); + +add_task(async function pauseImpressionIntervalMs() { + const additionalInterval = 1000; + const originalInterval = UrlbarPrefs.get( + "searchEngagementTelemetry.pauseImpressionIntervalMs" + ); + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", + originalInterval + additionalInterval, + ], + ], + }); + + await doTest(async browser => { + await openPopup("https://example.com"); + + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, originalInterval)); + await Services.fog.testFlushAllChildren(); + assertImpressionTelemetry([]); + + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, additionalInterval)); + await Services.fog.testFlushAllChildren(); + assertImpressionTelemetry([{ sap: "urlbar_newtab" }]); + }); + + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js new file mode 100644 index 0000000000..482b906024 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of impression telemetry. +// - sap + +add_setup(async function () { + await initSapTest(); +}); + +add_task(async function urlbar() { + await doUrlbarTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause", sap: "urlbar_newtab" }, + { reason: "pause", sap: "urlbar" }, + ]), + }); +}); + +add_task(async function handoff() { + await doHandoffTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", sap: "handoff" }]), + }); +}); + +add_task(async function urlbar_addonpage() { + await doUrlbarAddonpageTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", sap: "urlbar_addonpage" }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js new file mode 100644 index 0000000000..c5bd983d7f --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of impression telemetry. +// - search_engine_default_id + +add_setup(async function () { + await initSearchEngineDefaultIdTest(); + // Increase the pausing time to ensure to ready for all suggestions. + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", + 500, + ], + ], + }); +}); + +add_task(async function basic() { + await doSearchEngineDefaultIdTest({ + trigger: () => waitForPauseImpression(), + assert: engineId => + assertImpressionTelemetry([{ search_engine_default_id: engineId }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js new file mode 100644 index 0000000000..727afa3cef --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of impression telemetry. +// - search_mode + +add_setup(async function () { + await initSearchModeTest(); + // Increase the pausing time to ensure entering search mode. + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", + 1000, + ], + ], + }); +}); + +add_task(async function not_search_mode() { + await doNotSearchModeTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", search_mode: "" }]), + }); +}); + +add_task(async function search_engine() { + await doSearchEngineTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause", search_mode: "search_engine" }, + ]), + }); +}); + +add_task(async function bookmarks() { + await doBookmarksTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([ + { reason: "pause", search_mode: "bookmarks" }, + ]), + }); +}); + +add_task(async function history() { + await doHistoryTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", search_mode: "history" }]), + }); +}); + +add_task(async function tabs() { + await doTabTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", search_mode: "tabs" }]), + }); +}); + +add_task(async function actions() { + await doActionsTest({ + trigger: () => waitForPauseImpression(), + assert: () => + assertImpressionTelemetry([{ reason: "pause", search_mode: "actions" }]), + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js new file mode 100644 index 0000000000..31f64996f3 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the taking timing for the impression telemetry. + +add_setup(async function () { + await setup(); +}); + +add_task(async function cancelImpressionTimerByEngagementEvent() { + const additionalInterval = 1000; + const originalInterval = UrlbarPrefs.get( + "searchEngagementTelemetry.pauseImpressionIntervalMs" + ); + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", + originalInterval + additionalInterval, + ], + ], + }); + + for (const trigger of [doEnter, doBlur]) { + await doTest(async browser => { + await openPopup("https://example.com"); + await trigger(); + + // Check whether the impression timer was canceled. + await new Promise(r => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(r, originalInterval + additionalInterval) + ); + assertImpressionTelemetry([]); + }); + } + + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function cancelInpressionTimerByType() { + const originalInterval = UrlbarPrefs.get( + "searchEngagementTelemetry.pauseImpressionIntervalMs" + ); + + await doTest(async browser => { + await openPopup("x"); + await new Promise(r => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(r, originalInterval / 10) + ); + assertImpressionTelemetry([]); + + EventUtils.synthesizeKey(" "); + EventUtils.synthesizeKey("z"); + await UrlbarTestUtils.promiseSearchComplete(window); + assertImpressionTelemetry([]); + await waitForPauseImpression(); + + assertImpressionTelemetry([{ n_chars: 3 }]); + }); +}); + +add_task(async function oneImpressionInOneSession() { + await doTest(async browser => { + await openPopup("x"); + await waitForPauseImpression(); + + // Sanity check. + assertImpressionTelemetry([{ n_chars: 1 }]); + + // Add a keyword to start new query. + EventUtils.synthesizeKey(" "); + EventUtils.synthesizeKey("z"); + await UrlbarTestUtils.promiseSearchComplete(window); + await waitForPauseImpression(); + + // No more taking impression telemetry. + assertImpressionTelemetry([{ n_chars: 1 }]); + + // Finish the current session. + await doEnter(); + + // Should take pause impression since new session started. + await openPopup("x z y"); + await waitForPauseImpression(); + assertImpressionTelemetry([{ n_chars: 1 }, { n_chars: 5 }]); + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_record_preferences.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_record_preferences.js new file mode 100644 index 0000000000..88adc2fc11 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_record_preferences.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for preference telemetry. + +add_setup(async function () { + await Services.fog.testFlushAllChildren(); + Services.fog.testResetFOG(); + + // Create a new window in order to initialize TelemetryEvent of + // UrlbarController. + const win = await BrowserTestUtils.openNewBrowserWindow(); + registerCleanupFunction(async function () { + await BrowserTestUtils.closeWindow(win); + }); +}); + +add_task(async function prefMaxRichResults() { + Assert.equal( + Glean.urlbar.prefMaxResults.testGetValue(), + UrlbarPrefs.get("maxRichResults"), + "Record prefMaxResults when UrlbarController is initialized" + ); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.maxRichResults", 0]], + }); + Assert.equal( + Glean.urlbar.prefMaxResults.testGetValue(), + UrlbarPrefs.get("maxRichResults"), + "Record prefMaxResults when the maxRichResults pref is updated" + ); +}); + +add_task(async function boolPref() { + const testData = [ + { + green: "prefSuggestDataCollection", + pref: "quicksuggest.dataCollection.enabled", + }, + { + green: "prefSuggestNonsponsored", + pref: "suggest.quicksuggest.nonsponsored", + }, + { + green: "prefSuggestSponsored", + pref: "suggest.quicksuggest.sponsored", + }, + { + green: "prefSuggestTopsites", + pref: "suggest.topsites", + }, + ]; + + for (const { green, pref } of testData) { + Assert.equal( + Glean.urlbar[green].testGetValue(), + UrlbarPrefs.get(pref), + `Record ${green} when UrlbarController is initialized` + ); + + await SpecialPowers.pushPrefEnv({ + set: [[`browser.urlbar.${pref}`, !UrlbarPrefs.get(pref)]], + }); + + Assert.equal( + Glean.urlbar[green].testGetValue(), + UrlbarPrefs.get(pref), + `Record ${green} when the ${pref} pref is updated` + ); + } +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-exposure.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-exposure.js new file mode 100644 index 0000000000..f0723be701 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-exposure.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +async function doExposureTest({ + prefs, + query, + trigger, + assert, + select = defaultSelect, +}) { + const cleanupQuickSuggest = await ensureQuickSuggestInit(); + await SpecialPowers.pushPrefEnv({ + set: prefs, + }); + + await doTest(async () => { + await openPopup(query); + await select(query); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); + await cleanupQuickSuggest(); +} + +async function defaultSelect(query) { + await selectRowByURL(`https://example.com/${query}`); +} + +async function getResultByType(provider) { + for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) { + const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i); + const telemetryType = UrlbarUtils.searchEngagementTelemetryType( + detail.result + ); + if (telemetryType === provider) { + return detail.result; + } + } + return null; +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js new file mode 100644 index 0000000000..e86c664b46 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js @@ -0,0 +1,339 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +ChromeUtils.defineESModuleGetters(this, { + UrlbarProviderClipboard: + "resource:///modules/UrlbarProviderClipboard.sys.mjs", +}); + +async function doHeuristicsTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + + await trigger(); + await assert(); + }); +} + +async function doAdaptiveHistoryTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.autoFill", false]], + }); + + await doTest(async browser => { + await PlacesTestUtils.addVisits(["https://example.com/test"]); + await UrlbarUtils.addToInputHistory("https://example.com/test", "examp"); + + await openPopup("exa"); + await selectRowByURL("https://example.com/test"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doSearchHistoryTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.maxHistoricalSearchSuggestions", 2], + ], + }); + + await doTest(async browser => { + await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]); + + await openPopup("foo"); + await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doRecentSearchTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.recentsearches.featureGate", true]], + }); + + await doTest(async browser => { + await UrlbarTestUtils.formHistory.add([ + { value: "foofoo", source: Services.search.defaultEngine.name }, + ]); + + await openPopup(""); + await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doSearchSuggestTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.maxHistoricalSearchSuggestions", 2], + ], + }); + + await doTest(async browser => { + await openPopup("foo"); + await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doTailSearchSuggestTest({ trigger, assert }) { + const cleanup = await _useTailSuggestionsEngine(); + + await doTest(async browser => { + await openPopup("hello"); + await selectRowByProvider("SearchSuggestions"); + + await trigger(); + await assert(); + }); + + await cleanup(); +} + +async function doTopPickTest({ trigger, assert }) { + const cleanupQuickSuggest = await ensureQuickSuggestInit({ + merinoSuggestions: [ + { + title: "Navigational suggestion", + url: "https://example.com/navigational-suggestion", + provider: "top_picks", + is_sponsored: false, + score: 0.25, + block_id: 0, + is_top_pick: true, + }, + ], + }); + + await doTest(async browser => { + await openPopup("navigational"); + await selectRowByURL("https://example.com/navigational-suggestion"); + + await trigger(); + await assert(); + }); + + await cleanupQuickSuggest(); +} + +async function doTopSiteTest({ trigger, assert }) { + await doTest(async browser => { + await addTopSites("https://example.com/"); + + await showResultByArrowDown(); + await selectRowByURL("https://example.com/"); + + await trigger(); + await assert(); + }); +} + +async function doClipboardTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.clipboard.featureGate", true], + ["browser.urlbar.suggest.clipboard", true], + ], + }); + SpecialPowers.clipboardCopyString("https://example.com/clipboard"); + await doTest(async browser => { + await showResultByArrowDown(); + await selectRowByURL("https://example.com/clipboard"); + + await trigger(); + await assert(); + }); + SpecialPowers.clipboardCopyString(""); + UrlbarProviderClipboard.setPreviousClipboardValue(""); + await SpecialPowers.popPrefEnv(); +} + +async function doRemoteTabTest({ trigger, assert }) { + const remoteTab = await loadRemoteTab("https://example.com"); + + await doTest(async browser => { + await openPopup("example"); + await selectRowByProvider("RemoteTabs"); + + await trigger(); + await assert(); + }); + + await remoteTab.unload(); +} + +async function doAddonTest({ trigger, assert }) { + const addon = loadOmniboxAddon({ keyword: "omni" }); + await addon.startup(); + + await doTest(async browser => { + await openPopup("omni test"); + + await trigger(); + await assert(); + }); + + await addon.unload(); +} + +async function doGeneralBookmarkTest({ trigger, assert }) { + await doTest(async browser => { + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "https://example.com/bookmark", + title: "bookmark", + }); + + await openPopup("bookmark"); + await selectRowByURL("https://example.com/bookmark"); + + await trigger(); + await assert(); + }); +} + +async function doGeneralHistoryTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.autoFill", false]], + }); + + await doTest(async browser => { + await PlacesTestUtils.addVisits("https://example.com/test"); + + await openPopup("example"); + await selectRowByURL("https://example.com/test"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doSuggestTest({ trigger, assert }) { + const cleanupQuickSuggest = await ensureQuickSuggestInit(); + + await doTest(async browser => { + await openPopup("nonsponsored"); + await selectRowByURL("https://example.com/nonsponsored"); + + await trigger(); + await assert(); + }); + + await cleanupQuickSuggest(); +} + +async function doAboutPageTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.maxRichResults", 3]], + }); + + await doTest(async browser => { + await openPopup("about:"); + await selectRowByURL("about:robots"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doSuggestedIndexTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.unitConversion.enabled", true]], + }); + + await doTest(async browser => { + await openPopup("1m to cm"); + await selectRowByProvider("UnitConversion"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +/** + * Creates a search engine that returns tail suggestions and sets it as the + * default engine. + * + * @returns {Function} + * A cleanup function that will revert the default search engine and stop http + * server. + */ +async function _useTailSuggestionsEngine() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.search.suggest.enabled", true], + ["browser.urlbar.suggest.searches", true], + ["browser.urlbar.richSuggestions.tail", true], + ], + }); + + const engineName = "TailSuggestions"; + const httpServer = new HttpServer(); + httpServer.start(-1); + httpServer.registerPathHandler("/suggest", (req, resp) => { + const params = new URLSearchParams(req.queryString); + const searchStr = params.get("q"); + const suggestions = [ + searchStr, + [searchStr + "-tail"], + [], + { + "google:suggestdetail": [{ t: "-tail", mp: "… " }], + }, + ]; + resp.setHeader("Content-Type", "application/json", false); + resp.write(JSON.stringify(suggestions)); + }); + + await SearchTestUtils.installSearchExtension({ + name: engineName, + search_url: `http://localhost:${httpServer.identity.primaryPort}/search`, + suggest_url: `http://localhost:${httpServer.identity.primaryPort}/suggest`, + suggest_url_get_params: "?q={searchTerms}", + search_form: `http://localhost:${httpServer.identity.primaryPort}/search?q={searchTerms}`, + }); + + const tailEngine = Services.search.getEngineByName(engineName); + const originalEngine = await Services.search.getDefault(); + Services.search.setDefault( + tailEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + + return async () => { + Services.search.setDefault( + originalEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + httpServer.stop(() => {}); + await SpecialPowers.popPrefEnv(); + }; +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js new file mode 100644 index 0000000000..244e27d272 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js @@ -0,0 +1,340 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +ChromeUtils.defineESModuleGetters(this, { + CUSTOM_SEARCH_SHORTCUTS: + "resource://activity-stream/lib/SearchShortcuts.sys.mjs", + NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", + SEARCH_SHORTCUTS: "resource://activity-stream/lib/SearchShortcuts.sys.mjs", + SearchService: "resource://gre/modules/SearchService.sys.mjs", +}); + +async function doTopsitesTest({ trigger, assert }) { + await doTest(async browser => { + await addTopSites("https://example.com/"); + + await showResultByArrowDown(); + await selectRowByURL("https://example.com/"); + + await trigger(); + await assert(); + }); +} + +async function doTopsitesSearchTest({ trigger, assert }) { + await doTest(async browser => { + let extension = await SearchTestUtils.installSearchExtension( + { + name: "MozSearch", + keyword: "@test", + search_url: "https://example.com/", + search_url_get_params: "q={searchTerms}", + }, + { skipUnload: true } + ); + + // Fresh profiles come with an empty set of pinned websites (pref doesn't + // exist). Search shortcut topsites make this test more complicated because + // the feature pins a new website on startup. Behaviour can vary when running + // with --verify so it's more predictable to clear pins entirely. + Services.prefs.clearUserPref("browser.newtabpage.pinned"); + NewTabUtils.pinnedLinks.resetCache(); + + let entry = { + keyword: "@test", + shortURL: "example", + url: "https://example.com/", + }; + + // The array is used to identify sites that should be converted to + // a Top Site. + let searchShortcuts = JSON.parse(JSON.stringify(SEARCH_SHORTCUTS)); + SEARCH_SHORTCUTS.push(entry); + + // TopSitesFeed takes a list of app provided engines and determine if the + // engine containing an alias that matches a keyword inside of this array. + // If so, the list of search shortcuts in the store will be updated. + let customSearchShortcuts = JSON.parse( + JSON.stringify(CUSTOM_SEARCH_SHORTCUTS) + ); + CUSTOM_SEARCH_SHORTCUTS.push(entry); + + // TopSitesFeed only allows app provided engines to be included as + // search shortcuts. + // eslint-disable-next-line mozilla/valid-lazy + let sandbox = lazy.sinon.createSandbox(); + sandbox + .stub(SearchService.prototype, "getAppProvidedEngines") + .resolves([{ aliases: ["@test"] }]); + + let siteToPin = { + url: "https://example.com", + label: "@test", + searchTopSite: true, + }; + NewTabUtils.pinnedLinks.pin(siteToPin, 0); + + await updateTopSites(sites => { + return sites && sites[0] && sites[0].url == "https://example.com"; + }, true); + + BrowserTestUtils.startLoadingURIString(browser, "about:newtab"); + await BrowserTestUtils.browserStopped(browser, "about:newtab"); + + await BrowserTestUtils.synthesizeMouseAtCenter( + ".search-shortcut .top-site-button", + {}, + gBrowser.selectedBrowser + ); + + EventUtils.synthesizeKey("x"); + await UrlbarTestUtils.promiseSearchComplete(window); + + await trigger(); + await assert(); + + // Clean up. + NewTabUtils.pinnedLinks.unpin(siteToPin); + SEARCH_SHORTCUTS.pop(); + CUSTOM_SEARCH_SHORTCUTS.pop(); + // Sanity check to ensure we're leaving the shortcuts in their default state. + Assert.deepEqual( + searchShortcuts, + SEARCH_SHORTCUTS, + "SEARCH_SHORTCUTS values" + ); + Assert.deepEqual( + customSearchShortcuts, + CUSTOM_SEARCH_SHORTCUTS, + "CUSTOM_SEARCH_SHORTCUTS values" + ); + sandbox.restore(); + Services.prefs.clearUserPref("browser.newtabpage.pinned"); + NewTabUtils.pinnedLinks.resetCache(); + await extension.unload(); + }); +} + +async function doTypedTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + + await trigger(); + await assert(); + }); +} + +async function doTypedWithResultsPopupTest({ trigger, assert }) { + await doTest(async browser => { + await showResultByArrowDown(); + EventUtils.synthesizeKey("x"); + await UrlbarTestUtils.promiseSearchComplete(window); + + await trigger(); + await assert(); + }); +} + +async function doPastedTest({ trigger, assert }) { + await doTest(async browser => { + await doPaste("www.example.com"); + + await trigger(); + await assert(); + }); +} + +async function doPastedWithResultsPopupTest({ trigger, assert }) { + await doTest(async browser => { + await showResultByArrowDown(); + await doPaste("x"); + + await trigger(); + await assert(); + }); +} + +async function doReturnedRestartedRefinedTest({ trigger, assert }) { + const testData = [ + { + firstInput: "x", + // Just move the focus to the URL bar after blur. + secondInput: null, + expected: "returned", + }, + { + firstInput: "x", + secondInput: "x", + expected: "returned", + }, + { + firstInput: "x", + secondInput: "y", + expected: "restarted", + }, + { + firstInput: "x", + secondInput: "x y", + expected: "refined", + }, + { + firstInput: "x y", + secondInput: "x", + expected: "refined", + }, + ]; + + for (const { firstInput, secondInput, expected } of testData) { + await doTest(async browser => { + await openPopup(firstInput); + await waitForPauseImpression(); + await doBlur(); + + await UrlbarTestUtils.promisePopupOpen(window, () => { + document.getElementById("Browser:OpenLocation").doCommand(); + }); + if (secondInput) { + for (let i = 0; i < secondInput.length; i++) { + EventUtils.synthesizeKey(secondInput.charAt(i)); + } + } + await UrlbarTestUtils.promiseSearchComplete(window); + + await trigger(); + await assert(expected); + }); + } +} + +async function doPersistedSearchTermsTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + await waitForPauseImpression(); + await doEnter(); + + await openPopup("x"); + + await trigger(); + await assert(); + }); +} + +async function doPersistedSearchTermsRestartedRefinedTest({ + enabled, + trigger, + assert, +}) { + const testData = [ + { + firstInput: "x", + // Just move the focus to the URL bar after engagement. + secondInput: null, + expected: enabled ? "persisted_search_terms" : "topsites", + }, + { + firstInput: "x", + secondInput: "x", + expected: enabled ? "persisted_search_terms" : "typed", + }, + { + firstInput: "x", + secondInput: "y", + expected: enabled ? "persisted_search_terms_restarted" : "typed", + }, + { + firstInput: "x", + secondInput: "x y", + expected: enabled ? "persisted_search_terms_refined" : "typed", + }, + { + firstInput: "x y", + secondInput: "x", + expected: enabled ? "persisted_search_terms_refined" : "typed", + }, + ]; + + for (const { firstInput, secondInput, expected } of testData) { + await doTest(async browser => { + await openPopup(firstInput); + await waitForPauseImpression(); + await doEnter(); + + await UrlbarTestUtils.promisePopupOpen(window, () => { + EventUtils.synthesizeKey("l", { accelKey: true }); + }); + if (secondInput) { + for (let i = 0; i < secondInput.length; i++) { + EventUtils.synthesizeKey(secondInput.charAt(i)); + } + } + await UrlbarTestUtils.promiseSearchComplete(window); + + await trigger(); + await assert(expected); + }); + } +} + +async function doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ + enabled, + trigger, + assert, +}) { + const testData = [ + { + firstInput: "x", + // Just move the focus to the URL bar after blur. + secondInput: null, + expected: enabled ? "persisted_search_terms" : "returned", + }, + { + firstInput: "x", + secondInput: "x", + expected: enabled ? "persisted_search_terms" : "returned", + }, + { + firstInput: "x", + secondInput: "y", + expected: enabled ? "persisted_search_terms_restarted" : "restarted", + }, + { + firstInput: "x", + secondInput: "x y", + expected: enabled ? "persisted_search_terms_refined" : "refined", + }, + { + firstInput: "x y", + secondInput: "x", + expected: enabled ? "persisted_search_terms_refined" : "refined", + }, + ]; + + for (const { firstInput, secondInput, expected } of testData) { + await doTest(async browser => { + await openPopup("any search"); + await waitForPauseImpression(); + await doEnter(); + + await openPopup(firstInput); + await waitForPauseImpression(); + await doBlur(); + + await UrlbarTestUtils.promisePopupOpen(window, () => { + EventUtils.synthesizeKey("l", { accelKey: true }); + }); + if (secondInput) { + for (let i = 0; i < secondInput.length; i++) { + EventUtils.synthesizeKey(secondInput.charAt(i)); + } + } + await UrlbarTestUtils.promiseSearchComplete(window); + + await trigger(); + await assert(expected); + }); + } +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js new file mode 100644 index 0000000000..6d4c61c7f0 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js @@ -0,0 +1,56 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +async function doNCharsTest({ trigger, assert }) { + for (const input of ["x", "xx", "xx x", "xx x "]) { + await doTest(async browser => { + await openPopup(input); + + await trigger(); + await assert(input.length); + }); + } +} + +async function doNCharsWithOverMaxTextLengthCharsTest({ trigger, assert }) { + await doTest(async browser => { + let input = ""; + for (let i = 0; i < UrlbarUtils.MAX_TEXT_LENGTH * 2; i++) { + input += "x"; + } + await openPopup(input); + + await trigger(); + await assert(UrlbarUtils.MAX_TEXT_LENGTH * 2); + }); +} + +async function doNWordsTest({ trigger, assert }) { + for (const input of ["x", "xx", "xx x", "xx x "]) { + await doTest(async browser => { + await openPopup(input); + + await trigger(); + const splits = input.trim().split(" "); + await assert(splits.length); + }); + } +} + +async function doNWordsWithOverMaxTextLengthCharsTest({ trigger, assert }) { + await doTest(async browser => { + const word = "1234 "; + let input = ""; + while (input.length < UrlbarUtils.MAX_TEXT_LENGTH * 2) { + input += word; + } + await openPopup(input); + + await trigger(); + await assert(UrlbarUtils.MAX_TEXT_LENGTH / word.length); + }); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js new file mode 100644 index 0000000000..ef95873813 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +async function doUrlbarNewTabTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + + await trigger(); + await assert(); + }); +} + +async function doUrlbarTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + await waitForPauseImpression(); + await doEnter(); + await openPopup("y"); + + await trigger(); + await assert(); + }); +} + +async function doHandoffTest({ trigger, assert }) { + await doTest(async browser => { + BrowserTestUtils.startLoadingURIString(browser, "about:newtab"); + await BrowserTestUtils.browserStopped(browser, "about:newtab"); + await SpecialPowers.spawn(browser, [], function () { + const searchInput = content.document.querySelector(".fake-editable"); + searchInput.click(); + }); + EventUtils.synthesizeKey("x"); + await UrlbarTestUtils.promiseSearchComplete(window); + + await trigger(); + await assert(); + }); +} + +async function doUrlbarAddonpageTest({ trigger, assert }) { + const extensionData = { + files: { + "page.html": "<!DOCTYPE html>hello", + }, + }; + const extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + const extensionURL = `moz-extension://${extension.uuid}/page.html`; + + await doTest(async browser => { + const onLoad = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.startLoadingURIString(browser, extensionURL); + await onLoad; + await openPopup("x"); + + await trigger(); + await assert(); + }); + + await extension.unload(); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js new file mode 100644 index 0000000000..c0af764e7f --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +async function doSearchEngineDefaultIdTest({ trigger, assert }) { + await doTest(async browser => { + info("Test with current engine"); + const defaultEngine = await Services.search.getDefault(); + + await openPopup("x"); + await trigger(); + await assert(defaultEngine.telemetryId); + }); + + await doTest(async browser => { + info("Test with new engine"); + const defaultEngine = await Services.search.getDefault(); + const newEngineName = "NewDummyEngine"; + await SearchTestUtils.installSearchExtension({ + name: newEngineName, + search_url: "https://example.com/", + search_url_get_params: "q={searchTerms}", + }); + const newEngine = await Services.search.getEngineByName(newEngineName); + Assert.notEqual(defaultEngine.telemetryId, newEngine.telemetryId); + await Services.search.setDefault( + newEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + + await openPopup("x"); + await trigger(); + await assert(newEngine.telemetryId); + + await Services.search.setDefault( + defaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + }); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js new file mode 100644 index 0000000000..5c877da05f --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +async function doNotSearchModeTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + + await trigger(); + await assert(); + }); +} + +async function doSearchEngineTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("x"); + await UrlbarTestUtils.enterSearchMode(window); + + await trigger(); + await assert(); + }); +} + +async function doBookmarksTest({ trigger, assert }) { + await doTest(async browser => { + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "https://example.com/bookmark", + title: "bookmark", + }); + await openPopup("bookmark"); + await UrlbarTestUtils.enterSearchMode(window, { + source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + }); + await selectRowByURL("https://example.com/bookmark"); + + await trigger(); + await assert(); + }); +} + +async function doHistoryTest({ trigger, assert }) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.autoFill", false]], + }); + + await doTest(async browser => { + await PlacesTestUtils.addVisits("https://example.com/test"); + await openPopup("example"); + await UrlbarTestUtils.enterSearchMode(window, { + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + }); + await selectRowByURL("https://example.com/test"); + + await trigger(); + await assert(); + }); + + await SpecialPowers.popPrefEnv(); +} + +async function doTabTest({ trigger, assert }) { + const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/"); + + await doTest(async browser => { + await openPopup("example"); + await UrlbarTestUtils.enterSearchMode(window, { + source: UrlbarUtils.RESULT_SOURCE.TABS, + }); + await selectRowByProvider("Places"); + + await trigger(); + await assert(); + }); + + BrowserTestUtils.removeTab(tab); +} + +async function doActionsTest({ trigger, assert }) { + await doTest(async browser => { + await openPopup("add"); + await UrlbarTestUtils.enterSearchMode(window, { + source: UrlbarUtils.RESULT_SOURCE.ACTIONS, + }); + await selectRowByProvider("quickactions"); + + await trigger(); + await assert(); + }); +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js new file mode 100644 index 0000000000..367387b0e8 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js @@ -0,0 +1,473 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/head-common.js", + this +); + +ChromeUtils.defineESModuleGetters(this, { + QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs", +}); + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "QuickSuggestTestUtils", () => { + const { QuickSuggestTestUtils: module } = ChromeUtils.importESModule( + "resource://testing-common/QuickSuggestTestUtils.sys.mjs" + ); + module.init(this); + return module; +}); + +ChromeUtils.defineLazyGetter(this, "MerinoTestUtils", () => { + const { MerinoTestUtils: module } = ChromeUtils.importESModule( + "resource://testing-common/MerinoTestUtils.sys.mjs" + ); + module.init(this); + return module; +}); + +ChromeUtils.defineESModuleGetters(lazy, { + UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs", + sinon: "resource://testing-common/Sinon.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(this, "PlacesFrecencyRecalculator", () => { + return Cc["@mozilla.org/places/frecency-recalculator;1"].getService( + Ci.nsIObserver + ).wrappedJSObject; +}); + +async function addTopSites(url) { + for (let i = 0; i < 5; i++) { + await PlacesTestUtils.addVisits(url); + } + await updateTopSites(sites => { + return sites && sites[0] && sites[0].url == url; + }); +} + +function assertAbandonmentTelemetry(expectedExtraList) { + _assertGleanTelemetry("abandonment", expectedExtraList); +} + +function assertEngagementTelemetry(expectedExtraList) { + _assertGleanTelemetry("engagement", expectedExtraList); +} + +function assertImpressionTelemetry(expectedExtraList) { + _assertGleanTelemetry("impression", expectedExtraList); +} + +function assertExposureTelemetry(expectedExtraList) { + _assertGleanTelemetry("exposure", expectedExtraList); +} + +function _assertGleanTelemetry(telemetryName, expectedExtraList) { + const telemetries = Glean.urlbar[telemetryName].testGetValue() ?? []; + info( + "Asserting Glean telemetry is correct, actual events are: " + + JSON.stringify(telemetries) + ); + Assert.equal( + telemetries.length, + expectedExtraList.length, + "Telemetry event length matches expected event length." + ); + + for (let i = 0; i < telemetries.length; i++) { + const telemetry = telemetries[i]; + Assert.equal(telemetry.category, "urlbar"); + Assert.equal(telemetry.name, telemetryName); + + const expectedExtra = expectedExtraList[i]; + for (const key of Object.keys(expectedExtra)) { + Assert.equal( + telemetry.extra[key], + expectedExtra[key], + `${key} is correct` + ); + } + } +} + +async function ensureQuickSuggestInit({ ...args } = {}) { + return lazy.QuickSuggestTestUtils.ensureQuickSuggestInit({ + ...args, + remoteSettingsRecords: [ + { + type: "data", + attachment: [ + { + id: 1, + url: "https://example.com/sponsored", + title: "Sponsored suggestion", + keywords: ["sponsored"], + click_url: "https://example.com/click", + impression_url: "https://example.com/impression", + advertiser: "TestAdvertiser", + iab_category: "22 - Shopping", + icon: "1234", + }, + { + id: 2, + url: `https://example.com/nonsponsored`, + title: "Non-sponsored suggestion", + keywords: ["nonsponsored"], + click_url: "https://example.com/click", + impression_url: "https://example.com/impression", + advertiser: "Wikipedia", + iab_category: "5 - Education", + icon: "1234", + }, + ], + }, + { + type: "weather", + weather: MerinoTestUtils.WEATHER_RS_DATA, + }, + ], + }); +} + +async function doBlur() { + await UrlbarTestUtils.promisePopupClose(window, () => { + gURLBar.blur(); + }); +} + +async function doClick() { + const selected = UrlbarTestUtils.getSelectedRow(window); + const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + EventUtils.synthesizeMouseAtCenter(selected, {}); + await onLoad; +} + +async function doClickSubButton(selector) { + const selected = UrlbarTestUtils.getSelectedElement(window); + const button = selected.closest(".urlbarView-row").querySelector(selector); + EventUtils.synthesizeMouseAtCenter(button, {}); +} + +async function doDropAndGo(data) { + const onLoad = BrowserTestUtils.browserLoaded(browser); + EventUtils.synthesizeDrop( + document.getElementById("back-button"), + gURLBar.inputField, + [[{ type: "text/plain", data }]], + "copy", + window + ); + await onLoad; +} + +async function doEnter(modifier = {}) { + const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + EventUtils.synthesizeKey("KEY_Enter", modifier); + await onLoad; +} + +async function doPaste(data) { + await SimpleTest.promiseClipboardChange(data, () => { + clipboardHelper.copyString(data); + }); + + gURLBar.focus(); + gURLBar.select(); + document.commandDispatcher + .getControllerForCommand("cmd_paste") + .doCommand("cmd_paste"); + await UrlbarTestUtils.promiseSearchComplete(window); +} + +async function doPasteAndGo(data) { + await SimpleTest.promiseClipboardChange(data, () => { + clipboardHelper.copyString(data); + }); + const inputBox = gURLBar.querySelector("moz-input-box"); + const contextMenu = inputBox.menupopup; + const onPopup = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, { + type: "contextmenu", + button: 2, + }); + await onPopup; + const onLoad = BrowserTestUtils.browserLoaded(browser); + const menuitem = inputBox.getMenuItem("paste-and-go"); + contextMenu.activateItem(menuitem); + await onLoad; +} + +async function doTest(testFn) { + await Services.fog.testFlushAllChildren(); + Services.fog.testResetFOG(); + // Enable recording telemetry for impression, as it is disabled by default. + Services.fog.setMetricsFeatureConfig( + JSON.stringify({ + "urlbar.impression": true, + }) + ); + + gURLBar.controller.engagementEvent.reset(); + await PlacesUtils.history.clear(); + await PlacesUtils.bookmarks.eraseEverything(); + await PlacesTestUtils.clearHistoryVisits(); + await PlacesTestUtils.clearInputHistory(); + await UrlbarTestUtils.formHistory.clear(window); + await QuickSuggest.blockedSuggestions.clear(); + await QuickSuggest.blockedSuggestions._test_readyPromise; + await updateTopSites(() => true); + + try { + await BrowserTestUtils.withNewTab(gBrowser, testFn); + } finally { + Services.fog.setMetricsFeatureConfig("{}"); + } +} + +async function initGroupTest() { + /* import-globals-from head-groups.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js", + this + ); + await setup(); +} + +async function initInteractionTest() { + /* import-globals-from head-interaction.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js", + this + ); + await setup(); +} + +async function initNCharsAndNWordsTest() { + /* import-globals-from head-n_chars_n_words.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js", + this + ); + await setup(); +} + +async function initSapTest() { + /* import-globals-from head-sap.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js", + this + ); + await setup(); +} + +async function initSearchEngineDefaultIdTest() { + /* import-globals-from head-search_engine_default_id.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js", + this + ); + await setup(); +} + +async function initSearchModeTest() { + /* import-globals-from head-search_mode.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js", + this + ); + await setup(); +} + +async function initExposureTest() { + /* import-globals-from head-exposure.js */ + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/urlbar/tests/engagementTelemetry/browser/head-exposure.js", + this + ); + await setup(); +} + +function loadOmniboxAddon({ keyword }) { + return ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["tabs"], + omnibox: { + keyword, + }, + }, + background() { + /* global browser */ + browser.omnibox.setDefaultSuggestion({ + description: "doit", + }); + browser.omnibox.onInputEntered.addListener(() => { + browser.tabs.update({ url: "https://example.com/" }); + }); + browser.omnibox.onInputChanged.addListener((text, suggest) => { + suggest([]); + }); + }, + }); +} + +async function loadRemoteTab(url) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", false], + ["browser.urlbar.maxHistoricalSearchSuggestions", 0], + ["browser.urlbar.autoFill", false], + ["services.sync.username", "fake"], + ["services.sync.syncedTabs.showRemoteTabs", true], + ], + }); + + const REMOTE_TAB = { + id: "test", + type: "client", + lastModified: 1492201200, + name: "test", + clientType: "desktop", + tabs: [ + { + type: "tab", + title: "tesrt", + url, + icon: UrlbarUtils.ICON.DEFAULT, + client: "test", + lastUsed: Math.floor(Date.now() / 1000), + }, + ], + }; + + const sandbox = lazy.sinon.createSandbox(); + // eslint-disable-next-line no-undef + const syncedTabs = SyncedTabs; + const originalSyncedTabsInternal = syncedTabs._internal; + syncedTabs._internal = { + isConfiguredToSyncTabs: true, + hasSyncedThisSession: true, + getTabClients() { + return Promise.resolve([]); + }, + syncTabs() { + return Promise.resolve(); + }, + }; + const weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + const oldWeaveServiceReady = weaveXPCService.ready; + weaveXPCService.ready = true; + sandbox + .stub(syncedTabs._internal, "getTabClients") + .callsFake(() => Promise.resolve(Cu.cloneInto([REMOTE_TAB], {}))); + + return { + async unload() { + sandbox.restore(); + weaveXPCService.ready = oldWeaveServiceReady; + syncedTabs._internal = originalSyncedTabsInternal; + // Reset internal cache in UrlbarProviderRemoteTabs. + Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs"); + await SpecialPowers.popPrefEnv(); + }, + }; +} + +async function openPopup(input) { + await UrlbarTestUtils.promisePopupOpen(window, async () => { + await UrlbarTestUtils.inputIntoURLBar(window, input); + }); + await UrlbarTestUtils.promiseSearchComplete(window); +} + +async function selectRowByURL(url) { + for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) { + const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i); + if (detail.url === url) { + UrlbarTestUtils.setSelectedRowIndex(window, i); + return; + } + } +} + +async function selectRowByProvider(provider) { + for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) { + const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i); + if (detail.result.providerName === provider) { + UrlbarTestUtils.setSelectedRowIndex(window, i); + break; + } + } +} + +async function selectRowByType(type) { + for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) { + const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i); + if (detail.result.payload.type === type) { + UrlbarTestUtils.setSelectedRowIndex(window, i); + return; + } + } +} + +async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.searchEngagementTelemetry.enabled", true], + ["browser.urlbar.quickactions.enabled", true], + ["browser.urlbar.quickactions.minimumSearchString", 0], + ["browser.urlbar.suggest.quickactions", true], + ["browser.urlbar.shortcuts.quickactions", true], + [ + "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", + 100, + ], + ], + }); + + const engine = await SearchTestUtils.promiseNewSearchEngine({ + url: "chrome://mochitests/content/browser/browser/components/urlbar/tests/browser/searchSuggestionEngine.xml", + }); + const originalDefaultEngine = await Services.search.getDefault(); + await Services.search.setDefault( + engine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + await Services.search.moveEngine(engine, 0); + + registerCleanupFunction(async function () { + await SpecialPowers.popPrefEnv(); + await Services.search.setDefault( + originalDefaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + }); +} + +async function setupNimbus(variables) { + return lazy.UrlbarTestUtils.initNimbusFeature(variables); +} + +async function showResultByArrowDown() { + gURLBar.value = ""; + gURLBar.select(); + await UrlbarTestUtils.promisePopupOpen(window, () => { + EventUtils.synthesizeKey("KEY_ArrowDown"); + }); + await UrlbarTestUtils.promiseSearchComplete(window); +} + +async function waitForPauseImpression() { + await new Promise(r => + setTimeout( + r, + UrlbarPrefs.get("searchEngagementTelemetry.pauseImpressionIntervalMs") + ) + ); + await Services.fog.testFlushAllChildren(); +} |