/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ /** * This test ensures that recorded telemetry is consistent even with multiple * tabs opened and closed. */ "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/, ], extraAdServersRegexps: [/^https:\/\/example\.com\/ad/], components: [ { 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, }, ], }, ]; // Deliberately make the web isolated process count as small as possible // so that we don't have to create a ton of tabs to reuse a process. const MAX_IPC = 1; const TABS_TO_OPEN = 2; function getSERPUrl(page, term) { let url = getRootDirectory(gTestPath).replace( "chrome://mochitests/content", "https://example.org" ) + page; return `${url}?s=${term}`; } 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(); await SpecialPowers.pushPrefEnv({ set: [ ["browser.search.log", true], ["browser.search.serpEventTelemetry.enabled", true], ["dom.ipc.processCount.webIsolated", MAX_IPC], ], }); registerCleanupFunction(async () => { SearchSERPTelemetry.overrideSearchTelemetryForTests(); resetTelemetry(); }); }); async function do_test(tab, impressionId, switchTab) { if (switchTab) { await BrowserTestUtils.switchTab(gBrowser, tab); } await BrowserTestUtils.synthesizeMouseAtCenter( "input", {}, tab.linkedBrowser ); await BrowserTestUtils.synthesizeMouseAtCenter( "a#images", {}, tab.linkedBrowser ); await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true); await Services.fog.testFlushAllChildren(); let engagements = Glean.serp.engagement.testGetValue() ?? []; Assert.equal(engagements.length, 2, "Should have two events recorded."); Assert.deepEqual( engagements[0].extra, { action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, target: SearchSERPTelemetryUtils.COMPONENTS.INCONTENT_SEARCHBOX, impression_id: impressionId, }, "Search box engagement event should match." ); Assert.deepEqual( engagements[1].extra, { action: SearchSERPTelemetryUtils.ACTIONS.CLICKED, target: SearchSERPTelemetryUtils.COMPONENTS.NON_ADS_LINK, impression_id: impressionId, }, "Non ads page engagement event should match." ); resetTelemetry(); } // This test deliberately opens a lot of tabs to ensure SERPs share the // same process. It interacts with the page to ensure the engagement // has the correct recording, especially the impression id which can be out of // sync if data in the child process isn't cached properly. add_task(async function test_multiple_tabs_forward() { resetTelemetry(); let tabs = []; let pid; // Open multiple tabs. for (let index = 0; index < TABS_TO_OPEN; ++index) { let url = getSERPUrl( "searchTelemetryAd_searchbox_with_content.html", `hello+world+${index}` ); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); await waitForPageWithAdImpressions(); tabs.push(tab); let currentPid = E10SUtils.getBrowserPids(tab.linkedBrowser).at(0); if (pid == null) { pid = currentPid; } else { Assert.ok(pid == currentPid, "The process ID should be the same."); } } // Extract the impression IDs. await Services.fog.testFlushAllChildren(); let recordedImpressions = Glean.serp.impression.testGetValue() ?? []; let impressionIds = recordedImpressions.map( impression => impression.extra.impression_id ); // Reset telemetry because we're not concerned about inspecting every // impression event. resetTelemetry(); for (let index = 0; index < TABS_TO_OPEN; ++index) { let tab = tabs[index]; let impressionId = impressionIds[index]; await do_test(tab, impressionId, true); BrowserTestUtils.removeTab(tab); } }); add_task(async function test_multiple_tabs_backward() { resetTelemetry(); let tabs = []; let pid; for (let index = 0; index < TABS_TO_OPEN; ++index) { let url = getSERPUrl( "searchTelemetryAd_searchbox_with_content.html", `hello+world+${index}` ); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); await waitForPageWithAdImpressions(); tabs.push(tab); let currentPid = E10SUtils.getBrowserPids(tab.linkedBrowser).at(0); if (pid == null) { pid = currentPid; } else { Assert.ok(pid == currentPid, "The process ID should be the same."); } } // Extract the impression IDs. await Services.fog.testFlushAllChildren(); let recordedImpressions = Glean.serp.impression.testGetValue() ?? []; let impressionIds = recordedImpressions.map( impression => impression.extra.impression_id ); // Reset telemetry because we're not concerned about inspecting every // impression event. resetTelemetry(); for (let index = TABS_TO_OPEN - 1; index >= 0; --index) { let tab = tabs[index]; let impressionId = impressionIds[index]; await do_test(tab, impressionId, false); BrowserTestUtils.removeTab(tab); } });