From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser/browser_search_telemetry_sources.js | 497 +++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 browser/components/search/test/browser/browser_search_telemetry_sources.js (limited to 'browser/components/search/test/browser/browser_search_telemetry_sources.js') diff --git a/browser/components/search/test/browser/browser_search_telemetry_sources.js b/browser/components/search/test/browser/browser_search_telemetry_sources.js new file mode 100644 index 0000000000..173bc09694 --- /dev/null +++ b/browser/components/search/test/browser/browser_search_telemetry_sources.js @@ -0,0 +1,497 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * Main tests for SearchSERPTelemetry - general engine visiting and link clicking. + * + * NOTE: As this test file is already fairly long-running, adding to this file + * will likely cause timeout errors with test-verify jobs on Treeherder. + * Therefore, please do not add further tasks to this file. + */ + +"use strict"; + +const { SearchSERPTelemetry, SearchSERPTelemetryUtils } = + ChromeUtils.importESModule("resource:///modules/SearchSERPTelemetry.sys.mjs"); + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/searchTelemetry(?:Ad)?/, + queryParamName: "s", + codeParamName: "abc", + taggedCodes: ["ff"], + followOnParamNames: ["a"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad2?/], + components: [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ], + }, +]; + +function getPageUrl(useAdPage = false) { + let page = useAdPage ? "searchTelemetryAd.html" : "searchTelemetry.html"; + return `https://example.org/browser/browser/components/search/test/browser/${page}`; +} + +/** + * Returns the index of the first search suggestion in the urlbar results. + * + * @returns {number} An index, or -1 if there are no search suggestions. + */ +async function getFirstSuggestionIndex() { + const matchCount = UrlbarTestUtils.getResultCount(window); + for (let i = 0; i < matchCount; i++) { + let result = await UrlbarTestUtils.getDetailsOfResultAt(window, i); + if ( + result.type == UrlbarUtils.RESULT_TYPE.SEARCH && + result.searchParams.suggestion + ) { + return i; + } + } + return -1; +} + +// sharedData messages are only passed to the child on idle. Therefore +// we wait for a few idles to try and ensure the messages have been able +// to be passed across and handled. +async function waitForIdle() { + for (let i = 0; i < 10; i++) { + await new Promise(resolve => Services.tm.idleDispatchToMainThread(resolve)); + } +} + +SearchTestUtils.init(this); + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.searches", true], + [ + "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", + true, + ], + // Ensure to add search suggestion telemetry as search_suggestion not search_formhistory. + ["browser.urlbar.maxHistoricalSearchSuggestions", 0], + ["browser.search.serpEventTelemetry.enabled", true], + ], + }); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + Services.prefs.setBoolPref("browser.search.log", true); + + await SearchTestUtils.installSearchExtension( + { + search_url: getPageUrl(true), + search_url_get_params: "s={searchTerms}&abc=ff", + suggest_url: + "https://example.org/browser/browser/components/search/test/browser/searchSuggestionEngine.sjs", + suggest_url_get_params: "query={searchTerms}", + }, + { setAsDefault: true } + ); + + await gCUITestUtils.addSearchBar(); + + registerCleanupFunction(async () => { + gCUITestUtils.removeSearchBar(); + Services.prefs.clearUserPref("browser.search.log"); + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +async function track_ad_click( + expectedHistogramSource, + expectedScalarSource, + searchAdsFn, + cleanupFn +) { + searchCounts.clear(); + Services.telemetry.clearScalars(); + + let expectedContentScalarKey = "example:tagged:ff"; + let expectedScalarKey = "example:tagged"; + let expectedHistogramSAPSourceKey = `other-Example.${expectedHistogramSource}`; + let expectedContentScalar = `browser.search.content.${expectedScalarSource}`; + let expectedWithAdsScalar = `browser.search.withads.${expectedScalarSource}`; + let expectedAdClicksScalar = `browser.search.adclicks.${expectedScalarSource}`; + + let tab = await searchAdsFn(); + + await assertSearchSourcesTelemetry( + { + [expectedHistogramSAPSourceKey]: 1, + }, + { + [expectedContentScalar]: { [expectedContentScalarKey]: 1 }, + [expectedWithAdsScalar]: { [expectedScalarKey]: 1 }, + } + ); + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: expectedScalarSource, + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + }, + ]); + // Ad impression data is needed to categorize ads on the page in order to + // register ad click events before a click occurs. We don't assert their + // precise values here because other tests cover that the component + // categorizations are valid. + await promiseAdImpressionReceived(); + + let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser); + BrowserTestUtils.synthesizeMouseAtCenter("#ad1", {}, tab.linkedBrowser); + await pageLoadPromise; + await promiseWaitForAdLinkCheck(); + + await assertSearchSourcesTelemetry( + { + [expectedHistogramSAPSourceKey]: 1, + }, + { + [expectedContentScalar]: { [expectedContentScalarKey]: 1 }, + [expectedWithAdsScalar]: { [expectedScalarKey]: 1 }, + [expectedAdClicksScalar]: { [expectedScalarKey]: 1 }, + } + ); + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: expectedScalarSource, + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + }, + ], + }, + ]); + + await cleanupFn(); + + Services.fog.testResetFOG(); +} + +add_task(async function test_source_urlbar() { + let tab; + await track_ad_click( + "urlbar", + "urlbar", + async () => { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "searchSuggestion", + }); + let idx = await getFirstSuggestionIndex(); + Assert.greaterOrEqual(idx, 0, "there should be a first suggestion"); + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + while (idx--) { + EventUtils.sendKey("down"); + } + EventUtils.sendKey("return"); + await loadPromise; + return tab; + }, + async () => { + BrowserTestUtils.removeTab(tab); + } + ); +}); + +add_task(async function test_source_urlbar_handoff() { + let tab; + await track_ad_click( + "urlbar-handoff", + "urlbar_handoff", + async () => { + Services.fog.testResetFOG(); + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + BrowserTestUtils.loadURIString(tab.linkedBrowser, "about:newtab"); + await BrowserTestUtils.browserStopped(tab.linkedBrowser, "about:newtab"); + + info("Focus on search input in newtab content"); + await BrowserTestUtils.synthesizeMouseAtCenter( + ".fake-editable", + {}, + tab.linkedBrowser + ); + + info("Get suggestions"); + for (const c of "searchSuggestion".split("")) { + EventUtils.synthesizeKey(c); + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + await new Promise(r => setTimeout(r, 50)); + } + await TestUtils.waitForCondition(async () => { + const index = await getFirstSuggestionIndex(); + return index >= 0; + }, "Wait until suggestions are ready"); + + let idx = await getFirstSuggestionIndex(); + Assert.greaterOrEqual(idx, 0, "there should be a first suggestion"); + const onLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + while (idx--) { + EventUtils.sendKey("down"); + } + EventUtils.sendKey("return"); + await onLoaded; + + return tab; + }, + async () => { + const issueRecords = Glean.newtabSearch.issued.testGetValue(); + Assert.ok(!!issueRecords, "Must have recorded a search issuance"); + Assert.equal(issueRecords.length, 1, "One search, one event"); + const newtabVisitId = issueRecords[0].extra.newtab_visit_id; + Assert.ok(!!newtabVisitId, "Must have a visit id"); + Assert.deepEqual( + { + // Yes, this is tautological. But I want to use deepEqual. + newtab_visit_id: newtabVisitId, + search_access_point: "urlbar_handoff", + telemetry_id: "other-Example", + }, + issueRecords[0].extra, + "Must have recorded the expected information." + ); + const impRecords = Glean.newtabSearchAd.impression.testGetValue(); + Assert.equal(impRecords.length, 1, "One impression, one event."); + Assert.deepEqual( + { + newtab_visit_id: newtabVisitId, + search_access_point: "urlbar_handoff", + telemetry_id: "example", + is_tagged: "true", + is_follow_on: "false", + }, + impRecords[0].extra, + "Must have recorded the expected information." + ); + const clickRecords = Glean.newtabSearchAd.click.testGetValue(); + Assert.equal(clickRecords.length, 1, "One click, one event."); + Assert.deepEqual( + { + newtab_visit_id: newtabVisitId, + search_access_point: "urlbar_handoff", + telemetry_id: "example", + is_tagged: "true", + is_follow_on: "false", + }, + clickRecords[0].extra, + "Must have recorded the expected information." + ); + BrowserTestUtils.removeTab(tab); + } + ); +}); + +add_task(async function test_source_searchbar() { + let tab; + await track_ad_click( + "searchbar", + "searchbar", + async () => { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + let sb = BrowserSearch.searchBar; + // Write the search query in the searchbar. + sb.focus(); + sb.value = "searchSuggestion"; + sb.textbox.controller.startSearch("searchSuggestion"); + // Wait for the popup to show. + await BrowserTestUtils.waitForEvent(sb.textbox.popup, "popupshown"); + // And then for the search to complete. + await BrowserTestUtils.waitForCondition( + () => + sb.textbox.controller.searchStatus >= + Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH, + "The search in the searchbar must complete." + ); + + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + EventUtils.synthesizeKey("KEY_Enter"); + await loadPromise; + return tab; + }, + async () => { + BrowserTestUtils.removeTab(tab); + } + ); +}); + +async function checkAboutPage( + page, + expectedHistogramSource, + expectedScalarSource +) { + let tab; + await track_ad_click( + expectedHistogramSource, + expectedScalarSource, + async () => { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + BrowserTestUtils.loadURIString(tab.linkedBrowser, page); + await BrowserTestUtils.browserStopped(tab.linkedBrowser, page); + + // Wait for the full load. + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", + false, + ], + ], + }); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + await ContentTaskUtils.waitForCondition( + () => content.wrappedJSObject.gContentSearchController.defaultEngine + ); + }); + + let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await typeInSearchField( + tab.linkedBrowser, + "test query", + "newtab-search-text" + ); + await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser); + await p; + return tab; + }, + async () => { + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); + } + ); +} + +add_task(async function test_source_about_home() { + await checkAboutPage("about:home", "abouthome", "about_home"); +}); + +add_task(async function test_source_about_newtab() { + await checkAboutPage("about:newtab", "newtab", "about_newtab"); +}); + +add_task(async function test_source_system() { + let tab; + await track_ad_click( + "system", + "system", + async () => { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + // This is not quite the same as calling from the commandline, but close + // enough for this test. + BrowserSearch.loadSearchFromCommandLine( + "searchSuggestion", + false, + Services.scriptSecurityManager.getSystemPrincipal(), + gBrowser.selectedBrowser.csp + ); + + await loadPromise; + return tab; + }, + async () => { + BrowserTestUtils.removeTab(tab); + } + ); +}); + +add_task(async function test_source_webextension_search() { + /* global browser */ + async function background(SEARCH_TERM) { + // Search with no tabId + browser.search.search({ query: "searchSuggestion", engine: "Example" }); + } + + let searchExtension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["search", "tabs"], + }, + background, + useAddonManager: "temporary", + }); + + let tab; + await track_ad_click( + "webextension", + "webextension", + async () => { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + + await searchExtension.startup(); + + return (tab = await tabPromise); + }, + async () => { + await searchExtension.unload(); + BrowserTestUtils.removeTab(tab); + } + ); +}); + +add_task(async function test_source_webextension_query() { + async function background(SEARCH_TERM) { + // Search with no tabId + browser.search.query({ + text: "searchSuggestion", + disposition: "NEW_TAB", + }); + } + + let searchExtension = ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["search", "tabs"], + }, + background, + useAddonManager: "temporary", + }); + + let tab; + await track_ad_click( + "webextension", + "webextension", + async () => { + let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + + await searchExtension.startup(); + + return (tab = await tabPromise); + }, + async () => { + await searchExtension.unload(); + BrowserTestUtils.removeTab(tab); + } + ); +}); -- cgit v1.2.3