зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1839558
- Allow suggestion scores to be specified in Nimbus. r=daisuke
This adds a `quickSuggestScoreMap` Nimbus variable that lets experiments override suggestion scores. It maps from telemetry types to score values. For example: ``` "quickSuggestScoreMap": { "amo": 0.25, "adm_sponsored": 0.3 } ``` In this example, addon suggestions will always have a score of 0.25, and sponsored suggestions will always have a score of 0.3. Of course, different branches within an experiment and different experiments can set different scores. While working on this, I saw we have a bug when we try to look up the `BaseFeature` for a result. To do the lookup, we look up the result's `telemetryType` in `FEATURE_NAMES_BY_TELEMETRY_TYPE`. That's a problem for `adm` suggestions because the `telemetryType` will be either `adm_sponsored` or `adm_nonsponsored`, but neither of those is present in `FEATURE_NAMES_BY_TELEMETRY_TYPE` -- only `adm` is. To fix it, I added back the `provider` property to result payloads that I previously removed, and I added `BaseFeature.merinoProvider` so each feature can specify its Merino provider. Then, `QuickSuggest` can build a map from Merino provider names to features, allowing us to look up features without needing to hardcode something like `FEATURE_NAMES_BY_TELEMETRY_TYPE` or `FEATURE_NAMES_BY_MERINO_PROVIDER`. Since I added back the `provider` property, I had to update a lot of tests. (As a follow up, it would be nice to centralize the creation of expected result objects in the test helper.) I also added `BaseFeature.getSuggestionTelemetryType()` to help implement the score map and to better formalize the idea that telemetry types are an important property that all quick suggest results should include. Differential Revision: https://phabricator.services.mozilla.com/D181709
This commit is contained in:
Родитель
01cd7f07de
Коммит
0e465234f7
|
@ -141,6 +141,9 @@ class _QuickSuggest {
|
|||
let { [name]: ctor } = ChromeUtils.importESModule(uri);
|
||||
let feature = new ctor();
|
||||
this.#features[name] = feature;
|
||||
if (feature.merinoProvider) {
|
||||
this.#featuresByMerinoProvider.set(feature.merinoProvider, feature);
|
||||
}
|
||||
|
||||
// Update the map from enabling preferences to features.
|
||||
let prefs = feature.enablingPreferences;
|
||||
|
@ -173,6 +176,21 @@ class _QuickSuggest {
|
|||
return this.#features[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a quick suggest feature by the name of the Merino provider that
|
||||
* serves its suggestions (as defined by `feature.merinoProvider`). Not all
|
||||
* features correspond to a Merino provider.
|
||||
*
|
||||
* @param {string} provider
|
||||
* The name of a Merino provider.
|
||||
* @returns {BaseFeature}
|
||||
* The feature object, an instance of a subclass of `BaseFeature`, or null
|
||||
* if no feature corresponds to the Merino provider.
|
||||
*/
|
||||
getFeatureByMerinoProvider(provider) {
|
||||
return this.#featuresByMerinoProvider.get(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a urlbar pref changes.
|
||||
*
|
||||
|
@ -471,6 +489,9 @@ class _QuickSuggest {
|
|||
// Maps from quick suggest feature class names to feature instances.
|
||||
#features = {};
|
||||
|
||||
// Maps from Merino provider names to quick suggest feature class names.
|
||||
#featuresByMerinoProvider = new Map();
|
||||
|
||||
// Maps from preference names to the `Set` of feature instances they enable.
|
||||
#featuresByEnablingPrefs = new Map();
|
||||
}
|
||||
|
|
|
@ -458,6 +458,7 @@ const NIMBUS_DEFAULTS = {
|
|||
experimentType: "",
|
||||
isBestMatchExperiment: false,
|
||||
quickSuggestRemoteSettingsDataType: "data",
|
||||
quickSuggestScoreMap: null,
|
||||
recordNavigationalSuggestionTelemetry: false,
|
||||
weatherKeywords: null,
|
||||
weatherKeywordsMinimumLength: 0,
|
||||
|
|
|
@ -21,12 +21,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
|
||||
});
|
||||
|
||||
const FEATURE_NAMES_BY_TELEMETRY_TYPE = {
|
||||
adm: "AdmWikipedia",
|
||||
amo: "AddonSuggestions",
|
||||
pocket: "PocketSuggestions",
|
||||
};
|
||||
|
||||
const TELEMETRY_PREFIX = "contextual.services.quicksuggest";
|
||||
|
||||
const TELEMETRY_SCALARS = {
|
||||
|
@ -161,7 +155,26 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
let suggestions = values.flat().sort((a, b) => b.score - a.score);
|
||||
let suggestions = values.flat();
|
||||
|
||||
// Override suggestion scores with the ones defined in the Nimbus variable
|
||||
// `quickSuggestScoreMap`. It maps telemetry types to scores.
|
||||
let scoreMap = lazy.UrlbarPrefs.get("quickSuggestScoreMap");
|
||||
if (scoreMap) {
|
||||
for (let i = 0; i < suggestions.length; i++) {
|
||||
let telemetryType = this.#getSuggestionTelemetryType(suggestions[i]);
|
||||
if (scoreMap.hasOwnProperty(telemetryType)) {
|
||||
let score = parseFloat(scoreMap[telemetryType]);
|
||||
if (!isNaN(score)) {
|
||||
// Don't modify the original suggestion object in case the feature
|
||||
// that provided it returns the same object to all callers.
|
||||
suggestions[i] = { ...suggestions[i], score };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suggestions.sort((a, b) => b.score - a.score);
|
||||
|
||||
// Add a result for the first suggestion that can be shown.
|
||||
for (let suggestion of suggestions) {
|
||||
|
@ -255,23 +268,57 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
return this.#getFeatureByResult(result)?.getResultCommands?.(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `BaseFeature` instance that implements suggestions for a source
|
||||
* and provider name. The source and provider name can be supplied from either
|
||||
* a suggestion object or the payload of a `UrlbarResult` object.
|
||||
*
|
||||
* @param {object} options
|
||||
* Options object.
|
||||
* @param {string} options.source
|
||||
* The suggestion source, one of: "remote-settings", "merino"
|
||||
* @param {string} options.provider
|
||||
* If the suggestion source is remote settings, this should be the name of
|
||||
* the `BaseFeature` instance (`feature.name`) that manages the suggestion
|
||||
* type. If the suggestion source is Merino, this should be the name of the
|
||||
* Merino provider that serves the suggestion type.
|
||||
* @returns {BaseFeature}
|
||||
* The feature instance or null if no feature was found.
|
||||
*/
|
||||
#getFeature({ source, provider }) {
|
||||
return source == "remote-settings"
|
||||
? lazy.QuickSuggest.getFeature(provider)
|
||||
: lazy.QuickSuggest.getFeatureByMerinoProvider(provider);
|
||||
}
|
||||
|
||||
#getFeatureByResult(result) {
|
||||
return lazy.QuickSuggest.getFeature(
|
||||
FEATURE_NAMES_BY_TELEMETRY_TYPE[result.payload.telemetryType]
|
||||
);
|
||||
return this.#getFeature(result.payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the telemetry type for a suggestion. A telemetry type uniquely
|
||||
* identifies a type of suggestion as well as the kind of `UrlbarResult`
|
||||
* instances created from it.
|
||||
*
|
||||
* @param {object} suggestion
|
||||
* A suggestion from remote settings or Merino.
|
||||
* @returns {string}
|
||||
* The telemetry type. If the suggestion type is managed by a `BaseFeature`
|
||||
* instance, the telemetry type is retrieved from it. Otherwise the
|
||||
* suggestion type is assumed to come from Merino, and `suggestion.provider`
|
||||
* (the Merino provider name) is returned.
|
||||
*/
|
||||
#getSuggestionTelemetryType(suggestion) {
|
||||
let feature = this.#getFeature(suggestion);
|
||||
if (feature) {
|
||||
return feature.getSuggestionTelemetryType(suggestion);
|
||||
}
|
||||
return suggestion.provider;
|
||||
}
|
||||
|
||||
async #makeResult(queryContext, suggestion) {
|
||||
// For suggestions from remote settings, `suggestion.provider` will be the
|
||||
// feature name. For suggestions from Merino, it will be the Merino provider
|
||||
// name, which is also used as the `telemetryType`.
|
||||
let feature =
|
||||
lazy.QuickSuggest.getFeature(suggestion.provider) ||
|
||||
lazy.QuickSuggest.getFeature(
|
||||
FEATURE_NAMES_BY_TELEMETRY_TYPE[suggestion.provider]
|
||||
);
|
||||
|
||||
let result;
|
||||
let feature = this.#getFeature(suggestion);
|
||||
if (!feature) {
|
||||
result = this.#makeDefaultResult(queryContext, suggestion);
|
||||
} else {
|
||||
|
@ -286,6 +333,17 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// `source` will be one of: "remote-settings", "merino"
|
||||
result.payload.source = suggestion.source;
|
||||
|
||||
// If the suggestion source is remote settings, `provider` will be the name
|
||||
// of the `BaseFeature` instance (`feature.name`) that manages the
|
||||
// suggestion type. If the source is Merino, it will be the name of the
|
||||
// Merino provider that served the suggestion.
|
||||
result.payload.provider = suggestion.provider;
|
||||
|
||||
result.payload.telemetryType = this.#getSuggestionTelemetryType(suggestion);
|
||||
|
||||
if (!result.hasSuggestedIndex) {
|
||||
// When `bestMatchEnabled` is true, a "Top pick" checkbox appears in
|
||||
// about:preferences. Show top pick suggestions as top picks only if that
|
||||
|
@ -322,8 +380,6 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
url: suggestion.url,
|
||||
icon: suggestion.icon,
|
||||
isSponsored: suggestion.is_sponsored,
|
||||
source: suggestion.source,
|
||||
telemetryType: suggestion.provider,
|
||||
helpUrl: lazy.QuickSuggest.HELP_URL,
|
||||
helpL10n: {
|
||||
id: "urlbar-result-menu-learn-more-about-firefox-suggest",
|
||||
|
|
|
@ -256,7 +256,7 @@ class ProviderWeather extends UrlbarProvider {
|
|||
},
|
||||
requestId: suggestion.request_id,
|
||||
source: suggestion.source,
|
||||
merinoProvider: suggestion.provider,
|
||||
provider: suggestion.provider,
|
||||
dynamicType: WEATHER_DYNAMIC_TYPE,
|
||||
city: suggestion.city_name,
|
||||
temperatureUnit: unit,
|
||||
|
|
|
@ -1631,6 +1631,9 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
|
|||
originalUrl: {
|
||||
type: "string",
|
||||
},
|
||||
provider: {
|
||||
type: "string",
|
||||
},
|
||||
qsSuggestion: {
|
||||
type: "string",
|
||||
},
|
||||
|
|
|
@ -133,6 +133,10 @@ export class AddonSuggestions extends BaseFeature {
|
|||
return ["suggest.addons", "suggest.quicksuggest.nonsponsored"];
|
||||
}
|
||||
|
||||
get merinoProvider() {
|
||||
return "amo";
|
||||
}
|
||||
|
||||
enable(enabled) {
|
||||
if (enabled) {
|
||||
lazy.QuickSuggestRemoteSettings.register(this);
|
||||
|
@ -233,7 +237,6 @@ export class AddonSuggestions extends BaseFeature {
|
|||
}
|
||||
|
||||
const payload = {
|
||||
source: suggestion.source,
|
||||
icon: suggestion.icon,
|
||||
url: suggestion.url,
|
||||
title: suggestion.title,
|
||||
|
@ -243,7 +246,6 @@ export class AddonSuggestions extends BaseFeature {
|
|||
helpUrl: lazy.QuickSuggest.HELP_URL,
|
||||
shouldNavigate: true,
|
||||
dynamicType: "addons",
|
||||
telemetryType: "amo",
|
||||
};
|
||||
|
||||
return Object.assign(
|
||||
|
|
|
@ -44,6 +44,14 @@ export class AdmWikipedia extends BaseFeature {
|
|||
];
|
||||
}
|
||||
|
||||
get merinoProvider() {
|
||||
return "adm";
|
||||
}
|
||||
|
||||
getSuggestionTelemetryType(suggestion) {
|
||||
return suggestion.is_sponsored ? "adm_sponsored" : "adm_nonsponsored";
|
||||
}
|
||||
|
||||
enable(enabled) {
|
||||
if (enabled) {
|
||||
lazy.QuickSuggestRemoteSettings.register(this);
|
||||
|
@ -126,10 +134,6 @@ export class AdmWikipedia extends BaseFeature {
|
|||
url: suggestion.url,
|
||||
icon: suggestion.icon,
|
||||
isSponsored: suggestion.is_sponsored,
|
||||
source: suggestion.source,
|
||||
telemetryType: suggestion.is_sponsored
|
||||
? "adm_sponsored"
|
||||
: "adm_nonsponsored",
|
||||
requestId: suggestion.request_id,
|
||||
urlTimestampIndex: suggestion.urlTimestampIndex,
|
||||
sponsoredImpressionUrl: suggestion.impression_url,
|
||||
|
|
|
@ -63,6 +63,16 @@ export class BaseFeature {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
* If the feature manages suggestions served by Merino, the subclass should
|
||||
* override this getter and return the name of the specific Merino provider
|
||||
* that serves them.
|
||||
*/
|
||||
get merinoProvider() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should initialize or uninitialize any state related to the
|
||||
* feature.
|
||||
|
@ -97,6 +107,22 @@ export class BaseFeature {
|
|||
*/
|
||||
async onRemoteSettingsSync(rs) {}
|
||||
|
||||
/**
|
||||
* If the feature manages suggestions that either aren't served by Merino or
|
||||
* whose telemetry type is different from `merinoProvider`, the subclass
|
||||
* should override this method. It should return the telemetry type for the
|
||||
* given suggestion. A telemetry type uniquely identifies a type of suggestion
|
||||
* as well as the kind of `UrlbarResult` instances created from it.
|
||||
*
|
||||
* @param {object} suggestion
|
||||
* A suggestion from either remote settings or Merino.
|
||||
* @returns {string}
|
||||
* The suggestion's telemetry type.
|
||||
*/
|
||||
getSuggestionTelemetryType(suggestion) {
|
||||
return this.merinoProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the feature corresponds to a type of suggestion, the subclass should
|
||||
* override this method. It should return a new `UrlbarResult` for a given
|
||||
|
|
|
@ -46,6 +46,10 @@ export class PocketSuggestions extends BaseFeature {
|
|||
return ["suggest.pocket", "suggest.quicksuggest.nonsponsored"];
|
||||
}
|
||||
|
||||
get merinoProvider() {
|
||||
return "pocket";
|
||||
}
|
||||
|
||||
get showLessFrequentlyCount() {
|
||||
let count = lazy.UrlbarPrefs.get("pocket.showLessFrequentlyCount") || 0;
|
||||
return Math.max(count, 0);
|
||||
|
@ -118,8 +122,6 @@ export class PocketSuggestions extends BaseFeature {
|
|||
title: [suggestion.title, lazy.UrlbarUtils.HIGHLIGHT.TYPED],
|
||||
icon: "chrome://global/skin/icons/pocket.svg",
|
||||
helpUrl: lazy.QuickSuggest.HELP_URL,
|
||||
source: suggestion.source,
|
||||
telemetryType: "pocket",
|
||||
})
|
||||
),
|
||||
{ showFeedbackMenu: true }
|
||||
|
|
|
@ -106,6 +106,7 @@ const EXPECTED_SPONSORED_RESULT = {
|
|||
},
|
||||
displayUrl: "http://test.com/q=frabbits",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -140,6 +141,7 @@ const EXPECTED_NONSPONSORED_RESULT = {
|
|||
},
|
||||
displayUrl: "http://test.com/?q=nonsponsored",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -174,6 +176,7 @@ const EXPECTED_HTTP_RESULT = {
|
|||
},
|
||||
displayUrl: "http://" + PREFIX_SUGGESTIONS_STRIPPED_URL,
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -208,6 +211,7 @@ const EXPECTED_HTTPS_RESULT = {
|
|||
},
|
||||
displayUrl: PREFIX_SUGGESTIONS_STRIPPED_URL,
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -242,6 +246,31 @@ add_setup(async function init() {
|
|||
});
|
||||
});
|
||||
|
||||
add_task(async function telemetryType_sponsored() {
|
||||
Assert.equal(
|
||||
QuickSuggest.getFeature("AdmWikipedia").getSuggestionTelemetryType({
|
||||
is_sponsored: true,
|
||||
}),
|
||||
"adm_sponsored",
|
||||
"Telemetry type should be 'adm_sponsored'"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function telemetryType_nonsponsored() {
|
||||
Assert.equal(
|
||||
QuickSuggest.getFeature("AdmWikipedia").getSuggestionTelemetryType({
|
||||
is_sponsored: false,
|
||||
}),
|
||||
"adm_nonsponsored",
|
||||
"Telemetry type should be 'adm_nonsponsored'"
|
||||
);
|
||||
Assert.equal(
|
||||
QuickSuggest.getFeature("AdmWikipedia").getSuggestionTelemetryType({}),
|
||||
"adm_nonsponsored",
|
||||
"Telemetry type should be 'adm_nonsponsored' if `is_sponsored` not defined"
|
||||
);
|
||||
});
|
||||
|
||||
// Tests with only non-sponsored suggestions enabled with a matching search
|
||||
// string.
|
||||
add_task(async function nonsponsoredOnly_match() {
|
||||
|
@ -1024,6 +1053,7 @@ add_task(async function dedupeAgainstURL_timestamps() {
|
|||
: "firefox-suggest-urlbar-block",
|
||||
},
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -84,6 +84,14 @@ add_setup(async function init() {
|
|||
});
|
||||
});
|
||||
|
||||
add_task(async function telemetryType() {
|
||||
Assert.equal(
|
||||
QuickSuggest.getFeature("AddonSuggestions").getSuggestionTelemetryType({}),
|
||||
"amo",
|
||||
"Telemetry type should be 'amo'"
|
||||
);
|
||||
});
|
||||
|
||||
// When non-sponsored suggestions are disabled, addon suggestions should be
|
||||
// disabled.
|
||||
add_task(async function nonsponsoredDisabled() {
|
||||
|
@ -694,12 +702,15 @@ async function doShowLessFrequentlyTest({ tests, rs = {}, nimbus = {} }) {
|
|||
}
|
||||
|
||||
function makeExpectedResult({ suggestion, source, isTopPick }) {
|
||||
let provider;
|
||||
let rating;
|
||||
let number_of_ratings;
|
||||
if (source === "remote-settings") {
|
||||
provider = "AddonSuggestions";
|
||||
rating = suggestion.rating;
|
||||
number_of_ratings = suggestion.number_of_ratings;
|
||||
} else {
|
||||
provider = "amo";
|
||||
rating = suggestion.custom_details.amo.rating;
|
||||
number_of_ratings = suggestion.custom_details.amo.number_of_ratings;
|
||||
}
|
||||
|
@ -723,6 +734,7 @@ function makeExpectedResult({ suggestion, source, isTopPick }) {
|
|||
shouldNavigate: true,
|
||||
helpUrl: QuickSuggest.HELP_URL,
|
||||
source,
|
||||
provider,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ const EXPECTED_BEST_MATCH_URLBAR_RESULT = {
|
|||
},
|
||||
displayUrl: "http://example.com",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -121,6 +122,7 @@ const EXPECTED_NON_BEST_MATCH_URLBAR_RESULT = {
|
|||
},
|
||||
displayUrl: "http://example.com",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -154,6 +156,7 @@ const EXPECTED_BEST_MATCH_POSITION_URLBAR_RESULT = {
|
|||
},
|
||||
displayUrl: "http://example.com/best-match-position",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ function makeExpectedResult() {
|
|||
icon: "icon",
|
||||
qsSuggestion: "full_keyword",
|
||||
source: "merino",
|
||||
provider: "wikipedia",
|
||||
helpUrl: QuickSuggest.HELP_URL,
|
||||
helpL10n: {
|
||||
id: "urlbar-result-menu-learn-more-about-firefox-suggest",
|
||||
|
|
|
@ -65,6 +65,7 @@ const EXPECTED_SPONSORED_URLBAR_RESULT = {
|
|||
: "firefox-suggest-urlbar-block",
|
||||
},
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -99,6 +100,7 @@ const EXPECTED_NONSPONSORED_URLBAR_RESULT = {
|
|||
: "firefox-suggest-urlbar-block",
|
||||
},
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ const EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT = {
|
|||
},
|
||||
displayUrl: "http://test.com/q=frabbits",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -89,6 +90,7 @@ const EXPECTED_MERINO_URLBAR_RESULT = {
|
|||
displayUrl: "url",
|
||||
requestId: "request_id",
|
||||
source: "merino",
|
||||
provider: "adm",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -504,6 +506,7 @@ add_task(async function multipleMerinoSuggestions() {
|
|||
displayUrl: "multipleMerinoSuggestions 1 url",
|
||||
requestId: "request_id",
|
||||
source: "merino",
|
||||
provider: "adm",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -204,6 +204,7 @@ add_task(async function () {
|
|||
: "firefox-suggest-urlbar-block",
|
||||
},
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -45,6 +45,14 @@ add_setup(async function init() {
|
|||
});
|
||||
});
|
||||
|
||||
add_task(async function telemetryType() {
|
||||
Assert.equal(
|
||||
QuickSuggest.getFeature("PocketSuggestions").getSuggestionTelemetryType({}),
|
||||
"pocket",
|
||||
"Telemetry type should be 'pocket'"
|
||||
);
|
||||
});
|
||||
|
||||
// When non-sponsored suggestions are disabled, Pocket suggestions should be
|
||||
// disabled.
|
||||
add_task(async function nonsponsoredDisabled() {
|
||||
|
@ -256,6 +264,7 @@ function makeExpectedResult({
|
|||
heuristic: false,
|
||||
payload: {
|
||||
source,
|
||||
provider: source == "remote-settings" ? "PocketSuggestions" : "pocket",
|
||||
telemetryType: "pocket",
|
||||
title: suggestion.title,
|
||||
url: suggestion.url,
|
||||
|
|
|
@ -171,6 +171,7 @@ function createExpectedQuickSuggestResult(suggest) {
|
|||
},
|
||||
displayUrl: suggest.url,
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,798 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Tests the `quickSuggestScoreMap` Nimbus variable that assigns scores to
|
||||
// specified types of quick suggest suggestions. The scores in the map should
|
||||
// override the scores in the individual suggestion objects so that experiments
|
||||
// can fully control the relative ranking of suggestions.
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DEFAULT_SUGGESTION_SCORE } = QuickSuggestRemoteSettings;
|
||||
|
||||
const REMOTE_SETTINGS_RECORDS = [
|
||||
{
|
||||
type: "data",
|
||||
attachment: [
|
||||
// sponsored without score
|
||||
{
|
||||
iab_category: "22 - Shopping",
|
||||
keywords: [
|
||||
"sponsored without score",
|
||||
"sponsored without score, nonsponsored without score",
|
||||
"sponsored without score, nonsponsored with score",
|
||||
"sponsored without score, addon without score",
|
||||
],
|
||||
id: 1,
|
||||
url: "https://example.com/sponsored-without-score",
|
||||
title: "Sponsored without score",
|
||||
click_url: "https://example.com/click",
|
||||
impression_url: "https://example.com/impression",
|
||||
advertiser: "TestAdvertiser",
|
||||
icon: null,
|
||||
},
|
||||
// sponsored with score
|
||||
{
|
||||
iab_category: "22 - Shopping",
|
||||
score: 2 * DEFAULT_SUGGESTION_SCORE,
|
||||
keywords: [
|
||||
"sponsored with score",
|
||||
"sponsored with score, nonsponsored without score",
|
||||
"sponsored with score, nonsponsored with score",
|
||||
"sponsored with score, addon with score",
|
||||
],
|
||||
id: 2,
|
||||
url: "https://example.com/sponsored-with-score",
|
||||
title: "Sponsored with score",
|
||||
click_url: "https://example.com/click",
|
||||
impression_url: "https://example.com/impression",
|
||||
advertiser: "TestAdvertiser",
|
||||
icon: null,
|
||||
},
|
||||
// nonsponsored without score
|
||||
{
|
||||
iab_category: "5 - Education",
|
||||
keywords: [
|
||||
"nonsponsored without score",
|
||||
"sponsored without score, nonsponsored without score",
|
||||
"sponsored with score, nonsponsored without score",
|
||||
],
|
||||
id: 3,
|
||||
url: "https://example.com/nonsponsored-without-score",
|
||||
title: "Nonsponsored without score",
|
||||
click_url: "https://example.com/click",
|
||||
impression_url: "https://example.com/impression",
|
||||
advertiser: "TestAdvertiser",
|
||||
icon: null,
|
||||
},
|
||||
// nonsponsored with score
|
||||
{
|
||||
iab_category: "5 - Education",
|
||||
score: 2 * DEFAULT_SUGGESTION_SCORE,
|
||||
keywords: [
|
||||
"nonsponsored with score",
|
||||
"sponsored without score, nonsponsored with score",
|
||||
"sponsored with score, nonsponsored with score",
|
||||
],
|
||||
id: 4,
|
||||
url: "https://example.com/nonsponsored-with-score",
|
||||
title: "Nonsponsored with score",
|
||||
click_url: "https://example.com/click",
|
||||
impression_url: "https://example.com/impression",
|
||||
advertiser: "TestAdvertiser",
|
||||
icon: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "amo-suggestions",
|
||||
attachment: [
|
||||
// addon without score
|
||||
{
|
||||
keywords: [
|
||||
"addon without score",
|
||||
"sponsored without score, addon without score",
|
||||
],
|
||||
url: "https://example.com/addon-without-score",
|
||||
guid: "addon-without-score@example.com",
|
||||
icon: "https://example.com/addon.svg",
|
||||
title: "Addon without score",
|
||||
rating: "4.7",
|
||||
description: "Addon without score",
|
||||
number_of_ratings: 1256,
|
||||
is_top_pick: true,
|
||||
},
|
||||
// addon with score
|
||||
{
|
||||
score: 2 * DEFAULT_SUGGESTION_SCORE,
|
||||
keywords: [
|
||||
"addon with score",
|
||||
"sponsored with score, addon with score",
|
||||
],
|
||||
url: "https://example.com/addon-with-score",
|
||||
guid: "addon-with-score@example.com",
|
||||
icon: "https://example.com/addon.svg",
|
||||
title: "Addon with score",
|
||||
rating: "4.7",
|
||||
description: "Addon with score",
|
||||
number_of_ratings: 1256,
|
||||
is_top_pick: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const ADM_RECORD = REMOTE_SETTINGS_RECORDS[0];
|
||||
const SPONSORED_WITHOUT_SCORE = ADM_RECORD.attachment[0];
|
||||
const SPONSORED_WITH_SCORE = ADM_RECORD.attachment[1];
|
||||
const NONSPONSORED_WITHOUT_SCORE = ADM_RECORD.attachment[2];
|
||||
const NONSPONSORED_WITH_SCORE = ADM_RECORD.attachment[3];
|
||||
|
||||
const ADDON_RECORD = REMOTE_SETTINGS_RECORDS[1];
|
||||
const ADDON_WITHOUT_SCORE = ADDON_RECORD.attachment[0];
|
||||
const ADDON_WITH_SCORE = ADDON_RECORD.attachment[1];
|
||||
|
||||
const MERINO_SPONSORED_SUGGESTION = {
|
||||
provider: "adm",
|
||||
score: DEFAULT_SUGGESTION_SCORE,
|
||||
iab_category: "22 - Shopping",
|
||||
is_sponsored: true,
|
||||
keywords: ["test"],
|
||||
full_keyword: "test",
|
||||
block_id: 1,
|
||||
url: "https://example.com/merino-sponsored",
|
||||
title: "Merino sponsored",
|
||||
click_url: "https://example.com/click",
|
||||
impression_url: "https://example.com/impression",
|
||||
advertiser: "TestAdvertiser",
|
||||
icon: null,
|
||||
};
|
||||
|
||||
const MERINO_ADDON_SUGGESTION = {
|
||||
provider: "amo",
|
||||
score: DEFAULT_SUGGESTION_SCORE,
|
||||
keywords: ["test"],
|
||||
icon: "https://example.com/addon.svg",
|
||||
url: "https://example.com/merino-addon",
|
||||
title: "Merino addon",
|
||||
description: "Merino addon",
|
||||
is_top_pick: true,
|
||||
custom_details: {
|
||||
amo: {
|
||||
guid: "merino-addon@example.com",
|
||||
rating: "4.7",
|
||||
number_of_ratings: "1256",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MERINO_UNKNOWN_SUGGESTION = {
|
||||
provider: "some_unknown_provider",
|
||||
score: DEFAULT_SUGGESTION_SCORE,
|
||||
keywords: ["test"],
|
||||
url: "https://example.com/merino-unknown",
|
||||
title: "Merino unknown",
|
||||
};
|
||||
|
||||
add_setup(async function init() {
|
||||
UrlbarPrefs.set("quicksuggest.enabled", true);
|
||||
UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
|
||||
UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
|
||||
UrlbarPrefs.set("addons.featureGate", true);
|
||||
|
||||
// Disable search suggestions so we don't hit the network.
|
||||
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
|
||||
|
||||
await QuickSuggestTestUtils.ensureQuickSuggestInit({
|
||||
remoteSettingsResults: REMOTE_SETTINGS_RECORDS,
|
||||
merinoSuggestions: [],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWithout_nonsponsoredWithout_sponsoredWins() {
|
||||
let keyword = "sponsored without score, nonsponsored without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function sponsoredWithout_nonsponsoredWithout_nonsponsoredWins() {
|
||||
let keyword = "sponsored without score, nonsponsored without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_nonsponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: NONSPONSORED_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
}
|
||||
);
|
||||
add_task(
|
||||
async function sponsoredWithout_nonsponsoredWithout_sponsoredWins_both() {
|
||||
let keyword = "sponsored without score, nonsponsored without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
adm_nonsponsored: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function sponsoredWithout_nonsponsoredWithout_nonsponsoredWins_both() {
|
||||
let keyword = "sponsored without score, nonsponsored without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_nonsponsored: score,
|
||||
adm_sponsored: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: NONSPONSORED_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function sponsoredWith_nonsponsoredWith_sponsoredWins() {
|
||||
let keyword = "sponsored with score, nonsponsored with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_nonsponsoredWith_nonsponsoredWins() {
|
||||
let keyword = "sponsored with score, nonsponsored with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_nonsponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: NONSPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_nonsponsoredWith_sponsoredWins_both() {
|
||||
let keyword = "sponsored with score, nonsponsored with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
adm_nonsponsored: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_nonsponsoredWith_nonsponsoredWins_both() {
|
||||
let keyword = "sponsored with score, nonsponsored with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_nonsponsored: score,
|
||||
adm_sponsored: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: NONSPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWithout_addonWithout_sponsoredWins() {
|
||||
let keyword = "sponsored without score, addon without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWithout_addonWithout_addonWins() {
|
||||
let keyword = "sponsored without score, addon without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
amo: score,
|
||||
},
|
||||
expectedFeatureName: "AddonSuggestions",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAddonResult({
|
||||
suggestion: ADDON_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWithout_addonWithout_sponsoredWins_both() {
|
||||
let keyword = "sponsored without score, addon without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
amo: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWithout_addonWithout_addonWins_both() {
|
||||
let keyword = "sponsored without score, addon without score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
amo: score,
|
||||
adm_sponsored: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AddonSuggestions",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAddonResult({
|
||||
suggestion: ADDON_WITHOUT_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_addonWith_sponsoredWins() {
|
||||
let keyword = "sponsored with score, addon with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_addonWith_addonWins() {
|
||||
let keyword = "sponsored with score, addon with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
amo: score,
|
||||
},
|
||||
expectedFeatureName: "AddonSuggestions",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAddonResult({
|
||||
suggestion: ADDON_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_addonWith_sponsoredWins_both() {
|
||||
let keyword = "sponsored with score, addon with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
amo: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function sponsoredWith_addonWith_addonWins_both() {
|
||||
let keyword = "sponsored with score, addon with score";
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
amo: score,
|
||||
adm_sponsored: score / 2,
|
||||
},
|
||||
expectedFeatureName: "AddonSuggestions",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAddonResult({
|
||||
suggestion: ADDON_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function merino_sponsored_addon_sponsoredWins() {
|
||||
UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", false);
|
||||
|
||||
MerinoTestUtils.server.response.body.suggestions = [
|
||||
MERINO_SPONSORED_SUGGESTION,
|
||||
MERINO_ADDON_SUGGESTION,
|
||||
];
|
||||
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword: "test",
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword: "test",
|
||||
suggestion: MERINO_SPONSORED_SUGGESTION,
|
||||
source: "merino",
|
||||
}),
|
||||
});
|
||||
|
||||
UrlbarPrefs.clear("quicksuggest.remoteSettings.enabled");
|
||||
});
|
||||
|
||||
add_task(async function merino_sponsored_addon_addonWins() {
|
||||
UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", false);
|
||||
|
||||
MerinoTestUtils.server.response.body.suggestions = [
|
||||
MERINO_SPONSORED_SUGGESTION,
|
||||
MERINO_ADDON_SUGGESTION,
|
||||
];
|
||||
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword: "test",
|
||||
scoreMap: {
|
||||
amo: score,
|
||||
},
|
||||
expectedFeatureName: "AddonSuggestions",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAddonResult({
|
||||
suggestion: MERINO_ADDON_SUGGESTION,
|
||||
source: "merino",
|
||||
}),
|
||||
});
|
||||
|
||||
UrlbarPrefs.clear("quicksuggest.remoteSettings.enabled");
|
||||
});
|
||||
|
||||
add_task(async function merino_sponsored_unknown_sponsoredWins() {
|
||||
UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", false);
|
||||
|
||||
MerinoTestUtils.server.response.body.suggestions = [
|
||||
MERINO_SPONSORED_SUGGESTION,
|
||||
MERINO_UNKNOWN_SUGGESTION,
|
||||
];
|
||||
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword: "test",
|
||||
scoreMap: {
|
||||
adm_sponsored: score,
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword: "test",
|
||||
suggestion: MERINO_SPONSORED_SUGGESTION,
|
||||
source: "merino",
|
||||
}),
|
||||
});
|
||||
|
||||
UrlbarPrefs.clear("quicksuggest.remoteSettings.enabled");
|
||||
});
|
||||
|
||||
add_task(async function merino_sponsored_unknown_unknownWins() {
|
||||
UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", false);
|
||||
|
||||
MerinoTestUtils.server.response.body.suggestions = [
|
||||
MERINO_SPONSORED_SUGGESTION,
|
||||
MERINO_UNKNOWN_SUGGESTION,
|
||||
];
|
||||
|
||||
let score = 10 * DEFAULT_SUGGESTION_SCORE;
|
||||
await doTest({
|
||||
keyword: "test",
|
||||
scoreMap: {
|
||||
[MERINO_UNKNOWN_SUGGESTION.provider]: score,
|
||||
},
|
||||
expectedFeatureName: null,
|
||||
expectedScore: score,
|
||||
expectedResult: makeExpectedDefaultResult({
|
||||
suggestion: MERINO_UNKNOWN_SUGGESTION,
|
||||
}),
|
||||
});
|
||||
|
||||
UrlbarPrefs.clear("quicksuggest.remoteSettings.enabled");
|
||||
});
|
||||
|
||||
add_task(async function stringValue() {
|
||||
let keyword = "sponsored with score, nonsponsored with score";
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: "123.456",
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: 123.456,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function nanValue_sponsoredWins() {
|
||||
let keyword = "sponsored with score, nonsponsored without score";
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_nonsponsored: "this is NaN",
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: 2 * DEFAULT_SUGGESTION_SCORE,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: SPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function nanValue_nonsponsoredWins() {
|
||||
let keyword = "sponsored without score, nonsponsored with score";
|
||||
await doTest({
|
||||
keyword,
|
||||
scoreMap: {
|
||||
adm_sponsored: "this is NaN",
|
||||
},
|
||||
expectedFeatureName: "AdmWikipedia",
|
||||
expectedScore: 2 * DEFAULT_SUGGESTION_SCORE,
|
||||
expectedResult: makeExpectedAdmResult({
|
||||
keyword,
|
||||
suggestion: NONSPONSORED_WITH_SCORE,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Sets up Nimbus with a `quickSuggestScoreMap` variable value, does a search,
|
||||
* and makes sure the expected result is shown and the expected score is set on
|
||||
* the suggestion.
|
||||
*
|
||||
* @param {object} options
|
||||
* Options object.
|
||||
* @param {string} options.keyword
|
||||
* The search string. This should be equal to a keyword from one or more
|
||||
* suggestions.
|
||||
* @param {object} options.scoreMap
|
||||
* The value to set for the `quickSuggestScoreMap` variable.
|
||||
* @param {string} options.expectedFeatureName
|
||||
* The name of the `BaseFeature` instance that is expected to create the
|
||||
* `UrlbarResult` that's shown. If the suggestion is intentionally from an
|
||||
* unknown Merino provider and therefore the quick suggest provider is
|
||||
* expected to create a default result for it, set this to null.
|
||||
* @param {UrlbarResultstring} options.expectedResult
|
||||
* The `UrlbarResult` that's expected to be shown.
|
||||
* @param {number} options.expectedScore
|
||||
* The final `score` value that's expected to be defined on the suggestion
|
||||
* object.
|
||||
*/
|
||||
async function doTest({
|
||||
keyword,
|
||||
scoreMap,
|
||||
expectedFeatureName,
|
||||
expectedResult,
|
||||
expectedScore,
|
||||
}) {
|
||||
let cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({
|
||||
quickSuggestScoreMap: scoreMap,
|
||||
});
|
||||
|
||||
// Stub the expected feature's `makeResult()` so we can see the value of the
|
||||
// passed-in suggestion's score. If the suggestion's type is in the score map,
|
||||
// the provider will set its score before calling `makeResult()`.
|
||||
let actualScore;
|
||||
let sandbox;
|
||||
if (expectedFeatureName) {
|
||||
sandbox = sinon.createSandbox();
|
||||
let feature = QuickSuggest.getFeature(expectedFeatureName);
|
||||
let stub = sandbox
|
||||
.stub(feature, "makeResult")
|
||||
.callsFake((queryContext, suggestion, searchString) => {
|
||||
actualScore = suggestion.score;
|
||||
return stub.wrappedMethod.call(
|
||||
feature,
|
||||
queryContext,
|
||||
suggestion,
|
||||
searchString
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
await check_results({
|
||||
context: createContext(keyword, {
|
||||
providers: [UrlbarProviderQuickSuggest.name],
|
||||
isPrivate: false,
|
||||
}),
|
||||
matches: [expectedResult],
|
||||
});
|
||||
|
||||
if (expectedFeatureName) {
|
||||
Assert.equal(
|
||||
actualScore,
|
||||
expectedScore,
|
||||
"Suggestion score should be set correctly"
|
||||
);
|
||||
sandbox.restore();
|
||||
}
|
||||
|
||||
await cleanUpNimbus();
|
||||
}
|
||||
|
||||
function makeExpectedAdmResult({
|
||||
suggestion,
|
||||
keyword,
|
||||
source = "remote-settings",
|
||||
}) {
|
||||
let isSponsored = suggestion.iab_category != "5 - Education";
|
||||
let result = {
|
||||
type: UrlbarUtils.RESULT_TYPE.URL,
|
||||
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
|
||||
heuristic: false,
|
||||
payload: {
|
||||
source,
|
||||
isSponsored,
|
||||
provider: source == "remote-settings" ? "AdmWikipedia" : "adm",
|
||||
telemetryType: isSponsored ? "adm_sponsored" : "adm_nonsponsored",
|
||||
title: suggestion.title,
|
||||
url: suggestion.url,
|
||||
originalUrl: suggestion.url,
|
||||
displayUrl: suggestion.url.replace(/^https:\/\//, ""),
|
||||
icon: suggestion.icon,
|
||||
sponsoredBlockId:
|
||||
source == "remote-settings" ? suggestion.id : suggestion.block_id,
|
||||
sponsoredImpressionUrl: suggestion.impression_url,
|
||||
sponsoredClickUrl: suggestion.click_url,
|
||||
sponsoredAdvertiser: suggestion.advertiser,
|
||||
sponsoredIabCategory: suggestion.iab_category,
|
||||
qsSuggestion: keyword,
|
||||
helpUrl: QuickSuggest.HELP_URL,
|
||||
helpL10n: {
|
||||
id: "urlbar-result-menu-learn-more-about-firefox-suggest",
|
||||
},
|
||||
isBlockable: true,
|
||||
blockL10n: {
|
||||
id: "urlbar-result-menu-dismiss-firefox-suggest",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (source == "merino") {
|
||||
result.payload.requestId = "request_id";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function makeExpectedAddonResult({ suggestion, source = "remote-settings" }) {
|
||||
return {
|
||||
type: UrlbarUtils.RESULT_TYPE.DYNAMIC,
|
||||
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
|
||||
heuristic: false,
|
||||
payload: {
|
||||
source,
|
||||
provider: source == "remote-settings" ? "AddonSuggestions" : "amo",
|
||||
telemetryType: "amo",
|
||||
dynamicType: "addons",
|
||||
title: suggestion.title,
|
||||
url: suggestion.url,
|
||||
displayUrl: suggestion.url.replace(/^https:\/\//, ""),
|
||||
icon: suggestion.icon,
|
||||
description: suggestion.description,
|
||||
rating: Number(
|
||||
source == "remote-settings"
|
||||
? suggestion.rating
|
||||
: suggestion.custom_details.amo.rating
|
||||
),
|
||||
reviews: Number(
|
||||
source == "remote-settings"
|
||||
? suggestion.number_of_ratings
|
||||
: suggestion.custom_details.amo.number_of_ratings
|
||||
),
|
||||
shouldNavigate: true,
|
||||
helpUrl: QuickSuggest.HELP_URL,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeExpectedDefaultResult({ suggestion }) {
|
||||
return {
|
||||
type: UrlbarUtils.RESULT_TYPE.URL,
|
||||
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
|
||||
heuristic: false,
|
||||
payload: {
|
||||
source: "merino",
|
||||
provider: suggestion.provider,
|
||||
telemetryType: suggestion.provider,
|
||||
isSponsored: suggestion.is_sponsored,
|
||||
title: suggestion.title,
|
||||
url: suggestion.url,
|
||||
displayUrl: suggestion.url.replace(/^https:\/\//, ""),
|
||||
icon: suggestion.icon,
|
||||
helpUrl: QuickSuggest.HELP_URL,
|
||||
helpL10n: {
|
||||
id: "urlbar-result-menu-learn-more-about-firefox-suggest",
|
||||
},
|
||||
isBlockable: true,
|
||||
blockL10n: {
|
||||
id: "urlbar-result-menu-dismiss-firefox-suggest",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
|
@ -267,6 +267,7 @@ function makeExpectedResult({
|
|||
icon: "icon",
|
||||
isSponsored: false,
|
||||
source: "merino",
|
||||
provider: telemetryType,
|
||||
helpUrl: QuickSuggest.HELP_URL,
|
||||
helpL10n: {
|
||||
id: "urlbar-result-menu-learn-more-about-firefox-suggest",
|
||||
|
|
|
@ -1379,7 +1379,7 @@ function makeExpectedResult({
|
|||
},
|
||||
requestId: MerinoTestUtils.server.response.body.request_id,
|
||||
source: "merino",
|
||||
merinoProvider: "accuweather",
|
||||
provider: "accuweather",
|
||||
dynamicType: "weather",
|
||||
city: WEATHER_SUGGESTION.city_name,
|
||||
temperature:
|
||||
|
|
|
@ -883,6 +883,7 @@ async function doMatchingQuickSuggestTest(pref, isSponsored) {
|
|||
: "firefox-suggest-urlbar-block",
|
||||
},
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1364,7 +1365,7 @@ function makeExpectedResult({
|
|||
},
|
||||
requestId: MerinoTestUtils.server.response.body.request_id,
|
||||
source: "merino",
|
||||
merinoProvider: "accuweather",
|
||||
provider: "accuweather",
|
||||
dynamicType: "weather",
|
||||
city: WEATHER_SUGGESTION.city_name,
|
||||
temperature:
|
||||
|
|
|
@ -18,6 +18,7 @@ firefox-appdir = browser
|
|||
[test_quicksuggest_offlineDefault.js]
|
||||
[test_quicksuggest_pocket.js]
|
||||
[test_quicksuggest_positionInSuggestions.js]
|
||||
[test_quicksuggest_scoreMap.js]
|
||||
[test_quicksuggest_topPicks.js]
|
||||
[test_suggestionsMap.js]
|
||||
[test_weather.js]
|
||||
|
|
|
@ -62,6 +62,7 @@ const EXPECTED_REMOTE_SETTINGS_URLBAR_RESULT = {
|
|||
},
|
||||
displayUrl: "http://test.com/q=frabbits",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -96,6 +97,7 @@ const EXPECTED_NON_SPONSORED_REMOTE_SETTINGS_RESULT = {
|
|||
},
|
||||
displayUrl: "http://test.com/q=frabbits",
|
||||
source: "remote-settings",
|
||||
provider: "AdmWikipedia",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -290,6 +290,16 @@ urlbar:
|
|||
- history
|
||||
- offline
|
||||
- online
|
||||
quickSuggestScoreMap:
|
||||
type: json
|
||||
description: >-
|
||||
A JSON object that maps telemetry result types to suggestion scores. If
|
||||
a telemetry result type is present in this map, the client will use the
|
||||
corresponding score as the score for all suggestions of the type,
|
||||
overriding all other sources of scores for the type. In other words,
|
||||
the scores in this map will override scores that are set in remote
|
||||
settings and Merino as well as scores that are hardcoded in the client.
|
||||
Example entries: `"amo": 0.5`, `"adm_sponsored": 0.9`
|
||||
quickSuggestShouldShowOnboardingDialog:
|
||||
type: boolean
|
||||
fallbackPref: browser.urlbar.quicksuggest.shouldShowOnboardingDialog
|
||||
|
|
Загрузка…
Ссылка в новой задаче