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_search_telemetry_engagement_target.js | 433 +++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 browser/components/search/test/browser/browser_search_telemetry_engagement_target.js (limited to 'browser/components/search/test/browser/browser_search_telemetry_engagement_target.js') diff --git a/browser/components/search/test/browser/browser_search_telemetry_engagement_target.js b/browser/components/search/test/browser/browser_search_telemetry_engagement_target.js new file mode 100644 index 0000000000..d29169b776 --- /dev/null +++ b/browser/components/search/test/browser/browser_search_telemetry_engagement_target.js @@ -0,0 +1,433 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * These tests load SERPs and click on links that can either be ads or non ads + * and verifies that the engagement events and the target associated with them + * are correct. + */ + +"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\/searchTelemetryAd_/, + queryParamName: "s", + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + nonAdsLinkRegexps: [ + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/searchTelemetryAd_nonAdsLink_redirect.html/, + ], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + components: [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_CAROUSEL, + included: { + parent: { + selector: ".moz-carousel", + }, + children: [ + { + selector: ".moz-carousel-card", + countChildren: true, + }, + ], + related: { + selector: "button", + }, + }, + }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + included: { + parent: { + selector: ".moz_ad", + }, + children: [ + { + selector: ".multi-col", + type: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK, + }, + ], + related: { + selector: "button", + }, + }, + excluded: { + parent: { + selector: ".rhs", + }, + }, + }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.INCONTENT_SEARCHBOX, + included: { + parent: { + selector: "form", + }, + children: [ + { + // This isn't contained in any of the HTML examples but the + // presence of the entry ensures that if it is not found during + // a topDown examination, the next element in the array is + // inspected and found. + selector: "textarea", + }, + { + selector: "input", + }, + ], + related: { + selector: "div", + }, + }, + topDown: true, + }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ], + }, +]; + +// This is used to check if not providing an nonAdsLinkRegexp can still +// reliably categorize non_ads_links. +const TEST_PROVIDER_INFO_NO_NON_ADS_REGEXP = [ + { + ...TEST_PROVIDER_INFO[0], + nonAdsLinkRegexps: [], + }, +]; + +function getSERPUrl(page, organic = false) { + let url = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.org" + ) + page; + return `${url}?s=test${organic ? "" : "&abc=ff"}`; +} + +async function promiseImpressionReceived() { + return TestUtils.waitForCondition(() => { + let adImpressions = Glean.serp.adImpression.testGetValue() ?? []; + return adImpressions.length; + }, "Should have received an ad impression."); +} + +async function waitForIdle() { + for (let i = 0; i < 10; i++) { + await new Promise(resolve => Services.tm.idleDispatchToMainThread(resolve)); + } +} + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + // Enable local telemetry recording for the duration of the tests. + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.search.log", true], + ["browser.search.serpEventTelemetry.enabled", true], + ], + }); + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +// This test ensures clicking a non-first link in a component registers the +// proper component. This is because the first link of a component does the +// heavy lifting in finding the parent and best categorization of the +// component. Subsequent anchors that have the same parent get grouped into it. +// Additionally, this test deliberately has ads with different paths so that +// there are no collisions in hrefs. +add_task(async function test_click_second_ad_in_component() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_text.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#deep_ad_sitelink", + {}, + tab.linkedBrowser + ); + await pageLoadPromise; + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK, + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// If a provider appends query parameters to a link after the page has been +// parsed, we should still be able to record the click. +add_task(async function test_click_ads_link_modified() { + resetTelemetry(); + + let url = getSERPUrl("searchTelemetryAd_components_text.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + let target = content.document.getElementById("deep_ad_sitelink"); + let href = target.getAttribute("href"); + target.setAttribute("href", href + "?foo=bar"); + content.document.getElementById("deep_ad_sitelink").click(); + }); + await browserLoadedPromise; + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK, + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Search box is a special case which has to be tracked in the child process. +add_task(async function test_click_and_submit_incontent_searchbox() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_searchbox.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + // Click on the searchbox. + let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser, url); + await BrowserTestUtils.synthesizeMouseAtCenter( + "form input", + {}, + tab.linkedBrowser + ); + EventUtils.synthesizeKey("KEY_Enter"); + await pageLoadPromise; + + await TestUtils.waitForCondition(() => { + return Glean.serp.impression?.testGetValue()?.length == 2; + }, "Should have two impressions."); + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.INCONTENT_SEARCHBOX, + }, + { + action: SearchSERPTelemetryUtils.ACTIONS.SUBMITTED, + target: SearchSERPTelemetryUtils.COMPONENTS.INCONTENT_SEARCHBOX, + }, + ], + }, + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "follow_on_from_refine_on_incontent_search", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Click an auto-suggested term. The element that is clicked is related +// to the searchbox but not in search-telemetry-v2 because it can be too +// difficult to determine ahead of time since the elements are generated +// dynamically. So instead it should listen to an element higher in the DOM. +add_task(async function test_click_autosuggest() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_searchbox.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + // Click an autosuggested term. + let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser, url); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#suggest", + {}, + tab.linkedBrowser + ); + await pageLoadPromise; + + await TestUtils.waitForCondition(() => { + return Glean.serp.impression?.testGetValue()?.length == 2; + }, "Should have two impressions."); + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.SUBMITTED, + target: SearchSERPTelemetryUtils.COMPONENTS.INCONTENT_SEARCHBOX, + }, + ], + }, + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "follow_on_from_refine_on_incontent_search", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Carousel related buttons expand content. +add_task(async function test_click_carousel_expand() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_carousel.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + // Click a button that is expected to expand. + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.querySelector("button").click(); + }); + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.EXPANDED, + target: SearchSERPTelemetryUtils.COMPONENTS.AD_CAROUSEL, + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// This test clicks a link that has apostrophes in the both the path and list +// of query parameters, and uses search telemetry with no nonAdsRegexps defined, +// which will force us to cache every non ads link in a map and pass it back to +// the parent. +// If this test fails, it means we're doing the conversion wrong, because when +// we observe the clicked URL in the parent process, it should look exactly the +// same as how it was saved in the hrefToComponent map. +add_task(async function test_click_link_with_special_characters_in_path() { + SearchSERPTelemetry.overrideSearchTelemetryForTests( + TEST_PROVIDER_INFO_NO_NON_ADS_REGEXP + ); + await waitForIdle(); + + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_text.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let pageLoadPromise = BrowserTestUtils.waitForLocationChange( + gBrowser, + "https://example.com/path'?hello_world&foo=bar%27s" + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#non_ads_link_with_special_characters_in_path", + {}, + tab.linkedBrowser + ); + await pageLoadPromise; + + assertImpressionEvents([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.NON_ADS_LINK, + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); -- cgit v1.2.3