diff options
Diffstat (limited to 'browser/components/search/test/browser/telemetry')
27 files changed, 2449 insertions, 53 deletions
diff --git a/browser/components/search/test/browser/telemetry/browser.toml b/browser/components/search/test/browser/telemetry/browser.toml index 49d8f256aa..660fc4eae2 100644 --- a/browser/components/search/test/browser/telemetry/browser.toml +++ b/browser/components/search/test/browser/telemetry/browser.toml @@ -4,10 +4,7 @@ support-files = ["head.js", "head-spa.js"] prefs = ["browser.search.log=true"] ["browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js"] -support-files = [ - "domain_category_mappings.json", - "searchTelemetryDomainCategorizationReporting.html", -] +support-files = ["searchTelemetryDomainCategorizationReporting.html"] ["browser_search_glean_serp_event_telemetry_enabled_by_nimbus_variable.js"] support-files = ["searchTelemetryAd.html"] @@ -31,9 +28,16 @@ support-files = [ "searchTelemetryAd_components_carousel_outer_container.html", "searchTelemetryAd_components_text.html", "searchTelemetryAd_components_visibility.html", + "searchTelemetryAd_components_cookie_banner.html", "serp.css", ] +["browser_search_telemetry_adImpression_component_skipCount_children.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + +["browser_search_telemetry_adImpression_component_skipCount_parent.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + ["browser_search_telemetry_categorization_timing.js"] ["browser_search_telemetry_content.js"] @@ -42,7 +46,6 @@ support-files = [ support-files = ["searchTelemetryDomainCategorizationReporting.html"] ["browser_search_telemetry_domain_categorization_download_timer.js"] -support-files = ["domain_category_mappings.json"] ["browser_search_telemetry_domain_categorization_extraction.js"] support-files = ["searchTelemetryDomainExtraction.html"] @@ -85,6 +88,15 @@ support-files = [ "serp.css", ] +["browser_search_telemetry_engagement_eventListeners_children.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + +["browser_search_telemetry_engagement_eventListeners_parent.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + +["browser_search_telemetry_engagement_ignoreLinkRegexps.js"] +support-files = ["searchTelemetryAd_searchbox_with_content.html", "serp.css"] + ["browser_search_telemetry_engagement_multiple_tabs.js"] support-files = [ "searchTelemetryAd_searchbox_with_content.html", @@ -98,6 +110,14 @@ support-files = [ "serp.css", ] +["browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js"] +support-files = [ + "searchTelemetryAd_searchbox_with_redirecting_links.html", + "searchTelemetryAd_shopping.html", + "searchTelemetry_redirect_with_js.html", + "serp.css", +] + ["browser_search_telemetry_engagement_query_params.js"] support-files = [ "searchTelemetryAd_components_query_parameters.html", diff --git a/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js b/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js index ed71a7c5ed..e73a9601d4 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js +++ b/browser/components/search/test/browser/telemetry/browser_search_glean_serp_event_telemetry_categorization_enabled_by_nimbus_variable.js @@ -38,7 +38,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js index 8049406d40..5a09353ed6 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component.js @@ -78,6 +78,15 @@ const TEST_PROVIDER_INFO = [ type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, default: true, }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + included: { + parent: { + selector: "#banner", + }, + }, + topDown: true, + }, ], }, ]; @@ -500,3 +509,35 @@ add_task(async function test_impressions_without_ads() { BrowserTestUtils.removeTab(tab); }); + +add_task(async function test_ad_impressions_with_cookie_banner() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + await waitForPageWithAdImpressions(); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_children.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_children.js new file mode 100644 index 0000000000..65cd612a49 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_children.js @@ -0,0 +1,149 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests skipCount property on elements in the children. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + let components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + TEST_PROVIDER_INFO[0].components = components; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +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; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +// For older clients, skipCount won't be available. +add_task(async function test_skip_count_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + children: [ + { + selector: "a", + }, + ], + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_false() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + children: [ + { + selector: "a", + skipCount: false, + }, + ], + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_true() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + children: [ + { + selector: "a", + skipCount: true, + }, + ], + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [], + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_parent.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_parent.js new file mode 100644 index 0000000000..8471215840 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_adImpression_component_skipCount_parent.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests skipCount property on parent elements. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + let components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + TEST_PROVIDER_INFO[0].components = components; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +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; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +// For older clients, skipCount won't be available. +add_task(async function test_skip_count_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + }, + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_false() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: false, + }, + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_skip_count_is_true() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + }); + + let { cleanup } = await openSerpInNewTab(SERP_URL); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + adImpressions: [], + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js index ce18f64e9f..246caf6f47 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_ad_values.js @@ -30,7 +30,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js index d01141d826..b8dd85da97 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_download_timer.js @@ -33,7 +33,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, @@ -119,6 +119,7 @@ add_task(async function test_download_after_failure() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); @@ -173,6 +174,7 @@ add_task(async function test_download_after_multiple_failures() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); @@ -220,6 +222,7 @@ add_task(async function test_cancel_download_timer() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); @@ -277,6 +280,7 @@ add_task(async function test_download_adjust() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await db.importChanges({}, Date.now()); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js index 03ddb75481..e653be6c48 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_extraction.js @@ -11,6 +11,10 @@ ChromeUtils.defineESModuleGetters(this, { SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", }); +// The search provider's name is provided to ensure we can extract domains +// from relative links, e.g. /url?=https://www.foobar.com +const SEARCH_PROVIDER_NAME = "example"; + const TESTS = [ { title: "Extract domain from href (absolute URL) - one link.", @@ -35,7 +39,7 @@ const TESTS = [ expectedDomains: ["foo.com", "bar.com", "baz.com", "qux.com"], }, { - title: "Extract domain from href (relative URL).", + title: "Extract domain from href (relative URL / URL matching provider)", extractorInfos: [ { selectors: @@ -43,38 +47,33 @@ const TESTS = [ method: "href", }, ], - expectedDomains: ["example.org"], + expectedDomains: [], }, { title: "Extract domain from data attribute - one link.", extractorInfos: [ { selectors: "#test4 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, }, ], - expectedDomains: ["www.abc.com"], + expectedDomains: ["abc.com"], }, { title: "Extract domain from data attribute - multiple links.", extractorInfos: [ { selectors: "#test5 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, }, ], - expectedDomains: [ - "www.foo.com", - "www.bar.com", - "www.baz.com", - "www.qux.com", - ], + expectedDomains: ["foo.com", "bar.com", "baz.com", "qux.com"], }, { title: "Extract domain from an href's query param value.", @@ -88,7 +87,7 @@ const TESTS = [ }, }, ], - expectedDomains: ["def.com"], + expectedDomains: ["def.com", "bar.com", "baz.com"], }, { title: @@ -144,7 +143,7 @@ const TESTS = [ }, { selectors: "#test10 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, @@ -158,7 +157,7 @@ const TESTS = [ }, }, ], - expectedDomains: ["foobar.com", "www.abc.com", "def.com"], + expectedDomains: ["foobar.com", "abc.com", "def.com"], }, { title: "No elements match the selectors.", @@ -176,7 +175,7 @@ const TESTS = [ extractorInfos: [ { selectors: "#test12 [data-dtld]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "dtld", }, @@ -208,6 +207,161 @@ const TESTS = [ ], expectedDomains: [], }, + { + title: "Second-level domains to a top-level domain.", + extractorInfos: [ + { + selectors: "#test15 a", + method: "href", + }, + ], + expectedDomains: [ + "foobar.gc.ca", + "foobar.gov.uk", + "foobar.co.uk", + "foobar.co.il", + ], + }, + { + title: "URL with a long subdomain.", + extractorInfos: [ + { + selectors: "#test16 a", + method: "href", + }, + ], + expectedDomains: ["foobar.com"], + }, + { + title: "URLs with the same top level domain.", + extractorInfos: [ + { + selectors: "#test17 a", + method: "href", + }, + ], + expectedDomains: ["foobar.com"], + }, + { + title: "Maximum domains extracted from a single selector.", + extractorInfos: [ + { + selectors: "#test18 a", + method: "href", + }, + ], + expectedDomains: [ + "foobar1.com", + "foobar2.com", + "foobar3.com", + "foobar4.com", + "foobar5.com", + "foobar6.com", + "foobar7.com", + "foobar8.com", + "foobar9.com", + "foobar10.com", + ], + }, + { + // This is just in case we use multiple selectors meant for separate SERPs + // and the provider switches to re-using their markup. + title: "Maximum domains extracted from multiple matching selectors.", + extractorInfos: [ + { + selectors: "#test19 a.foo", + method: "href", + }, + { + selectors: "#test19 a.baz", + method: "href", + }, + ], + expectedDomains: [ + "foobar1.com", + "foobar2.com", + "foobar3.com", + "foobar4.com", + "foobar5.com", + "foobar6.com", + "foobar7.com", + "foobar8.com", + "foobar9.com", + // This is from the second selector. + "foobaz1.com", + ], + }, + { + title: "Bing organic result.", + extractorInfos: [ + { + selectors: "#test20 #b_results .b_algo .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: ["organic.com"], + }, + { + title: "Bing sponsored result.", + extractorInfos: [ + { + selectors: "#test21 #b_results .b_ad .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: ["sponsored.com"], + }, + { + title: "Bing carousel result.", + extractorInfos: [ + { + selectors: "#test22 .adsMvCarousel cite", + method: "textContent", + }, + ], + expectedDomains: ["fixedupfromthecarousel.com"], + }, + { + title: "Bing sidebar result.", + extractorInfos: [ + { + selectors: "#test23 aside cite", + method: "textContent", + }, + ], + expectedDomains: ["fixedupfromthesidebar.com"], + }, + { + title: "Extraction threshold respected using text content method.", + extractorInfos: [ + { + selectors: "#test24 #b_results .b_ad .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: [ + "sponsored1.com", + "sponsored2.com", + "sponsored3.com", + "sponsored4.com", + "sponsored5.com", + "sponsored6.com", + "sponsored7.com", + "sponsored8.com", + "sponsored9.com", + "sponsored10.com", + ], + }, + { + title: "Bing organic result with no protocol.", + extractorInfos: [ + { + selectors: "#test25 #b_results .b_algo .b_attribution cite", + method: "textContent", + }, + ], + expectedDomains: ["organic.com"], + }, ]; add_setup(async function () { @@ -240,14 +394,15 @@ add_task(async function test_domain_extraction_heuristics() { let expectedDomains = new Set(currentTest.expectedDomains); let actualDomains = await SpecialPowers.spawn( gBrowser.selectedBrowser, - [currentTest.extractorInfos], - extractorInfos => { + [currentTest.extractorInfos, SEARCH_PROVIDER_NAME], + (extractorInfos, searchProviderName) => { const { domainExtractor } = ChromeUtils.importESModule( "resource:///actors/SearchSERPTelemetryChild.sys.mjs" ); return domainExtractor.extractDomainsFromDocument( content.document, - extractorInfos + extractorInfos, + searchProviderName ); } ); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js index f328bb4f79..4c47b0b14a 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_region.js @@ -30,7 +30,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js index b7edb8763f..973f17b760 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting.js @@ -30,7 +30,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js index cfb8590960..9d3ac2c931 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer.js @@ -31,7 +31,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js index cb95164221..c73e224eae 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_domain_categorization_reporting_timer_wakeup.js @@ -32,7 +32,7 @@ const TEST_PROVIDER_INFO = [ ads: [ { selectors: "[data-ad-domain]", - method: "data-attribute", + method: "dataAttribute", options: { dataAttributeKey: "adDomain", }, diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js index a7ea62ebd5..f94e6b0bd8 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_content.js @@ -138,8 +138,8 @@ add_task(async function test_click_tab() { { impression: { provider: "example", - tagged: "false", - partner_code: "", + tagged: "true", + partner_code: "ff", source: "unknown", is_shopping_page: "false", is_private: "false", @@ -217,8 +217,8 @@ add_task(async function test_click_shopping() { { impression: { provider: "example", - tagged: "false", - partner_code: "", + tagged: "true", + partner_code: "ff", source: "unknown", is_shopping_page: "true", is_private: "false", diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_children.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_children.js new file mode 100644 index 0000000000..4f5aaf9378 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_children.js @@ -0,0 +1,480 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests eventListeners property on children elements in topDown searches. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SELECTOR = ".arrow"; +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +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; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +add_task(async function test_listeners_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_no_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_listener() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [{ eventType: "click" }], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: ".arrow-next", + tab, + }); + await synthesizePageAction({ + selector: ".arrow-prev", + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +/** + * The click event is by far our most used event so by default, we translate + * a "click" eventType to a "clicked" action. If no action is provided for + * another type of event, nothing should be reported. + */ +add_task(async function test_event_with_no_default_action() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [{ eventType: "mousedown" }], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_event_no_default_action_with_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "mousedown", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [{ eventType: "click", target: "custom_target" }], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_and_action_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + action: "custom_action", + target: "custom_target", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "custom_action", + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_multiple_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: " .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + }, + { + eventType: "mouseover", + action: "mouseovered", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + event: { + type: "mouseover", + }, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: "mouseovered", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: ".arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "keydownEnter", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + let pageActionPromise = waitForPageWithAction(); + await EventUtils.synthesizeKey("KEY_Enter"); + await pageActionPromise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "keydowned", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition_invalid() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons", + skipCount: true, + }, + children: [ + { + selector: ".arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "noConditionExistsWithThisName", + }, + ], + }, + ], + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + await EventUtils.synthesizeKey("KEY_Enter"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_parent.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_parent.js new file mode 100644 index 0000000000..4e3c635b4c --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_eventListeners_parent.js @@ -0,0 +1,434 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests eventListeners property on parent elements in topDown searches. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + }, +]; + +// The impression doesn't change in these tests. +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SELECTOR = ".arrow"; +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +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; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +add_task(async function test_listeners_not_provided() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_no_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_listener() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: ".arrow-next", + tab, + }); + await synthesizePageAction({ + selector: ".arrow-prev", + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +/** + * The click event is by far our most used event so by default, we translate + * a "click" eventType to a "clicked" action. If no action is provided for + * another type of event, nothing should be reported. + */ +add_task(async function test_event_with_no_default_action_parent() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "mousedown", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + expectEngagement: false, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_event_no_default_action_with_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "mousedown", + action: "clicked", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + target: "custom_target", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_target_and_action_override() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + target: "custom_target", + action: "custom_action", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "custom_action", + target: "custom_target", + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_multiple_listeners() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "click", + action: "clicked", + }, + { + eventType: "mouseover", + action: "mouseovered", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await synthesizePageAction({ + selector: SELECTOR, + tab, + }); + await synthesizePageAction({ + selector: SELECTOR, + tab, + event: { + type: "mouseover", + }, + }); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + { + action: "mouseovered", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "keydownEnter", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + let pageActionPromise = waitForPageWithAction(); + await EventUtils.synthesizeKey("KEY_Enter"); + await pageActionPromise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "keydowned", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + ]); + + await cleanup(); +}); + +add_task(async function test_condition_invalid() { + await replaceIncludedProperty({ + parent: { + selector: ".refined-search-buttons .arrow", + skipCount: true, + eventListeners: [ + { + eventType: "keydown", + action: "keydowned", + condition: "noConditionExistsWithThisName", + }, + ], + }, + }); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + await SpecialPowers.spawn(tab.linkedBrowser, [SELECTOR], async function (s) { + let el = content.document.querySelector(s); + el.focus(); + }); + + await EventUtils.synthesizeKey("A"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + await EventUtils.synthesizeKey("KEY_Enter"); + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + await new Promise(resolve => setTimeout(resolve, 10)); + + assertSERPTelemetry([ + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_ignoreLinkRegexps.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_ignoreLinkRegexps.js new file mode 100644 index 0000000000..10f2a2d836 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_ignoreLinkRegexps.js @@ -0,0 +1,223 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests ignoreLinkRegexps property in search telemetry that explicitly results + * in our network code ignoring the link. The main reason for doing so is for + * rare situations where we need to find a components from a topDown approach + * but it loads a page in the network process. + */ + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + ignoreLinkRegexps: [ + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_searchbox_with_content.html\?s=test&page=images/, + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_searchbox_with_content.html\?s=test&page=shopping/, + ], + components: [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ], + hrefToComponentMapAugmentation: [ + { + action: "clicked_something", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + url: "https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox.html", + }, + ], + }, +]; + +// The impression doesn't change in these tests. +const IMPRESSION = { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", +}; + +const SERP_URL = getSERPUrl("searchTelemetryAd_searchbox_with_content.html"); + +async function replaceIncludedProperty(included) { + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + included, + topDown: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +} + +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; + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + Services.telemetry.canRecordExtended = oldCanRecord; + resetTelemetry(); + }); +}); + +add_task(async function test_click_link_1_matching_ignore_link_regexps() { + resetTelemetry(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#images", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + abandonment: { + reason: SearchSERPTelemetryUtils.ABANDONMENTS.NAVIGATION, + }, + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_link_2_matching_ignore_link_regexps() { + resetTelemetry(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#shopping", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + abandonment: { + reason: SearchSERPTelemetryUtils.ABANDONMENTS.NAVIGATION, + }, + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_link_3_not_matching_ignore_link_regexps() { + resetTelemetry(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#extra", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.NON_ADS_LINK, + }, + ], + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); + +add_task(async function test_click_listener_with_ignore_link_regexps() { + resetTelemetry(); + + TEST_PROVIDER_INFO[0].components = [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + topDown: true, + included: { + parent: { + selector: "nav a", + skipCount: true, + eventListeners: [ + { + eventType: "click", + action: "clicked", + }, + ], + }, + }, + }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ]; + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + + let { tab, cleanup } = await openSerpInNewTab(SERP_URL); + + let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#images", + {}, + tab.linkedBrowser + ); + await promise; + + assertSERPTelemetry([ + { + impression: IMPRESSION, + engagements: [ + { + action: "clicked", + target: SearchSERPTelemetryUtils.COMPONENTS.REFINED_SEARCH_BUTTONS, + }, + ], + }, + { + impression: IMPRESSION, + }, + ]); + + await cleanup(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js new file mode 100644 index 0000000000..93a6b7993e --- /dev/null +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_nonAdsLinkQueryParamNames.js @@ -0,0 +1,252 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * These tests load SERPs and click on links. + */ + +"use strict"; + +const TEST_PROVIDER_INFO = [ + { + telemetryId: "example", + searchPageRegexp: + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetry/, + queryParamNames: ["s"], + codeParamName: "abc", + taggedCodes: ["ff"], + adServerAttributes: ["mozAttr"], + nonAdsLinkRegexps: [ + /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetry_redirect_with_js/, + ], + nonAdsLinkQueryParamNames: ["url"], + extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + shoppingTab: { + regexp: "&page=shopping", + selector: "nav a", + inspectRegexpInSERP: true, + }, + components: [ + { + type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, + default: true, + }, + ], + }, +]; + +add_setup(async function () { + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); + + registerCleanupFunction(async () => { + SearchSERPTelemetry.overrideSearchTelemetryForTests(); + resetTelemetry(); + }); +}); + +add_task(async function test_click_absolute_url_in_query_param() { + resetTelemetry(); + + let url = getSERPUrl( + "searchTelemetryAd_searchbox_with_redirecting_links.html" + ); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#shopping-redirect-absolute-link", + {}, + tab.linkedBrowser + ); + await browserLoadedPromise; + await waitForPageWithAdImpressions(); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "true", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "true", + is_private: "false", + shopping_tab_displayed: "true", + }, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); + +add_task(async function test_click_relative_href_in_query_param() { + resetTelemetry(); + + let url = getSERPUrl( + "searchTelemetryAd_searchbox_with_redirecting_links.html" + ); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#shopping-redirect-relative-link", + {}, + tab.linkedBrowser + ); + await browserLoadedPromise; + await waitForPageWithAdImpressions(); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "true", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "true", + is_private: "false", + shopping_tab_displayed: "true", + }, + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); + +add_task(async function test_click_irrelevant_href_in_query_param() { + resetTelemetry(); + + let url = getSERPUrl( + "searchTelemetryAd_searchbox_with_redirecting_links.html" + ); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + let browserLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true, + "https://example.org/foo/bar" + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#organic-redirect", + {}, + tab.linkedBrowser + ); + await browserLoadedPromise; + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "true", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, + target: SearchSERPTelemetryUtils.COMPONENTS.NON_ADS_LINK, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.SHOPPING_TAB, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); + + // Reset state for other tests. + SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); + await waitForIdle(); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js index b30a7bc0c1..8f7f7f4e05 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_engagement_target.js @@ -22,6 +22,7 @@ const TEST_PROVIDER_INFO = [ /^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_nonAdsLink_redirect.html/, ], extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], + ignoreLinkRegexps: [/^https:\/\/example\.org\/consent\?data=/], components: [ { type: SearchSERPTelemetryUtils.COMPONENTS.AD_CAROUSEL, @@ -90,6 +91,44 @@ const TEST_PROVIDER_INFO = [ type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK, default: true, }, + { + type: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + topDown: true, + included: { + parent: { + selector: "#banner", + }, + children: [ + { + selector: "#cookie_accept", + eventListeners: [ + { + eventType: "click", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_ACCEPT, + }, + ], + }, + { + selector: "#cookie_reject", + eventListeners: [ + { + eventType: "click", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_REJECT, + }, + ], + }, + { + selector: "#cookie_more_options", + eventListeners: [ + { + eventType: "click", + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_MORE_OPTIONS, + }, + ], + }, + ], + }, + }, ], }, ]; @@ -455,3 +494,138 @@ add_task(async function test_click_link_with_special_characters_in_path() { SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO); await waitForIdle(); }); + +// Test that clicking the accept button on the cookie banner is correctly +// tracked as an engagement event. +add_task(async function test_click_cookie_banner_accept() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#cookie_accept", + {}, + tab.linkedBrowser + ); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_ACCEPT, + target: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Test that clicking the reject button on the cookie banner is accurately +// recorded as an engagement event. +add_task(async function test_click_cookie_banner_reject() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#cookie_reject", + {}, + tab.linkedBrowser + ); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_REJECT, + target: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); + +// Test that clicking the more options button on the cookie banner is accurately +// recorded as an engagement event. +add_task(async function test_click_cookie_banner_more_options() { + resetTelemetry(); + let url = getSERPUrl("searchTelemetryAd_components_cookie_banner.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await waitForPageWithAdImpressions(); + + await BrowserTestUtils.synthesizeMouseAtCenter( + "#cookie_more_options", + {}, + tab.linkedBrowser + ); + + assertSERPTelemetry([ + { + impression: { + provider: "example", + tagged: "true", + partner_code: "ff", + source: "unknown", + is_shopping_page: "false", + is_private: "false", + shopping_tab_displayed: "false", + }, + engagements: [ + { + action: SearchSERPTelemetryUtils.ACTIONS.CLICKED_MORE_OPTIONS, + target: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + }, + ], + adImpressions: [ + { + component: SearchSERPTelemetryUtils.COMPONENTS.COOKIE_BANNER, + ads_loaded: "1", + ads_visible: "1", + ads_hidden: "0", + }, + ], + }, + ]); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js b/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js index f7b22f004b..cb9e123622 100644 --- a/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js +++ b/browser/components/search/test/browser/telemetry/browser_search_telemetry_sources_webextension.js @@ -152,7 +152,7 @@ async function track_ad_click( add_task(async function test_source_webextension_search() { /* global browser */ - async function background(SEARCH_TERM) { + async function background() { // Search with no tabId browser.search.search({ query: "searchSuggestion", engine: "Example" }); } @@ -184,7 +184,7 @@ add_task(async function test_source_webextension_search() { }); add_task(async function test_source_webextension_query() { - async function background(SEARCH_TERM) { + async function background() { // Search with no tabId browser.search.query({ text: "searchSuggestion", diff --git a/browser/components/search/test/browser/telemetry/domain_category_mappings.json b/browser/components/search/test/browser/telemetry/domain_category_mappings.json deleted file mode 100644 index 2f8d0d2af2..0000000000 --- a/browser/components/search/test/browser/telemetry/domain_category_mappings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "DqNorjpE3CBY9OZh0wf1uA==": [2, 90], - "kpuib0kvhtSp1moICEmGWg==": [2, 95], - "+5WbbjV3Nmxp0mBZODcJWg==": [2, 78, 4, 10], - "OIHlWZ/yMyTHHuY78AV9VQ==": [3, 90], - "r1hDZinn+oNrQjabn8IB9w==": [4, 90], - "AtlIam7nqWvzFzTGkYI01w==": [4, 90] -} diff --git a/browser/components/search/test/browser/telemetry/head.js b/browser/components/search/test/browser/telemetry/head.js index 416451e400..b798099bdd 100644 --- a/browser/components/search/test/browser/telemetry/head.js +++ b/browser/components/search/test/browser/telemetry/head.js @@ -45,6 +45,10 @@ ChromeUtils.defineLazyGetter(this, "SEARCH_AD_CLICK_SCALARS", () => { ]; }); +ChromeUtils.defineLazyGetter(this, "gCryptoHash", () => { + return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); +}); + // For use with categorization. const APP_MAJOR_VERSION = parseInt(Services.appinfo.version).toString(); const CHANNEL = SearchUtils.MODIFIED_APP_CHANNEL; @@ -207,6 +211,11 @@ function resetTelemetry() { * values we use to validate the recorded Glean impression events. */ function assertSERPTelemetry(expectedEvents) { + // Do a deep copy of impressions in case the input is using constants, as + // we insert impression id into the expected events to make it easier to + // run Assert.deepEqual() on the expected and actual result. + expectedEvents = JSON.parse(JSON.stringify(expectedEvents)); + // A single test might run assertImpressionEvents more than once // so the Set needs to be cleared or else the impression event // check will throw. @@ -385,6 +394,46 @@ add_setup(function () { }); }); +async function openSerpInNewTab(url, expectedAds = true) { + let promise; + if (expectedAds) { + promise = waitForPageWithAdImpressions(); + } + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await promise; + + let cleanup = async () => { + await BrowserTestUtils.removeTab(tab); + resetTelemetry(); + }; + + return { tab, cleanup }; +} + +async function synthesizePageAction({ + selector, + event = {}, + tab, + expectEngagement = true, +} = {}) { + let promise; + if (expectEngagement) { + promise = waitForPageWithAction(); + } else { + // Wait roughly around how much it might take for a possible page action + // to be registered in telemetry. + /* eslint-disable-next-line mozilla/no-arbitrary-setTimeout */ + promise = new Promise(resolve => setTimeout(resolve, 50)); + } + await BrowserTestUtils.synthesizeMouseAtCenter( + selector, + event, + tab.linkedBrowser + ); + + await promise; +} + function assertCategorizationValues(expectedResults) { // TODO Bug 1868476: Replace with calls to Glean telemetry. let actualResults = [...fakeTelemetryStorage]; @@ -435,6 +484,10 @@ function assertCategorizationValues(expectedResults) { } } +function waitForPageWithAction() { + return TestUtils.topicObserved("reported-page-with-action"); +} + function waitForPageWithAdImpressions() { return TestUtils.topicObserved("reported-page-with-ad-impressions"); } @@ -459,10 +512,9 @@ registerCleanupFunction(async () => { await PlacesUtils.history.clear(); }); -async function mockRecordWithAttachment({ id, version, filename }) { +async function mockRecordWithAttachment({ id, version, filename, mapping }) { // Get the bytes of the file for the hash and size for attachment metadata. - let data = await IOUtils.readUTF8(getTestFilePath(filename)); - let buffer = new TextEncoder().encode(data).buffer; + let buffer = new TextEncoder().encode(JSON.stringify(mapping)).buffer; let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance( Ci.nsIArrayBufferInputStream ); @@ -506,6 +558,30 @@ async function resetCategorizationCollection(record) { await client.db.importChanges({}, Date.now()); } +const MOCK_ATTACHMENT_VALUES = { + "abc.com": [2, 95], + "abc.org": [4, 90], + "def.com": [2, 78, 4, 10], + "def.org": [4, 90], + "foobar.org": [3, 90], +}; + +const CONVERTED_ATTACHMENT_VALUES = convertDomainsToHashes( + MOCK_ATTACHMENT_VALUES +); + +function convertDomainsToHashes(domainsToCategories) { + let newObj = {}; + for (let [key, value] of Object.entries(domainsToCategories)) { + gCryptoHash.init(gCryptoHash.SHA256); + let bytes = new TextEncoder().encode(key); + gCryptoHash.update(bytes, key.length); + let hash = gCryptoHash.finish(true); + newObj[hash] = value; + } + return newObj; +} + async function insertRecordIntoCollection() { const client = RemoteSettings(TELEMETRY_CATEGORIZATION_KEY); const db = client.db; @@ -515,6 +591,7 @@ async function insertRecordIntoCollection() { id: "example_id", version: 1, filename: "domain_category_mappings.json", + mapping: CONVERTED_ATTACHMENT_VALUES, }); await db.create(record); await client.attachments.cacheImpl.set(record.id, attachment); diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryAd_components_cookie_banner.html b/browser/components/search/test/browser/telemetry/searchTelemetryAd_components_cookie_banner.html new file mode 100644 index 0000000000..e33afb2672 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/searchTelemetryAd_components_cookie_banner.html @@ -0,0 +1,16 @@ +<html> +<head> + <title>A top-level page with cookie banner</title> +</head> +<body> + <h1>This is the top-level page</h1> + <div id="banner"> + <button id="cookie_accept">Accept</button> + <button id="cookie_reject">Reject</button> + <button id="cookie_more_options" + onclick="location.href='https:example.org/consent?data='"> + More Options + </button> + </div> +</body> +</html> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html index 9c4d371691..d23255984f 100644 --- a/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html +++ b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html @@ -11,13 +11,16 @@ </form> </section> <nav> - <a id="images" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=images">Images</a> - <a id="shopping" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=shopping">Shopping</a> - <a id="extra" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox.html?s=test">Extra Page</a> + <a id="images" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=images&abc=ff">Images</a> + <a id="shopping" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test&page=shopping&abc=ff">Shopping</a> + <a id="extra" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox.html?s=test&abc=ff">Extra Page</a> </nav> <section class="refined-search-buttons"> + <button class="arrow arrow-prev">← Prev</button> <a id="refined-search-button" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test's">Test's</a> <a id="refined-search-button-with-partner-code" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_content.html?s=test2&abc=ff">Test 2</a> + <a href="javascript:void(0)">Element relying on Javascript</a> + <button class="arrow arrow-next">Next →</button> </section> <section id="searchresults"> <div class="lhs"> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_redirecting_links.html b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_redirecting_links.html new file mode 100644 index 0000000000..2b09409126 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/searchTelemetryAd_searchbox_with_redirecting_links.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" href="./serp.css" /> +</head> +<body> + <nav> + <a href="/">All Results</a> + <a id="shopping-redirect-relative-link" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_shopping.html?s=test&page=shopping&abc=ff">Shopping Relative</a> + <a id="shopping-redirect-absolute-link" href="https://example.org/browser/browser/components/search/test/browser/telemetry/searchTelemetryAd_shopping.html?s=test&page=shopping&abc=ff">Shopping Absolute</a> + </nav> + <section> + <form role="search"> + <input type="text" value="test" /> + </form> + </section> + <section id="searchresults"> + <a id="organic-redirect" href="https://example.org/foo/bar">Organic Redirect Result</a> + </section> +</body> +<script type="text/javascript"> + const ORIGIN = "https://example.org"; + const PATH = "/browser/browser/components/search/test/browser/telemetry/" + const REDIRECT_URL = `${ORIGIN + PATH}searchTelemetry_redirect_with_js.html`; + const SHOPPING_PAGE = "searchTelemetryAd_shopping.html?s=test&page=shopping&abc=ff"; + document.getElementById("shopping-redirect-relative-link").addEventListener("click", event => { + event.preventDefault(); + window.location.href = `${REDIRECT_URL}?url=${encodeURIComponent(PATH + SHOPPING_PAGE)}`; + }); + document.getElementById("shopping-redirect-absolute-link").addEventListener("click", event => { + event.preventDefault(); + window.location.href = `${REDIRECT_URL}?url=${encodeURIComponent(ORIGIN + PATH + SHOPPING_PAGE)}`; + }); + document.getElementById("organic-redirect").addEventListener("click", event => { + event.preventDefault(); + window.location.href = `${REDIRECT_URL}?url=${encodeURIComponent("https://example.org/foo/bar")}`; + }); +</script> +</html> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html b/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html index b49e5610ae..28c31af959 100644 --- a/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html +++ b/browser/components/search/test/browser/telemetry/searchTelemetryDomainExtraction.html @@ -25,6 +25,8 @@ <div id="test3"> <div data-layout="organic"> <a href="/dummy-page" data-testid="result-title-a">Extract domain from href (relative URL).</a> + <a href="https://example.org/dummy-page" data-testid="result-title-a">Extract domain from href.</a> + <a href="https://www.example.org/dummy-page" data-testid="result-title-a">Extract domain from href.</a> </div> </div> @@ -40,7 +42,9 @@ </div> <div id="test6"> - <a href="example.com/testing?ad_domain=def.com" class="js-carousel-item-title">Extract domain from an href's query param value.</a> + <a href="https://www.example.org/testing?ad_domain=def.com" class="js-carousel-item-title">Extract domain from an href's query param value.</a> + <a href="https://example.org/testing?ad_domain=bar.com" class="js-carousel-item-title">Extract domain from an href's query param value.</a> + <a href="/testing?ad_domain=baz.com" class="js-carousel-item-title">Extract domain from a relative href containing a relevant query param value.</a> </div> <div id="test7"> @@ -79,6 +83,179 @@ <div id="test14"> <a href="git://testing.com/testrepo">Non-standard URL scheme.</a> </div> + + <div id="test15"> + <h5>Second-level domains to a top-level domain.</h5> + <a href="https://www.foobar.gc.ca/">Link</a> + <a href="https://www.foobar.gov.uk/">Link</a> + <a href="https://foobar.co.uk">Link</a> + <a href="https://www.foobar.co.il">Link</a> + </div> + + <div id="test16"> + <a href="https://ab.cd.ef.gh.foobar.com/">URL with a long subdomain</a> + </div> + + <div id="test17"> + <h5>URL with the same top level domain.</h5> + <a href="https://foobar.com/">Link</a> + <a href="https://www.foobar.com/">Link</a> + <a href="https://abc.def.foobar.com/">Link</a> + </div> + + <div id="test18"> + <h5>More than the threshold of links.</h5> + <a href="https://foobar1.com/">Link</a> + <a href="https://foobar1.com/">Duplicate Link</a> + <a href="https://foobar2.com/">Link</a> + <a href="https://foobar3.com/">Link</a> + <a href="https://foobar4.com/">Link</a> + <a href="https://foobar5.com/">Link</a> + <a href="https://foobar6.com/">Link</a> + <a href="https://foobar7.com/">Link</a> + <a href="https://foobar8.com/">Link</a> + <a href="https://foobar9.com/">Link</a> + <a href="https://foobar10.com/">Link</a> + <a href="https://foobar11.com/">Link Outside Threshold</a> + </div> + + <div id="test19"> + <h5>More than the threshold of links using multiple matching selectors.</h5> + <a class="foo" href="https://foobar1.com/">Link</a> + <a class="foo" href="https://foobar2.com/">Link</a> + <a class="foo" href="https://foobar3.com/">Link</a> + <a class="foo" href="https://foobar4.com/">Link</a> + <a class="foo" href="https://foobar5.com/">Link</a> + <a class="foo" href="https://foobar6.com/">Link</a> + <a class="foo" href="https://foobar7.com/">Link</a> + <a class="foo" href="https://foobar8.com/">Link</a> + <a class="foo" href="https://foobar9.com/">Link</a> + <a class="baz" href="https://foobaz1.com/">Link</a> + <a class="baz" href="https://foobaz2.com/">Link Outside Threshold</a> + </div> + + <div id="test20"> + <div id="b_results"> + <div class="b_algo"> + <div class="b_attribution"> + <cite>https://organic.com</cite> + </div> + </div> + </div> + </div> + + <div id="test21"> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored.com</cite> + </div> + </div> + </div> + </div> + + <div id="test22"> + <div class="adsMvCarousel"> + <cite>Fixed up from the carousel</cite> + </div> + </div> + + <div id="test23"> + <aside> + <cite>Fixed up from the sidebar</cite> + </aside> + </div> + + <div id="test24"> + <h5>More than the threshold of links using the text content selection method.</h5> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored1.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored2.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored3.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored4.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored5.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored6.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored7.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored8.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored9.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored10.com</cite> + </div> + </div> + </div> + <div id="b_results"> + <div class="b_ad"> + <div class="b_attribution"> + <cite>https://sponsored11.com</cite> + </div> + </div> + </div> + </div> + + <div id="test25"> + <div id="b_results"> + <div class="b_algo"> + <div class="b_attribution"> + <cite>organic.com</cite> + </div> + </div> + </div> + </div> </div> </body> </html> diff --git a/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html b/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html index 7598da694e..f52088206f 100644 --- a/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html +++ b/browser/components/search/test/browser/telemetry/searchTelemetrySinglePageApp.html @@ -204,7 +204,7 @@ } }) - window.addEventListener("DOMContentLoaded", (event) => { + window.addEventListener("DOMContentLoaded", () => { let url = new URL(window.location.href); searchKey = url.searchParams.has("r") ? "r": "s"; @@ -219,7 +219,7 @@ updateSuggestions(); }); - window.addEventListener("popstate", (event) => { + window.addEventListener("popstate", () => { let baseUrl = new URL(window.location.href); let page = baseUrl.searchParams.get("page"); switch (page) { diff --git a/browser/components/search/test/browser/telemetry/searchTelemetry_redirect_with_js.html b/browser/components/search/test/browser/telemetry/searchTelemetry_redirect_with_js.html new file mode 100644 index 0000000000..744fcd2906 --- /dev/null +++ b/browser/components/search/test/browser/telemetry/searchTelemetry_redirect_with_js.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Page will redirect using the url param</title> + <script> + let parentWindow = window.parent; + let params = new URLSearchParams(window.location.search); + let paramValue = params.get("url"); + if (paramValue) { + // Replicate how some SERPs load pages by encoding the true destination + // in the query param value. + let url = paramValue.startsWith("https://") ? + new URL(paramValue).href : + new URL(paramValue, "https://example.org/").href; + window.location.href = url; + } + </script> + </head> + <body> + <h1>Redirecting...</h1> + </body> +</html> |