Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: toolkit/components/search/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
/*
* Tests for the default engine telemetry event that can be tested via xpcshell,
* related to changing or selecting a different configuration.
* Other tests are typically in browser mochitests.
*/
"use strict";
ChromeUtils.defineESModuleGetters(this, {
AppProvidedSearchEngine:
"resource://gre/modules/AppProvidedSearchEngine.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
});
const BASE_CONFIG = [
{
identifier: "originalDefault",
base: {
name: "Original Default",
},
},
},
];
const MAIN_CONFIG = [
{
identifier: "originalDefault",
base: {
name: "Original Default",
},
},
},
{
identifier: "newDefault",
base: {
name: "New Default",
},
},
},
{
identifier: "defaultInLocaleFRNotRegionDE",
base: {
name: "Default in Locale FR and not Region DE",
urls: {
search: {
params: [
{
name: "ie",
value: "iso-8859-1",
},
{
name: "oe",
value: "iso-8859-1",
},
],
searchTermParamName: "q",
},
},
},
},
{
identifier: "defaultInRegionDE",
base: {
name: "Default in Region DE",
urls: {
search: {
params: [
{
name: "code",
experimentConfig: "code",
},
{
name: "test",
experimentConfig: "test",
},
],
searchTermParamName: "q",
},
},
},
},
{
identifier: "defaultForExperiment",
base: {
name: "Default for Experiment",
},
},
},
{
globalDefault: "newDefault",
specificDefaults: [
{
default: "defaultInLocaleFRNotRegionDE",
environment: { excludedRegions: ["DE"], locales: ["fr"] },
},
{
default: "defaultInRegionDE",
environment: { regions: ["DE"] },
},
{
default: "defaultForExperiment",
environment: { experiment: "test1" },
},
],
},
];
const CONFIG_WITH_MODIFIED_CLASSIFICATION = [
{
identifier: "originalDefault",
base: {
name: "Original Default",
},
classification: "unknown",
},
},
];
const CONFIG_WITH_MODIFIED_NAME = [
{
identifier: "originalDefault",
base: {
name: "Modified Engine Name",
},
classification: "general",
},
},
];
const testSearchEngine = {
id: "originalDefault",
name: "Original Default",
loadPath: "[app]originalDefault",
};
const testNewDefaultEngine = {
id: "newDefault",
name: "New Default",
loadPath: "[app]newDefault",
};
const testDefaultInLocaleFRNotRegionDEEngine = {
id: "defaultInLocaleFRNotRegionDE",
name: "Default in Locale FR and not Region DE",
loadPath: "[app]defaultInLocaleFRNotRegionDE",
};
const testPrefEngine = {
id: "defaultInRegionDE",
name: "Default in Region DE",
loadPath: "[app]defaultInRegionDE",
};
const testDefaultForExperiment = {
id: "defaultForExperiment",
name: "Default for Experiment",
loadPath: "[app]defaultForExperiment",
};
function clearTelemetry() {
Services.telemetry.clearEvents();
Services.fog.testResetFOG();
}
async function checkTelemetry(
source,
prevEngine,
newEngine,
checkPrivate = false,
additionalEventsExpected = false
) {
let snapshot;
if (checkPrivate) {
snapshot = await Glean.searchEnginePrivate.changed.testGetValue();
} else {
snapshot = await Glean.searchEngineDefault.changed.testGetValue();
}
// additionalEventsExpected should be true whenever we expect something
// stored in AppProvidedSearchEngine.#prevEngineInfo to have changed.
if (additionalEventsExpected) {
delete snapshot[0].timestamp;
Assert.deepEqual(
snapshot[0],
{
category: checkPrivate
? "search.engine.private"
: "search.engine.default",
name: "changed",
extra: {
change_source: "engine-update",
previous_engine_id: prevEngine?.id ?? "",
new_engine_id: prevEngine?.id ?? "",
new_display_name: prevEngine?.name ?? "",
new_load_path: prevEngine?.loadPath ?? "",
new_submission_url: prevEngine?.submissionURL ?? "",
},
},
"Should have received the correct event details"
);
snapshot.shift();
}
delete snapshot[0].timestamp;
Assert.deepEqual(
snapshot[0],
{
category: checkPrivate
? "search.engine.private"
: "search.engine.default",
name: "changed",
extra: {
change_source: source,
previous_engine_id: prevEngine?.id ?? "",
new_engine_id: newEngine?.id ?? "",
new_display_name: newEngine?.name ?? "",
new_load_path: newEngine?.loadPath ?? "",
new_submission_url: newEngine?.submissionURL ?? "",
},
},
"Should have received the correct event details"
);
}
let getVariableStub;
add_setup(async () => {
Region._setHomeRegion("US", false);
Services.locale.availableLocales = [
...Services.locale.availableLocales,
"en",
"fr",
];
Services.locale.requestedLocales = ["en"];
sinon.spy(NimbusFeatures.searchConfiguration, "onUpdate");
sinon.stub(NimbusFeatures.searchConfiguration, "ready").resolves();
getVariableStub = sinon.stub(
NimbusFeatures.searchConfiguration,
"getVariable"
);
getVariableStub.returns(null);
Services.fog.initializeFOG();
sinon.stub(
Services.search.wrappedJSObject,
"_showRemovalOfSearchEngineNotificationBox"
);
SearchTestUtils.setRemoteSettingsConfig(BASE_CONFIG);
await Services.search.init();
});
add_task(async function test_configuration_changes_default() {
clearTelemetry();
await SearchTestUtils.updateRemoteSettingsConfig(MAIN_CONFIG);
await checkTelemetry(
"config",
testSearchEngine,
testNewDefaultEngine,
false,
true
);
});
add_task(async function test_experiment_changes_default() {
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
getVariableStub.callsFake(name => (name == "experiment" ? "test1" : null));
NimbusFeatures.searchConfiguration.onUpdate.firstCall.args[0]();
await reloadObserved;
await checkTelemetry(
"experiment",
testNewDefaultEngine,
testDefaultForExperiment,
false
);
// Reset the stub so that we are no longer in an experiment.
getVariableStub.returns(null);
});
add_task(async function test_locale_changes_default() {
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
Services.locale.requestedLocales = ["fr"];
await reloadObserved;
await checkTelemetry(
"locale",
testDefaultForExperiment,
testDefaultInLocaleFRNotRegionDEEngine,
false
);
});
add_task(async function test_region_changes_default() {
clearTelemetry();
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
Region._setHomeRegion("DE", true);
await reloadObserved;
await checkTelemetry(
"region",
testDefaultInLocaleFRNotRegionDEEngine,
testPrefEngine,
false
);
});
add_task(async function test_user_changes_separate_private_pref() {
Services.prefs.setBoolPref(
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault.ui.enabled",
true
);
Services.prefs.setBoolPref(
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
true
);
await Services.search.setDefaultPrivate(
Services.search.getEngineById("newDefault"),
Ci.nsISearchService.CHANGE_REASON_UNKNOWN
);
Assert.notEqual(
await Services.search.getDefault(),
await Services.search.getDefaultPrivate(),
"Should have different engines for the pre-condition"
);
Services.prefs.setBoolPref(
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault.ui.enabled",
false
);
clearTelemetry();
Services.prefs.setBoolPref(
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
false
);
await checkTelemetry("user_private_split", testNewDefaultEngine, null, true);
getVariableStub.returns(null);
});
add_task(async function test_experiment_with_separate_default_notifies() {
Services.prefs.setBoolPref(
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault.ui.enabled",
false
);
Services.prefs.setBoolPref(
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
true
);
clearTelemetry();
getVariableStub.callsFake(name =>
name == "seperatePrivateDefaultUIEnabled" ? true : null
);
NimbusFeatures.searchConfiguration.onUpdate.firstCall.args[0]();
await checkTelemetry("experiment", null, testNewDefaultEngine, true);
clearTelemetry();
// Reset the stub so that we are no longer in an experiment.
getVariableStub.returns(null);
NimbusFeatures.searchConfiguration.onUpdate.firstCall.args[0]();
await checkTelemetry("experiment", testNewDefaultEngine, null, true);
});
add_task(async function test_default_engine_update() {
clearTelemetry();
let extension = await SearchTestUtils.installSearchExtension(
{
name: "engine",
id: "engine@tests.mozilla.org",
search_url_get_params: `q={searchTerms}&version=1.0`,
version: "1.0",
},
{ skipUnload: true }
);
let engine = Services.search.getEngineByName("engine");
Assert.ok(!!engine, "Should have loaded the engine");
await Services.search.setDefault(
engine,
Ci.nsISearchService.CHANGE_REASON_UNKNOWN
);
clearTelemetry();
let promiseChanged = TestUtils.topicObserved(
"browser-search-engine-modified",
(eng, verb) => verb == "engine-changed"
);
let manifest = SearchTestUtils.createEngineManifest({
name: "Bar",
id: "engine@tests.mozilla.org",
search_url_get_params: `q={searchTerms}&version=2.0`,
version: "2.0",
});
await extension.upgrade({
useAddonManager: "permanent",
manifest,
});
await AddonTestUtils.waitForSearchProviderStartup(extension);
await promiseChanged;
const defaultEngineData = {
id: engine.telemetryId,
name: "Bar",
loadPath: engine.wrappedJSObject._loadPath,
};
await checkTelemetry("engine-update", defaultEngineData, defaultEngineData);
await extension.unload();
});
add_task(async function test_only_notify_on_relevant_engine_property_change() {
clearTelemetry();
await SearchTestUtils.updateRemoteSettingsConfig(BASE_CONFIG);
// Since SearchUtils.notifyAction can be called for multiple different search
// engine topics, `resetPrevEngineInfo` is a better way to track
// notifications in this case.
let notificationSpy = sinon.spy(
AppProvidedSearchEngine.prototype,
"_resetPrevEngineInfo"
);
// Change an engine property that is not stored in
// AppProvidedSearchEngine.#prevEngineInfo.
let reloadObserved =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
await SearchTestUtils.updateRemoteSettingsConfig(
CONFIG_WITH_MODIFIED_CLASSIFICATION
);
await reloadObserved;
Assert.equal(
notificationSpy.callCount,
0,
"Should not have sent a notification"
);
notificationSpy.restore();
});
add_task(
async function test_multiple_updates_only_notify_on_relevant_engine_property_change() {
clearTelemetry();
await SearchTestUtils.updateRemoteSettingsConfig(BASE_CONFIG);
// Since SearchUtils.notifyAction can be called for multiple different search
// engine topics, `resetPrevEngineInfo` is a better way to track
// notifications in this case.
let notificationSpy = sinon.spy(
AppProvidedSearchEngine.prototype,
"_resetPrevEngineInfo"
);
// Change an engine property that is not stored in
// AppProvidedSearchEngine.#prevEngineInfo.
let reloadObserved1 =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
await SearchTestUtils.updateRemoteSettingsConfig(
CONFIG_WITH_MODIFIED_CLASSIFICATION
);
await reloadObserved1;
Assert.equal(
notificationSpy.callCount,
0,
"Should not have sent a notification"
);
// Now change an engine property that is stored in
// AppProvidedSearchEngine.#prevEngineInfo.
let reloadObserved2 =
SearchTestUtils.promiseSearchNotification("engines-reloaded");
await SearchTestUtils.updateRemoteSettingsConfig(CONFIG_WITH_MODIFIED_NAME);
await reloadObserved2;
Assert.equal(
notificationSpy.callCount,
1,
"Should have sent a notification"
);
notificationSpy.restore();
}
);