diff --git a/browser/components/urlbar/moz.build b/browser/components/urlbar/moz.build index f95a48604013..9a332f6c5706 100644 --- a/browser/components/urlbar/moz.build +++ b/browser/components/urlbar/moz.build @@ -73,7 +73,6 @@ EXTRA_JS_MODULES["urlbar/private"] += [ TESTING_JS_MODULES += [ "tests/quicksuggest/MerinoTestUtils.sys.mjs", "tests/quicksuggest/QuickSuggestTestUtils.sys.mjs", - "tests/quicksuggest/RemoteSettingsServer.sys.mjs", "tests/UrlbarTestUtils.sys.mjs", ] BROWSER_CHROME_MANIFESTS += [ diff --git a/browser/components/urlbar/private/SuggestBackendJs.sys.mjs b/browser/components/urlbar/private/SuggestBackendJs.sys.mjs index bffd5988187e..685790299233 100644 --- a/browser/components/urlbar/private/SuggestBackendJs.sys.mjs +++ b/browser/components/urlbar/private/SuggestBackendJs.sys.mjs @@ -258,15 +258,6 @@ export class SuggestBackendJs extends BaseFeature { this.#emitter.emit("config-set"); } - async _test_syncAll() { - if (this.#rs) { - // `RemoteSettingsClient` won't start another import if it's already - // importing. Wait for it to finish before starting the new one. - await this.#rs._importingPromise; - await this.#syncAll(); - } - } - // The `RemoteSettings` client. #rs = null; diff --git a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs index 3604c8223b97..7bd1c73d0818 100644 --- a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs +++ b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs @@ -147,7 +147,7 @@ export class SuggestBackendRust extends BaseFeature { /** * nsITimerCallback */ - notify() { + async notify() { this.logger.info("Ingest timer fired"); this.#ingest(); } @@ -164,10 +164,7 @@ export class SuggestBackendRust extends BaseFeature { let path = this.#storePath; this.logger.info("Initializing SuggestStore: " + path); try { - this.#store = lazy.SuggestStore.init( - path, - SuggestBackendRust._test_remoteSettingsConfig - ); + this.#store = lazy.SuggestStore.init(path); } catch (error) { this.logger.error("Error initializing SuggestStore:"); this.logger.error(error); @@ -213,21 +210,10 @@ export class SuggestBackendRust extends BaseFeature { this.#ingestPromise = this.#store.ingest( new lazy.SuggestIngestionConstraints() ); - try { - await this.#ingestPromise; - } catch (error) { - // Ingest can throw a `SuggestApiError` subclass called `Other` that has a - // custom `reason` message, which is very helpful for diagnosing problems - // with remote settings data in tests in particular. - this.logger.error("Ingest error: " + (error.reason ?? error)); - } + await this.#ingestPromise; this.logger.info("Finished ingest"); } - async _test_ingest() { - await this.#ingest(); - } - // The `SuggestStore` instance. #store; diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js index 01d76ad056f1..9b733126a954 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js @@ -992,7 +992,7 @@ add_task(async function selected_result_addons() { add_task(async function selected_result_rust_adm_sponsored() { const cleanupQuickSuggest = await ensureQuickSuggestInit({ - prefs: [["quicksuggest.rustEnabled", true]], + rustEnabled: true, }); await doTest(async browser => { @@ -1016,7 +1016,7 @@ add_task(async function selected_result_rust_adm_sponsored() { add_task(async function selected_result_rust_adm_nonsponsored() { const cleanupQuickSuggest = await ensureQuickSuggestInit({ - prefs: [["quicksuggest.rustEnabled", true]], + rustEnabled: true, }); await doTest(async browser => { diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js index e86c664b465a..74019a216759 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js @@ -234,6 +234,9 @@ async function doGeneralHistoryTest({ trigger, assert }) { async function doSuggestTest({ trigger, assert }) { const cleanupQuickSuggest = await ensureQuickSuggestInit(); + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.bestMatch.enabled", false]], + }); await doTest(async browser => { await openPopup("nonsponsored"); @@ -243,6 +246,7 @@ async function doSuggestTest({ trigger, assert }) { await assert(); }); + await SpecialPowers.popPrefEnv(); await cleanupQuickSuggest(); } diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js index 9140bca14e70..b46ed4615f81 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js @@ -93,7 +93,7 @@ function _assertGleanTelemetry(telemetryName, expectedExtraList) { async function ensureQuickSuggestInit({ ...args } = {}) { return lazy.QuickSuggestTestUtils.ensureQuickSuggestInit({ ...args, - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: [ @@ -106,7 +106,6 @@ async function ensureQuickSuggestInit({ ...args } = {}) { impression_url: "https://example.com/impression", advertiser: "TestAdvertiser", iab_category: "22 - Shopping", - icon: "1234", }, { id: 2, @@ -115,9 +114,8 @@ async function ensureQuickSuggestInit({ ...args } = {}) { keywords: ["nonsponsored"], click_url: "https://example.com/click", impression_url: "https://example.com/impression", - advertiser: "Wikipedia", + advertiser: "TestAdvertiser", iab_category: "5 - Education", - icon: "1234", }, ], }, diff --git a/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs b/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs index 3e78fd98c01c..670fdf7a57ea 100644 --- a/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs +++ b/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs @@ -16,12 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, { NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs", RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", - RemoteSettingsConfig: "resource://gre/modules/RustRemoteSettings.sys.mjs", - RemoteSettingsServer: - "resource://testing-common/RemoteSettingsServer.sys.mjs", SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", - SuggestBackendRust: - "resource:///modules/urlbar/private/SuggestBackendRust.sys.mjs", Suggestion: "resource://gre/modules/RustSuggest.sys.mjs", SuggestionProvider: "resource://gre/modules/RustSuggest.sys.mjs", SuggestStore: "resource://gre/modules/RustSuggest.sys.mjs", @@ -129,6 +124,257 @@ const TEST_SCOPE_PROPERTIES = [ "registerCleanupFunction", ]; +/** + * Mock RemoteSettings. + * + * @param {object} options + * Options object + * @param {object} options.config + * Dummy config in the RemoteSettings. + * @param {Array} options.data + * Dummy data in the RemoteSettings. + */ +class MockRemoteSettings { + constructor({ config = DEFAULT_CONFIG, data = [] }) { + this.#config = config; + this.#data = data; + + // Make a stub for "get" function to return dummy data. + const rs = lazy.RemoteSettings("quicksuggest"); + this.#sandbox = lazy.sinon.createSandbox(); + this.#sandbox.stub(rs, "get").callsFake(async query => { + return query.filters.type === "configuration" + ? [{ configuration: this.#config }] + : this.#data.filter(r => r.type === query.filters.type); + }); + + // Make a stub for "download" in attachments. + this.#sandbox.stub(rs.attachments, "download").callsFake(async record => { + if (!record.attachment) { + throw new Error("No attachmet in the record"); + } + const encoder = new TextEncoder(); + return { + buffer: encoder.encode(JSON.stringify(record.attachment)), + }; + }); + } + + async sync() { + if (!lazy.QuickSuggest.jsBackend.rs) { + // There are no registered features that use remote settings. + return; + } + + // Observe config-set event to recognize that the config is synced. + const onConfigSync = new Promise(resolve => { + lazy.QuickSuggest.jsBackend.emitter.once("config-set", resolve); + }); + + // Make a stub for each feature to recognize that the features are synced. + const features = lazy.QuickSuggest.jsBackend.features; + const onFeatureSyncs = features.map(feature => { + return new Promise(resolve => { + const stub = this.#sandbox + .stub(feature, "onRemoteSettingsSync") + .callsFake(async (...args) => { + // Call and wait for the original function. + await stub.wrappedMethod.apply(feature, args); + stub.restore(); + resolve(); + }); + }); + }); + + // Force to sync. + const rs = lazy.RemoteSettings("quicksuggest"); + rs.emit("sync"); + + // Wait for sync. + await Promise.all([onConfigSync, ...onFeatureSyncs]); + } + + /* + * Update the config and data in RemoteSettings. If the config or the data are + * undefined, use the current one. + * + * @param {object} options + * Options object + * @param {object} options.config + * Dummy config in the RemoteSettings. + * @param {Array} options.data + * Dummy data in the RemoteSettings. + */ + async update({ config = this.#config, data = this.#data }) { + this.#config = config; + this.#data = data; + + await this.sync(); + } + + cleanup() { + this.#sandbox.restore(); + } + + #config = null; + #data = null; + #sandbox = null; +} + +/** + * Mock `RustSuggest` implementation. + * + * @param {object} options + * Options object + * @param {Array} options.data + * Mock remote settings records. + */ +export class MockRustSuggest { + constructor({ data = [] }) { + this.#data = data; + + this.#sandbox = lazy.sinon.createSandbox(); + this.#sandbox.stub(lazy.SuggestStore, "init").returns(this); + } + + /** + * Updates the mock data. + * + * @param {object} options + * Options object + * @param {Array} options.data + * Mock remote settings records. + */ + async update({ data }) { + this.#data = data; + } + + cleanup() { + this.#sandbox.restore(); + } + + // `RustSuggest` methods below. + + ingest() { + // Unlike the real Rust component, ingest isn't necessary here since our + // `query()` implementation has immediate access to the mock data. This + // makes it easier for tests because they don't need to wait for ingest + // every time they change the data. + return Promise.resolve(); + } + + interrupt() {} + + clear() {} + + async query(query) { + let { keyword, providers } = query; + return this.#data.reduce((matchedSuggestions, record) => { + if (!record.attachment) { + return matchedSuggestions; + } + for (let suggestion of record.attachment) { + let provider = this.#getProvider(record, suggestion); + if (!providers.includes(provider)) { + continue; + } + + switch (provider) { + case lazy.SuggestionProvider.POCKET: + if ( + !suggestion.lowConfidenceKeywords.includes(keyword) && + !suggestion.highConfidenceKeywords.includes(keyword) + ) { + continue; + } + break; + default: + if (!suggestion.keywords.includes(keyword)) { + continue; + } + break; + } + + matchedSuggestions.push( + this.#makeSuggestion(provider, suggestion, query) + ); + } + + return matchedSuggestions; + }, []); + } + + #getProvider(record, suggestion) { + switch (record.type) { + case "data": + case "test-data-type": { + let isSponsored = suggestion.hasOwnProperty("is_sponsored") + ? suggestion.is_sponsored + : suggestion.iab_category == "22 - Shopping"; + return isSponsored + ? lazy.SuggestionProvider.AMP + : lazy.SuggestionProvider.WIKIPEDIA; + } + case "amo-suggestions": + return lazy.SuggestionProvider.AMO; + case "pocket-suggestions": + return lazy.SuggestionProvider.POCKET; + } + throw new Error( + "Unrecognized record-suggestion: " + + JSON.stringify({ record, suggestion }) + ); + } + + #makeSuggestion(provider, suggestion, query) { + switch (provider) { + case lazy.SuggestionProvider.AMP: + return new lazy.Suggestion.Amp( + suggestion.title, + suggestion.url, + suggestion.url, // rawUrl + [], // icon + query.keyword, // fullKeyword + suggestion.id, // blockId + suggestion.advertiser, + suggestion.iab_category, + suggestion.impression_url, + suggestion.click_url, + suggestion.click_url // rawClickUrl + ); + case lazy.SuggestionProvider.AMO: + return new lazy.Suggestion.Amo( + suggestion.title, + suggestion.url, + suggestion.icon, + suggestion.description, + suggestion.rating, + suggestion.number_of_ratings, + suggestion.guid, + suggestion.score + ); + case lazy.SuggestionProvider.POCKET: + return new lazy.Suggestion.Pocket( + suggestion.title, + suggestion.url, + suggestion.score, + suggestion.highConfidenceKeywords.includes(query.keyword) // isTopPick + ); + case lazy.SuggestionProvider.WIKIPEDIA: + return new lazy.Suggestion.Wikipedia( + suggestion.title, + suggestion.url, + [], // icon + query.keyword // fullKeyword + ); + } + throw new Error("Unrecognized provider: " + provider); + } + + #data = null; + #sandbox = null; +} + /** * Test utils for quick suggest. */ @@ -175,16 +421,15 @@ class _QuickSuggestTestUtils { } /** - * Sets up local remote settings and Merino servers, registers test - * suggestions, and initializes Suggest. + * Waits for quick suggest initialization to finish, ensures its data will not + * be updated again during the test, and also optionally sets it up with mock + * suggestions. * * @param {object} options * Options object - * @param {Array} options.remoteSettingsRecords - * Array of remote settings records. Each item in this array should be a - * realistic remote settings record with some exceptions, e.g., - * `record.attachment`, if defined, should be the attachment itself and not - * its metadata. For details see `RemoteSettingsServer.addRecords()`. + * @param {Array} options.remoteSettingsResults + * Array of remote settings result objects. If not given, no suggestions + * will be present in remote settings. * @param {Array} options.merinoSuggestions * Array of Merino suggestion objects. If given, this function will start * the mock Merino server and set `quicksuggest.dataCollection.enabled` to @@ -192,152 +437,105 @@ class _QuickSuggestTestUtils { * Otherwise Merino will not serve suggestions, but you can still set up * Merino without using this function by using `MerinoTestUtils` directly. * @param {object} options.config - * The Suggest configuration object. This should not be the full remote - * settings record; only pass the object that should be set to the nested - * `configuration` object inside the record. - * @param {Array} options.prefs - * An array of Suggest-related prefs to set. This is useful because setting - * some prefs, like feature gates, can cause Suggest to sync from remote - * settings; this function will set them, wait for sync to finish, and clear - * them when the cleanup function is called. Each item in this array should - * itself be a two-element array `[prefName, prefValue]` similar to the - * `set` array passed to `SpecialPowers.pushPrefEnv()`, except here pref - * names are relative to `browser.urlbar`. + * The quick suggest configuration object. + * @param {object} options.rustEnabled + * Whether the Rust backend should be enabled. If false, the JS backend will + * be used. (There's no way to tell this function not to change the backend. + * If you need that, please modify this function to support it!) * @returns {Function} - * An async cleanup function. This function is automatically registered as a - * cleanup function, so you only need to call it if your test needs to clean - * up Suggest before it ends, for example if you have a small number of - * tasks that need Suggest and it's not enabled throughout your test. The - * cleanup function is idempotent so there's no harm in calling it more than - * once. Be sure to `await` it. + * An async cleanup function. This function is automatically registered as + * a cleanup function, so you only need to call it if your test needs to + * clean up quick suggest before it ends, for example if you have a small + * number of tasks that need quick suggest and it's not enabled throughout + * your test. The cleanup function is idempotent so there's no harm in + * calling it more than once. Be sure to `await` it. */ async ensureQuickSuggestInit({ - remoteSettingsRecords = [], + remoteSettingsResults, merinoSuggestions = null, config = DEFAULT_CONFIG, - prefs = [], + rustEnabled = false, } = {}) { - prefs.push(["quicksuggest.enabled", true]); - - // Set up the local remote settings server. We do this even for tests that - // aren't specifically related to remote settings so Suggest doesn't hit the - // real remote settings server during testing. - this.#log( - "ensureQuickSuggestInit", - "Started, preparing remote settings server" - ); - if (!this.#remoteSettingsServer) { - this.#remoteSettingsServer = new lazy.RemoteSettingsServer(); - } - await this.#remoteSettingsServer.setRecords({ - collection: "quicksuggest", - records: [ - ...remoteSettingsRecords, - { type: "configuration", configuration: config }, - ], + this.#mockRemoteSettings = new MockRemoteSettings({ + config, + data: remoteSettingsResults, + }); + this.#mockRustSuggest = new MockRustSuggest({ + data: remoteSettingsResults, }); - this.#log("ensureQuickSuggestInit", "Starting remote settings server"); - await this.#remoteSettingsServer.start(); - // Get the cached `RemoteSettings` client used by the JS backend and tell it - // to ignore signatures and to always force sync. Otherwise it won't sync if - // the previous sync was recent enough, which is incompatible with testing. - let rs = lazy.RemoteSettings("quicksuggest"); - let { get, verifySignature } = rs; - rs.verifySignature = false; - rs.get = opts => get.call(rs, { forceSync: true, ...opts }); - this.#restoreRemoteSettings = () => { - rs.verifySignature = verifySignature; - rs.get = get; - }; - - // Tell the Rust backend to use the local remote setting server. - lazy.SuggestBackendRust._test_remoteSettingsConfig = - new lazy.RemoteSettingsConfig( - this.#remoteSettingsServer.url.toString(), - "main", - "quicksuggest" - ); - - // Finally, init Suggest and set prefs. Do this after setting up remote - // settings because the current backend will immediately try to sync. - this.#log( - "ensureQuickSuggestInit", - "Calling QuickSuggest.init() and setting prefs" - ); + this.info?.("ensureQuickSuggestInit calling QuickSuggest.init()"); lazy.QuickSuggest.init(); - for (let [name, value] of prefs) { - lazy.UrlbarPrefs.set(name, value); + + // Set the Rust pref. This must happen after setting up `MockRustSuggest`. + // Otherwise the real Rust component will be used and the Rust remote + // settings client will try to access the real remote settings server on + // ingestion. + lazy.UrlbarPrefs.set("quicksuggest.rustEnabled", rustEnabled); + if (!rustEnabled) { + // Sync with current data. + this.info?.("ensureQuickSuggestInit syncing MockRemoteSettings"); + await this.#mockRemoteSettings.sync(); + this.info?.("ensureQuickSuggestInit done syncing MockRemoteSettings"); } - // Wait for the current backend to finish syncing. - await this.forceSync(); - - // Set up Merino. This can happen any time relative to Suggest init. + // Set up Merino. if (merinoSuggestions) { - this.#log("ensureQuickSuggestInit", "Setting up Merino server"); + this.info?.("ensureQuickSuggestInit setting up Merino server"); await lazy.MerinoTestUtils.server.start(); lazy.MerinoTestUtils.server.response.body.suggestions = merinoSuggestions; lazy.UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true); - this.#log("ensureQuickSuggestInit", "Done setting up Merino server"); + this.info?.("ensureQuickSuggestInit done setting up Merino server"); } let cleanupCalled = false; let cleanup = async () => { if (!cleanupCalled) { cleanupCalled = true; - await this.#uninitQuickSuggest(prefs, !!merinoSuggestions); + await this.#uninitQuickSuggest(!!merinoSuggestions); } }; this.registerCleanupFunction?.(cleanup); - this.#log("ensureQuickSuggestInit", "Done"); return cleanup; } - async #uninitQuickSuggest(prefs, clearDataCollectionEnabled) { - this.#log("#uninitQuickSuggest", "Started"); + async #uninitQuickSuggest(clearDataCollectionEnabled) { + this.info?.("uninitQuickSuggest started"); - // Reset prefs, which can cause the current backend to start syncing. Wait - // for it to finish. - for (let [name] of prefs) { - lazy.UrlbarPrefs.clear(name); + // Reset the Rust enabled status. If the JS backend becomes enabled now, it + // will re-sync all features. Wait for that to finish *before* cleaning up + // MockRemoteSettings. This will ensure that all activity has stopped before + // this function returns. + let rustEnabled = lazy.UrlbarPrefs.get("quicksuggest.rustEnabled"); + lazy.UrlbarPrefs.clear("quicksuggest.rustEnabled"); + if (rustEnabled && !lazy.UrlbarPrefs.get("quicksuggest.rustEnabled")) { + this.info?.("uninitQuickSuggest syncing MockRemoteSettings"); + await this.#mockRemoteSettings.sync(); + this.info?.("uninitQuickSuggest done syncing MockRemoteSettings"); } - await this.forceSync(); - this.#log("#uninitQuickSuggest", "Stopping remote settings server"); - await this.#remoteSettingsServer.stop(); - this.#restoreRemoteSettings(); + this.#mockRemoteSettings.cleanup(); + this.#mockRustSuggest.cleanup(); if (clearDataCollectionEnabled) { lazy.UrlbarPrefs.clear("quicksuggest.dataCollection.enabled"); } - this.#log("#uninitQuickSuggest", "Done"); + this.info?.("uninitQuickSuggest done"); } /** - * Removes all records from the local remote settings server and adds a new - * batch of records. + * Clears the current remote settings data and adds a new set of data. + * This can be used to add remote settings data after + * `ensureQuickSuggestInit()` has been called. * - * @param {Array} records - * Array of remote settings records. See `ensureQuickSuggestInit()`. - * @param {object} options - * Options object. - * @param {boolean} options.forceSync - * Whether to force Suggest to sync after updating the records. + * @param {Array} data + * Array of remote settings data objects. */ - async setRemoteSettingsRecords(records, { forceSync = true } = {}) { - this.#log("setRemoteSettingsRecords", "Started"); - await this.#remoteSettingsServer.setRecords({ - collection: "quicksuggest", - records, - }); - if (forceSync) { - this.#log("setRemoteSettingsRecords", "Forcing sync"); - await this.forceSync(); - } - this.#log("setRemoteSettingsRecords", "Done"); + async setRemoteSettingsResults(data) { + await this.#mockRemoteSettings.update({ data }); + this.#mockRustSuggest.update({ data }); } /** @@ -345,40 +543,10 @@ class _QuickSuggestTestUtils { * `DEFAULT_CONFIG` before your test finishes. See also `withConfig()`. * * @param {object} config - * The quick suggest configuration object. This should not be the full - * remote settings record; only pass the object that should be set to the - * `configuration` nested object inside the record. + * The config to be applied. See */ async setConfig(config) { - this.#log("setConfig", "Started"); - let type = "configuration"; - this.#remoteSettingsServer.removeRecords({ type }); - await this.#remoteSettingsServer.addRecords({ - collection: "quicksuggest", - records: [{ type, configuration: config }], - }); - this.#log("setConfig", "Forcing sync"); - await this.forceSync(); - this.#log("setConfig", "Done"); - } - - /** - * Forces Suggest to sync with remote settings. This can be used to ensure - * Suggest has finished all sync activity. - */ - async forceSync() { - this.#log("forceSync", "Started"); - if (lazy.QuickSuggest.rustBackend.isEnabled) { - this.#log("forceSync", "Syncing Rust backend"); - await lazy.QuickSuggest.rustBackend._test_ingest(); - this.#log("forceSync", "Done syncing Rust backend"); - } - if (lazy.QuickSuggest.jsBackend.isEnabled) { - this.#log("forceSync", "Syncing JS backend"); - await lazy.QuickSuggest.jsBackend._test_syncAll(); - this.#log("forceSync", "Done syncing JS backend"); - } - this.#log("forceSync", "Done"); + await this.#mockRemoteSettings.update({ config }); } /** @@ -484,8 +652,7 @@ class _QuickSuggestTestUtils { ); let { result } = details; - this.#log( - "assertIsQuickSuggest", + this.info?.( `Checking actual result at index ${index}: ` + JSON.stringify(result) ); @@ -694,8 +861,7 @@ class _QuickSuggestTestUtils { for (let i = 0; i < pings.length; i++) { let ping = pings[i]; - this.#log( - "assertPings", + this.info?.( `Checking ping at index ${i}, expected is: ` + JSON.stringify(ping) ); @@ -719,7 +885,7 @@ class _QuickSuggestTestUtils { // Check the payload. let actualPayload = call.args[0]; - this.#assertPingPayload(actualPayload, payload); + this._assertPingPayload(actualPayload, payload); } spy.resetHistory(); @@ -736,9 +902,8 @@ class _QuickSuggestTestUtils { * values. Function values are called and passed the corresponding actual * values and should return true if the actual values are correct. */ - #assertPingPayload(actualPayload, expectedPayload) { - this.#log( - "#assertPingPayload", + _assertPingPayload(actualPayload, expectedPayload) { + this.info?.( "Checking ping payload. Actual: " + JSON.stringify(actualPayload) + " -- Expected (excluding function properties): " + @@ -801,10 +966,7 @@ class _QuickSuggestTestUtils { urls[key] = { url, value, timestamp }; } - this.#log( - "assertTimestampsReplaced", - "Parsed timestamps: " + JSON.stringify(urls) - ); + this.info?.("Parsed timestamps: " + JSON.stringify(urls)); // Make a set of unique timestamp strings. There should only be one. let { timestamp } = Object.values(urls)[0]; @@ -860,7 +1022,7 @@ class _QuickSuggestTestUtils { * The experiment cleanup function (async). */ async enrollExperiment({ valueOverrides = {} }) { - this.#log("enrollExperiment", "Awaiting ExperimentAPI.ready"); + this.info?.("Awaiting ExperimentAPI.ready"); await lazy.ExperimentAPI.ready(); // Wait for any prior scenario updates to finish. If updates are ongoing, @@ -878,22 +1040,16 @@ class _QuickSuggestTestUtils { }); // Wait for the pref updates triggered by the experiment enrollment. - this.#log( - "enrollExperiment", - "Awaiting update after enrolling in experiment" - ); + this.info?.("Awaiting update after enrolling in experiment"); await this.waitForScenarioUpdated(); return async () => { - this.#log("enrollExperiment.cleanup", "Awaiting experiment cleanup"); + this.info?.("Awaiting experiment cleanup"); await doExperimentCleanup(); // The same pref updates will be triggered by unenrollment, so wait for // them again. - this.#log( - "enrollExperiment.cleanup", - "Awaiting update after unenrolling in experiment" - ); + this.info?.("Awaiting update after unenrolling in experiment"); await this.waitForScenarioUpdated(); }; } @@ -965,8 +1121,7 @@ class _QuickSuggestTestUtils { */ async withLocales(locales, callback) { let promiseChanges = async desiredLocales => { - this.#log( - "withLocales", + this.info?.( "Changing locales from " + JSON.stringify(Services.locale.requestedLocales) + " to " + @@ -978,38 +1133,32 @@ class _QuickSuggestTestUtils { return; } - this.#log("withLocales", "Waiting for intl:requested-locales-changed"); + this.info?.("Waiting for intl:requested-locales-changed"); await lazy.TestUtils.topicObserved("intl:requested-locales-changed"); - this.#log("withLocales", "Observed intl:requested-locales-changed"); + this.info?.("Observed intl:requested-locales-changed"); // Wait for the search service to reload engines. Otherwise tests can fail // in strange ways due to internal search service state during shutdown. // It won't always reload engines but it's hard to tell in advance when it // won't, so also set a timeout. - this.#log("withLocales", "Waiting for TOPIC_SEARCH_SERVICE"); + this.info?.("Waiting for TOPIC_SEARCH_SERVICE"); await Promise.race([ lazy.TestUtils.topicObserved( lazy.SearchUtils.TOPIC_SEARCH_SERVICE, (subject, data) => { - this.#log( - "withLocales", - "Observed TOPIC_SEARCH_SERVICE with data: " + data - ); + this.info?.("Observed TOPIC_SEARCH_SERVICE with data: " + data); return data == "engines-reloaded"; } ), new Promise(resolve => { lazy.setTimeout(() => { - this.#log( - "withLocales", - "Timed out waiting for TOPIC_SEARCH_SERVICE" - ); + this.info?.("Timed out waiting for TOPIC_SEARCH_SERVICE"); resolve(); }, 2000); }), ]); - this.#log("withLocales", "Done waiting for locale changes"); + this.info?.("Done waiting for locale changes"); }; let available = Services.locale.availableLocales; @@ -1035,12 +1184,8 @@ class _QuickSuggestTestUtils { await promise; } - #log(fnName, msg) { - this.info?.(`QuickSuggestTestUtils.${fnName} ${msg}`); - } - - #remoteSettingsServer; - #restoreRemoteSettings; + #mockRemoteSettings = null; + #mockRustSuggest = null; } export var QuickSuggestTestUtils = new _QuickSuggestTestUtils(); diff --git a/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs b/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs deleted file mode 100644 index 32b42198c390..000000000000 --- a/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs +++ /dev/null @@ -1,619 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/* eslint-disable jsdoc/require-param-description */ - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - HttpError: "resource://testing-common/httpd.sys.mjs", - HttpServer: "resource://testing-common/httpd.sys.mjs", - HTTP_404: "resource://testing-common/httpd.sys.mjs", - Log: "resource://gre/modules/Log.sys.mjs", -}); - -const SERVER_PREF = "services.settings.server"; - -/** - * A remote settings server. Tested with the desktop and Rust remote settings - * clients. - */ -export class RemoteSettingsServer { - /** - * The server must be started by calling `start()`. - * - * @param {object} options - * @param {number} options.logLevel - * A `Log.Level` value from `Log.sys.mjs`. `Log.Level.Info` logs basic info - * on requests and responses like paths and status codes. Pass - * `Log.Level.Debug` to log more info like headers, response bodies, and - * added and removed records. - */ - constructor({ logLevel = lazy.Log.Level.Info } = {}) { - this.#log = lazy.Log.repository.getLogger("RemoteSettingsServer"); - this.#log.level = logLevel; - - // Use `DumpAppender` instead of `ConsoleAppender`. The xpcshell and browser - // test harnesses buffer console messages and log them later, which makes it - // really hard to debug problems. `DumpAppender` logs to stdout, which the - // harnesses log immediately. - this.#log.addAppender( - new lazy.Log.DumpAppender(new lazy.Log.BasicFormatter()) - ); - } - - /** - * @returns {URL} - * The server's URL. Null when the server is stopped. - */ - get url() { - return this.#url; - } - - /** - * Starts the server and sets the `services.settings.server` pref to its - * URL. The server's `url` property will be non-null on return. - */ - async start() { - this.#log.info("Starting"); - - if (this.#url) { - this.#log.info("Already started at " + this.#url); - return; - } - - if (!this.#server) { - this.#server = new lazy.HttpServer(); - this.#server.registerPrefixHandler("/", this); - } - this.#server.start(-1); - - this.#url = new URL("http://localhost/v1"); - this.#url.port = this.#server.identity.primaryPort; - - this.#originalServerPrefValue = Services.prefs.getCharPref( - SERVER_PREF, - null - ); - Services.prefs.setCharPref(SERVER_PREF, this.#url.toString()); - - this.#log.info("Server is now started at " + this.#url); - } - - /** - * Stops the server and clears the `services.settings.server` pref. The - * server's `url` property will be null on return. - */ - async stop() { - this.#log.info("Stopping"); - - if (!this.#url) { - this.#log.info("Already stopped"); - return; - } - - await this.#server.stop(); - this.#url = null; - - if (this.#originalServerPrefValue === null) { - Services.prefs.clearUserPref(SERVER_PREF); - } else { - Services.prefs.setCharPref(SERVER_PREF, this.#originalServerPrefValue); - } - - this.#log.info("Server is now stopped"); - } - - /** - * Adds remote settings records to the server. Records may have attachments; - * see the param doc below. - * - * @param {object} options - * @param {string} options.bucket - * @param {string} options.collection - * @param {Array} options.records - * Each object in this array should be a realistic remote settings record - * with the following exceptions: - * - * - `record.id` will be generated if it's undefined. - * - `record.last_modified` will be set to the `#lastModified` property of - * the server if it's undefined. - * - `record.attachment`, if defined, should be the attachment itself and - * not its metadata. The server will automatically create some dummy - * metadata. Currently the only supported attachment type is plain - * JSON'able objects that the server will convert to JSON in responses. - */ - async addRecords({ bucket = "main", collection = "test", records }) { - this.#log.debug( - "Adding records: " + - JSON.stringify({ bucket, collection, records }, null, 2) - ); - - this.#lastModified++; - - let key = this.#recordsKey(bucket, collection); - let allRecords = this.#records.get(key); - if (!allRecords) { - allRecords = []; - this.#records.set(key, allRecords); - } - - for (let record of records) { - let copy = { ...record }; - if (!copy.hasOwnProperty("id")) { - copy.id = String(this.#nextRecordId++); - } - if (!copy.hasOwnProperty("last_modified")) { - copy.last_modified = this.#lastModified; - } - if (copy.attachment) { - await this.#addAttachment({ bucket, collection, record: copy }); - } - allRecords.push(copy); - } - - this.#log.debug( - "Done adding records. All records are now: " + - JSON.stringify([...this.#records.entries()], null, 2) - ); - } - - /** - * Marks records as deleted. Deleted records will still be returned in - * responses, but they'll have a `deleted = true` property. Their attachments - * will be deleted immediately, however. - * - * @param {object} filter - * If null, all records will be marked as deleted. Otherwise only records - * that match the filter will be marked as deleted. For a given record, each - * value in the filter object will be compared to the value with the same - * key in the record. If all values are the same, the record will be - * removed. Examples: - * - * To remove remove records whose `type` key has the value "data": - * `{ type: "data" } - * - * To remove remove records whose `type` key has the value "data" and whose - * `last_modified` key has the value 1234: - * `{ type: "data", last_modified: 1234 } - */ - removeRecords(filter = null) { - this.#log.debug("Removing records: " + JSON.stringify({ filter })); - - this.#lastModified++; - - for (let [recordsKey, records] of this.#records.entries()) { - for (let record of records) { - if ( - !filter || - Object.entries(filter).every( - ([filterKey, filterValue]) => - record.hasOwnProperty(filterKey) && - record[filterKey] == filterValue - ) - ) { - if (record.attachment) { - let attachmentKey = `${recordsKey}/${record.attachment.filename}`; - this.#attachments.delete(attachmentKey); - } - record.deleted = true; - record.last_modified = this.#lastModified; - } - } - } - - this.#log.debug( - "Done removing records. All records are now: " + - JSON.stringify([...this.#records.entries()], null, 2) - ); - } - - /** - * Removes all existing records and adds the given records to the server. - * - * @param {object} options - * @param {string} options.bucket - * @param {string} options.collection - * @param {Array} options.records - * See `addRecords()`. - */ - async setRecords({ bucket = "main", collection = "test", records }) { - this.#log.debug("Setting records"); - - this.removeRecords(); - await this.addRecords({ bucket, collection, records }); - - this.#log.debug("Done setting records"); - } - - /** - * `nsIHttpRequestHandler` callback from the backing server. Handles a - * request. - * - * @param {nsIHttpRequest} request - * @param {nsIHttpResponse} response - */ - handle(request, response) { - this.#logRequest(request); - - // Get the route that matches the request path. - let { match, route } = this.#getRoute(request.path) || {}; - if (!route) { - this.#prepareError({ request, response, error: lazy.HTTP_404 }); - return; - } - - let respInfo = route.response(match, request, response); - if (respInfo instanceof lazy.HttpError) { - this.#prepareError({ request, response, error: respInfo }); - } else { - this.#prepareResponse({ ...respInfo, request, response }); - } - } - - /** - * @returns {Array} - * The routes handled by the server. Each item in this array is an object - * with the following properties that describes one or more paths and the - * response that should be sent when a request is made on those paths: - * - * {string} spec - * A path spec. This is required unless `specs` is defined. To determine - * which route should be used for a given request, the server will check - * each route's spec(s) until it finds the first that matches the - * request's path. A spec is just a path whose components can be variables - * that start with "$". When a spec with variables matches a request path, - * the `match` object passed to the route's `response` function will map - * from variable names to the corresponding components in the path. - * {Array} specs - * An array of path spec strings. Use this instead of `spec` if the route - * handles more than one. - * {function} response - * A function that will be called when the route matches a request. It is - * called as: `response(match, request, response)` - * - * {object} match - * An object mapping variable names in the spec to their matched - * components in the path. See `#match()` for details. - * {nsIHttpRequest} request - * {nsIHttpResponse} response - * - * The function must return one of the following: - * - * {object} - * An object that describes the response with the following properties: - * {object} body - * A plain JSON'able object. The server will convert this to JSON and - * set it to the response body. - * {HttpError} - * An `HttpError` instance defined in `httpd.sys.mjs`. - */ - get #routes() { - return [ - { - spec: "/v1", - response: () => ({ - body: { - capabilities: { - attachments: { - base_url: this.#url.toString(), - }, - }, - }, - }), - }, - - { - spec: "/v1/buckets/monitor/collections/changes/changeset", - response: () => ({ - body: { - timestamp: this.#lastModified, - changes: [ - { - last_modified: this.#lastModified, - }, - ], - }, - }), - }, - - { - spec: "/v1/buckets/$bucket/collections/$collection/changeset", - response: ({ bucket, collection }) => { - let records = this.#getRecords(bucket, collection); - return !records - ? lazy.HTTP_404 - : { - body: { - metadata: null, - timestamp: this.#lastModified, - changes: records, - }, - }; - }, - }, - - { - spec: "/v1/buckets/$bucket/collections/$collection/records", - response: ({ bucket, collection }) => { - let records = this.#getRecords(bucket, collection); - return !records - ? lazy.HTTP_404 - : { - body: { - data: records, - }, - }; - }, - }, - - { - specs: [ - // The Rust remote settings client doesn't include "v1" in attachment - // URLs, but the JS client does. - "/attachments/$bucket/$collection/$filename", - "/v1/attachments/$bucket/$collection/$filename", - ], - response: ({ bucket, collection, filename }) => { - return { - body: this.#getAttachment(bucket, collection, filename), - }; - }, - }, - ]; - } - - /** - * @returns {object} - * Default response headers. - */ - get #responseHeaders() { - return { - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": - "Retry-After, Content-Length, Alert, Backoff", - Server: "waitress", - Etag: `"${this.#lastModified}"`, - }; - } - - /** - * Returns the route that matches a request path. - * - * @param {string} path - * A request path. - * @returns {object} - * If no route matches the path, returns an empty object. Otherwise returns - * an object with the following properties: - * - * {object} match - * An object describing the matched variables in the route spec. See - * `#match()` for details. - * {object} route - * The matched route. See `#routes` for details. - */ - #getRoute(path) { - for (let route of this.#routes) { - let specs = route.specs || [route.spec]; - for (let spec of specs) { - let match = this.#match(path, spec); - if (match) { - return { match, route }; - } - } - } - return {}; - } - - /** - * Matches a request path to a route spec. - * - * @param {string} path - * A request path. - * @param {string} spec - * A route spec. See `#routes` for details. - * @returns {object|null} - * If the spec doesn't match the path, returns null. Otherwise returns an - * object mapping variable names in the spec to their matched components in - * the path. Example: - * - * path : "/main/myfeature/foo" - * spec : "/$bucket/$collection/foo" - * returns: `{ bucket: "main", collection: "myfeature" }` - */ - #match(path, spec) { - let pathParts = path.split("/"); - let specParts = spec.split("/"); - - if (pathParts.length != specParts.length) { - // If the path has only one more part than the spec and its last part is - // empty, then the path ends in a trailing slash but the spec does not. - // Consider that a match. Otherwise return null for no match. - if ( - pathParts[pathParts.length - 1] || - pathParts.length != specParts.length + 1 - ) { - return null; - } - pathParts.pop(); - } - - let match = {}; - for (let i = 0; i < pathParts.length; i++) { - let pathPart = pathParts[i]; - let specPart = specParts[i]; - if (specPart.startsWith("$")) { - match[specPart.substring(1)] = pathPart; - } else if (pathPart != specPart) { - return null; - } - } - - return match; - } - - #getRecords(bucket, collection) { - return this.#records.get(this.#recordsKey(bucket, collection)); - } - - #recordsKey(bucket, collection) { - return `${bucket}/${collection}`; - } - - /** - * Registers an attachment for a record. - * - * @param {object} options - * @param {string} options.bucket - * @param {string} options.collection - * @param {object} options.record - * The record should have an `attachment` property as described in - * `addRecords()`. - */ - async #addAttachment({ bucket, collection, record }) { - let { attachment } = record; - let filename = record.id; - - this.#attachments.set( - this.#attachmentsKey(bucket, collection, filename), - attachment - ); - - let encoder = new TextEncoder(); - let bytes = encoder.encode(JSON.stringify(attachment)); - - let hashBuffer = await crypto.subtle.digest("SHA-256", bytes); - let hashBytes = new Uint8Array(hashBuffer); - let toHex = b => b.toString(16).padStart(2, "0"); - let hash = Array.from(hashBytes, toHex).join(""); - - // Replace `record.attachment` with appropriate metadata in order to conform - // with the remote settings API. - record.attachment = { - hash, - filename, - mimetype: "application/json; charset=UTF-8", - size: bytes.length, - location: `attachments/${bucket}/${collection}/${filename}`, - }; - } - - #attachmentsKey(bucket, collection, filename) { - return `${bucket}/${collection}/${filename}`; - } - - #getAttachment(bucket, collection, filename) { - return this.#attachments.get( - this.#attachmentsKey(bucket, collection, filename) - ); - } - - /** - * Prepares an HTTP response. - * - * @param {object} options - * @param {nsIHttpRequest} options.request - * @param {nsIHttpResponse} options.response - * @param {object|null} options.body - * Currently only JSON'able objects are supported. They will be converted to - * JSON in the response. - * @param {integer} options.status - * @param {string} options.statusText - */ - #prepareResponse({ - request, - response, - body = null, - status = 200, - statusText = "OK", - }) { - let headers = { ...this.#responseHeaders }; - if (body) { - headers["Content-Type"] = "application/json; charset=UTF-8"; - } - - this.#logResponse({ request, status, statusText, headers, body }); - - for (let [name, value] of Object.entries(headers)) { - response.setHeader(name, value, false); - } - if (body) { - response.write(JSON.stringify(body)); - } - response.setStatusLine(request.httpVersion, status, statusText); - } - - /** - * Prepares an HTTP error response. - * - * @param {object} options - * @param {nsIHttpRequest} options.request - * @param {nsIHttpResponse} options.response - * @param {HttpError} options.error - * An `HttpError` instance defined in `httpd.sys.mjs`. - */ - #prepareError({ request, response, error }) { - this.#prepareResponse({ - request, - response, - status: error.code, - statusText: error.description, - }); - } - - /** - * Logs a request. - * - * @param {nsIHttpRequest} request - */ - #logRequest(request) { - let pathAndQuery = request.path; - if (request.queryString) { - pathAndQuery += "?" + request.queryString; - } - this.#log.info( - `< HTTP ${request.httpVersion} ${request.method} ${pathAndQuery}` - ); - for (let name of request.headers) { - this.#log.debug(`${name}: ${request.getHeader(name.toString())}`); - } - } - - /** - * Logs a response. - * - * @param {object} options - * @param {nsIHttpRequest} options.request - * The associated request. - * @param {integer} options.status - * The HTTP status code of the response. - * @param {string} options.statusText - * The description of the status code. - * @param {object} options.headers - * An object mapping from response header names to values. - * @param {object} options.body - * The response body, if any. - */ - #logResponse({ request, status, statusText, headers, body }) { - this.#log.info(`> ${status} ${request.path}`); - for (let [name, value] of Object.entries(headers)) { - this.#log.debug(`${name}: ${value}`); - } - if (body) { - this.#log.debug("Response body: " + JSON.stringify(body, null, 2)); - } - } - - // records key (see `#recordsKey()`) -> array of record objects - #records = new Map(); - - // attachments key (see `#attachmentsKey()`) -> attachment object - #attachments = new Map(); - - #log; - #server; - #originalServerPrefValue; - #url = null; - #lastModified = 1368273600000; - #nextRecordId = 1; -} diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js index 130afe8c53e3..be07b28be648 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js @@ -18,8 +18,6 @@ const REMOTE_SETTINGS_RESULTS = [ click_url: "http://click.reporting.test.com/", impression_url: "http://impression.reporting.test.com/", advertiser: "TestAdvertiser", - iab_category: "22 - Shopping", - icon: "1234", }, { id: 2, @@ -28,9 +26,8 @@ const REMOTE_SETTINGS_RESULTS = [ keywords: ["nonspon"], click_url: "http://click.reporting.test.com/nonsponsored", impression_url: "http://impression.reporting.test.com/nonsponsored", - advertiser: "Wikipedia", + advertiser: "TestAdvertiserNonSponsored", iab_category: "5 - Education", - icon: "1234", }, ]; @@ -40,7 +37,7 @@ add_setup(async function () { await UrlbarTestUtils.formHistory.clear(); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, @@ -50,7 +47,7 @@ add_setup(async function () { }); // Tests a sponsored result and keyword highlighting. -add_tasks_with_rust(async function sponsored() { +add_task(async function sponsored() { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "fra", @@ -76,7 +73,7 @@ add_tasks_with_rust(async function sponsored() { }); // Tests a non-sponsored result. -add_tasks_with_rust(async function nonSponsored() { +add_task(async function nonSponsored() { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "nonspon", @@ -91,7 +88,7 @@ add_tasks_with_rust(async function nonSponsored() { }); // Tests sponsored priority feature. -add_tasks_with_rust(async function sponsoredPriority() { +add_task(async function sponsoredPriority() { const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({ quickSuggestSponsoredPriority: true, }); @@ -134,33 +131,31 @@ add_tasks_with_rust(async function sponsoredPriority() { }); // Tests sponsored priority feature does not affect to non-sponsored suggestion. -add_tasks_with_rust( - async function sponsoredPriorityButNotSponsoredSuggestion() { - const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({ - quickSuggestSponsoredPriority: true, - }); +add_task(async function sponsoredPriorityButNotSponsoredSuggestion() { + const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({ + quickSuggestSponsoredPriority: true, + }); - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "nonspon", - }); - await QuickSuggestTestUtils.assertIsQuickSuggest({ - window, - index: 1, - isSponsored: false, - url: `${TEST_URL}?q=nonsponsored`, - }); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "nonspon", + }); + await QuickSuggestTestUtils.assertIsQuickSuggest({ + window, + index: 1, + isSponsored: false, + url: `${TEST_URL}?q=nonsponsored`, + }); - let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1); - let before = window.getComputedStyle(row, "::before"); - Assert.equal(before.content, "attr(label)", "::before.content is enabled"); - Assert.equal( - row.getAttribute("label"), - "Firefox Suggest", - "Row has general group label for quick suggest" - ); + let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1); + let before = window.getComputedStyle(row, "::before"); + Assert.equal(before.content, "attr(label)", "::before.content is enabled"); + Assert.equal( + row.getAttribute("label"), + "Firefox Suggest", + "Row has general group label for quick suggest" + ); - await UrlbarTestUtils.promisePopupClose(window); - await cleanUpNimbus(); - } -); + await UrlbarTestUtils.promisePopupClose(window); + await cleanUpNimbus(); +}); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js index 1499fd6ece4d..68fd3a1592e2 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js @@ -79,12 +79,11 @@ const TEST_MERINO_SUGGESTIONS = [ add_setup(async function () { await SpecialPowers.pushPrefEnv({ - set: [ - // Disable search suggestions so we don't hit the network. - ["browser.search.suggest.enabled", false], - ], + set: [["browser.urlbar.quicksuggest.enabled", true]], }); + await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); + await QuickSuggestTestUtils.ensureQuickSuggestInit({ merinoSuggestions: TEST_MERINO_SUGGESTIONS, }); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js index 556178cdf01c..b0ab46762f03 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js @@ -2,9 +2,6 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests quick suggest dismissals ("blocks"). -// -// TODO (bug 1859389): Make this work with Rust enabled once timestamp templates -// are handled. "use strict"; @@ -59,7 +56,7 @@ add_setup(async function () { Services.telemetry.clearEvents(); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js index e7779d0a20ae..282e1a2ba006 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_indexes.js @@ -43,10 +43,12 @@ const REMOTE_SETTINGS_RESULTS = [ }, ]; -// Trying to avoid timeouts. -requestLongerTimeout(3); - add_setup(async function () { + // This test intermittently times out on Mac TV WebRender. + if (AppConstants.platform == "macosx") { + requestLongerTimeout(3); + } + await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); await UrlbarTestUtils.formHistory.clear(); @@ -55,7 +57,7 @@ add_setup(async function () { await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js index 112af8f943b0..439efe90dd29 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js @@ -21,13 +21,25 @@ const REMOTE_SETTINGS_DATA = [ ]; add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.quicksuggest.enabled", true], + ["browser.urlbar.quicksuggest.nonsponsored", true], + ["browser.urlbar.suggest.mdn", true], + ], + }); + await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: REMOTE_SETTINGS_DATA, - prefs: [["mdn.featureGate", true]], + remoteSettingsResults: REMOTE_SETTINGS_DATA, }); }); add_task(async function basic() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.mdn.featureGate", true]], + }); + await waitForSuggestions(); + const suggestion = REMOTE_SETTINGS_DATA[0].attachment[0]; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, @@ -56,10 +68,15 @@ add_task(async function basic() { Assert.ok(true, "Expected page is loaded"); await PlacesUtils.history.clear(); + await SpecialPowers.popPrefEnv(); }); // Tests the row/group label. add_task(async function rowLabel() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.mdn.featureGate", true]], + }); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: REMOTE_SETTINGS_DATA[0].attachment[0].keywords[0], @@ -71,6 +88,7 @@ add_task(async function rowLabel() { Assert.equal(row.getAttribute("label"), "Recommended resource"); await UrlbarTestUtils.promisePopupClose(window); + await SpecialPowers.popPrefEnv(); }); add_task(async function disable() { @@ -103,7 +121,7 @@ add_task(async function resultMenu_notInterested() { // Re-enable suggestions and wait until MDNSuggestions syncs them from // remote settings again. UrlbarPrefs.set("suggest.mdn", true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); }); // Tests the "Not relevant" result menu dismissal command. @@ -120,6 +138,11 @@ add_task(async function notRelevant() { }); async function doDismissTest(command) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.mdn.featureGate", true]], + }); + await waitForSuggestions(); + const keyword = REMOTE_SETTINGS_DATA[0].attachment[0].keywords[0]; // Do a search. await UrlbarTestUtils.promiseAutocompleteResultPopup({ @@ -223,4 +246,14 @@ async function doDismissTest(command) { } await UrlbarTestUtils.promisePopupClose(window); + await SpecialPowers.popPrefEnv(); +} + +async function waitForSuggestions() { + const keyword = REMOTE_SETTINGS_DATA[0].attachment[0].keywords[0]; + const feature = QuickSuggest.getFeature("MDNSuggestions"); + await TestUtils.waitForCondition(async () => { + const suggestions = await feature.queryRemoteSettings(keyword); + return !!suggestions.length; + }, "Waiting for MDNSuggestions to serve remote settings suggestions"); } diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js index 6337c8da0700..70c9b068c9d8 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js @@ -35,11 +35,6 @@ if (AppConstants.platform === "macosx") { let gCanTabMoveFocus; add_setup(async function () { gCanTabMoveFocus = await canTabMoveFocus(); - - // Ensure the test remote settings server is set up. This test doesn't trigger - // any suggestions but it enables Suggest, which will attempt to sync from - // remote settings. - await QuickSuggestTestUtils.ensureQuickSuggestInit(); }); // When the user has already enabled the data-collection pref, the dialog should diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js index eb7a23a714c5..f15dac0c168a 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js @@ -4,12 +4,6 @@ "use strict"; // Browser tests for Pocket suggestions. -// -// TODO: Make this work with Rust enabled. Right now, running this test with -// Rust hits the following error on ingest, which prevents ingest from finishing -// successfully: -// -// 0:03.17 INFO Console message: [JavaScript Error: "1698289045697 urlbar ERROR QuickSuggest.SuggestBackendRust :: Ingest error: Error executing SQL: FOREIGN KEY constraint failed" {file: "resource://gre/modules/Log.sys.mjs" line: 722}] // The expected index of the Pocket suggestion. const EXPECTED_RESULT_INDEX = 1; @@ -30,20 +24,24 @@ const REMOTE_SETTINGS_DATA = [ ]; add_setup(async function () { + // This must be done before enabling the feature (using the `featureGate` + // pref) so that the mock remote settings are set up first. Also, don't pass + // in the remote settings data yet; see below. + await QuickSuggestTestUtils.ensureQuickSuggestInit(); + await SpecialPowers.pushPrefEnv({ set: [ + ["browser.urlbar.quicksuggest.enabled", true], + ["browser.urlbar.suggest.quicksuggest.nonsponsored", true], + ["browser.urlbar.pocket.featureGate", true], // Disable search suggestions so we don't hit the network. ["browser.search.suggest.enabled", false], ], }); - await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: REMOTE_SETTINGS_DATA, - prefs: [ - ["suggest.quicksuggest.nonsponsored", true], - ["pocket.featureGate", true], - ], - }); + // Now that the feature is enabled, set the remote settings data to force the + // feature to sync so we can be sure syncing is done before starting the test. + await QuickSuggestTestUtils.setRemoteSettingsResults(REMOTE_SETTINGS_DATA); }); add_task(async function basic() { @@ -95,9 +93,6 @@ add_task(async function basic() { }); // Tests the "Show less frequently" command. -// -// TODO (bug 1861220, bug 1861228): The Rust implementation doesn't support -// prefix matching or the remote settings config. add_task(async function resultMenu_showLessFrequently() { await SpecialPowers.pushPrefEnv({ set: [ @@ -241,7 +236,11 @@ add_task(async function resultMenu_notInterested() { // Re-enable suggestions and wait until PocketSuggestions syncs them from // remote settings again. UrlbarPrefs.set("suggest.pocket", true); - await QuickSuggestTestUtils.forceSync(); + let feature = QuickSuggest.getFeature("PocketSuggestions"); + await TestUtils.waitForCondition(async () => { + let suggestions = await feature.queryRemoteSettings("pocket suggestion"); + return !!suggestions.length; + }, "Waiting for PocketSuggestions to serve remote settings suggestions"); }); // Tests the "Not relevant" result menu dismissal command. @@ -389,9 +388,6 @@ add_task(async function rowLabel() { }); // Tests visibility of "Show less frequently" menu. -// -// TODO (bug 1861220, bug 1861228): The Rust implementation doesn't support -// prefix matching or the remote settings config. add_task(async function showLessFrequentlyMenuVisibility() { const testCases = [ // high confidence keyword best match diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js index 1d3ed72b1d74..4f41d94d465a 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js @@ -57,7 +57,7 @@ add_setup(async function () { await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js index 9a1aa06c0268..fd81b64fda62 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js @@ -21,19 +21,15 @@ const REMOTE_SETTINGS_RESULT = { keywords: ["nonsponsored"], advertiser: "Wikipedia", iab_category: "5 - Education", - icon: "1234", }; const suggestion_type = "nonsponsored"; const index = 1; const position = index + 1; -// Trying to avoid timeouts in TV mode. -requestLongerTimeout(3); - add_setup(async function () { await setUpTelemetryTest({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: [REMOTE_SETTINGS_RESULT], diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_other.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_other.js index d40c70107e3a..f697628e99e2 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_other.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_other.js @@ -36,7 +36,7 @@ add_setup(async function () { await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js index 7c477e8af753..2ec780759256 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js @@ -23,19 +23,15 @@ const REMOTE_SETTINGS_RESULT = { impression_url: "https://example.com/impression", advertiser: "testadvertiser", iab_category: "22 - Shopping", - icon: "1234", }; const suggestion_type = "sponsored"; const index = 1; const position = index + 1; -// Trying to avoid timeouts in TV mode. -requestLongerTimeout(3); - add_setup(async function () { await setUpTelemetryTest({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: [REMOTE_SETTINGS_RESULT], diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js index d17f4001ee9a..be5741db029c 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js @@ -29,7 +29,8 @@ add_setup(async function () { }); await setUpTelemetryTest({ - remoteSettingsRecords: [ + suggestions: [], + remoteSettingsResults: [ { type: "weather", weather: WEATHER_RS_DATA, diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js index 23cff79ed266..383080c56665 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js @@ -13,7 +13,7 @@ ChromeUtils.defineESModuleGetters(this, { add_setup(async function () { await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, @@ -85,7 +85,7 @@ add_task(async function test_weather_result_selection() { // repeats both steps until the min keyword length cap is reached. add_task(async function showLessFrequentlyCapReached_manySearches() { // Set up a min keyword length and cap. - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: { @@ -172,7 +172,7 @@ add_task(async function showLessFrequentlyCapReached_manySearches() { gURLBar.view.resultMenu.hidePopup(true); await UrlbarTestUtils.promisePopupClose(window); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, @@ -185,7 +185,7 @@ add_task(async function showLessFrequentlyCapReached_manySearches() { // a single search until the min keyword length cap is reached. add_task(async function showLessFrequentlyCapReached_oneSearch() { // Set up a min keyword length and cap. - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: { @@ -245,7 +245,7 @@ add_task(async function showLessFrequentlyCapReached_oneSearch() { gURLBar.view.resultMenu.hidePopup(true); await UrlbarTestUtils.promisePopupClose(window); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, diff --git a/browser/components/urlbar/tests/quicksuggest/browser/head.js b/browser/components/urlbar/tests/quicksuggest/browser/head.js index 3ceea5d4603f..c3f20d7ebb18 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/head.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/head.js @@ -84,15 +84,21 @@ async function updateTopSites(condition, searchShortcuts = false) { * * @param {object} options * Options - * @param {Array} options.remoteSettingsRecords - * See `QuickSuggestTestUtils.ensureQuickSuggestInit()`. + * @param {Array} options.remoteSettingsResults + * Array of remote settings result objects. If not given, no suggestions + * will be present in remote settings. * @param {Array} options.merinoSuggestions - * See `QuickSuggestTestUtils.ensureQuickSuggestInit()`. + * Array of Merino suggestion objects. If given, this function will start + * the mock Merino server and set `quicksuggest.dataCollection.enabled` to + * true so that `UrlbarProviderQuickSuggest` will fetch suggestions from it. + * Otherwise Merino will not serve suggestions, but you can still set up + * Merino without using this function by using `MerinoTestUtils` directly. * @param {Array} options.config - * See `QuickSuggestTestUtils.ensureQuickSuggestInit()`. + * Quick suggest will be initialized with this config. Leave undefined to use + * the default config. See `QuickSuggestTestUtils` for details. */ async function setUpTelemetryTest({ - remoteSettingsRecords, + remoteSettingsResults, merinoSuggestions = null, config = QuickSuggestTestUtils.DEFAULT_CONFIG, }) { @@ -115,7 +121,7 @@ async function setUpTelemetryTest({ await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords, + remoteSettingsResults, merinoSuggestions, config, }); @@ -663,34 +669,19 @@ function add_tasks_with_rust(...args) { for (let rustEnabled of [false, true]) { let newTaskFn = async (...taskFnArgs) => { - info("add_tasks_with_rust: Setting rustEnabled: " + rustEnabled); + info("Setting rustEnabled: " + rustEnabled); UrlbarPrefs.set("quicksuggest.rustEnabled", rustEnabled); - info("add_tasks_with_rust: Done setting rustEnabled: " + rustEnabled); - - // The current backend may now start syncing, so wait for it to finish. - info("add_tasks_with_rust: Forcing sync"); - await QuickSuggestTestUtils.forceSync(); - info("add_tasks_with_rust: Done forcing sync"); + info("Done setting rustEnabled: " + rustEnabled); let rv; try { - info( - "add_tasks_with_rust: Calling original task function: " + taskFn.name - ); + info("Calling original task function: " + taskFn.name); rv = await taskFn(...taskFnArgs); } finally { - info( - "add_tasks_with_rust: Done calling original task function: " + - taskFn.name - ); - info("add_tasks_with_rust: Clearing rustEnabled"); + info("Done calling original task function: " + taskFn.name); + info("Clearing rustEnabled"); UrlbarPrefs.clear("quicksuggest.rustEnabled"); - info("add_tasks_with_rust: Done clearing rustEnabled"); - - // The current backend may now start syncing, so wait for it to finish. - info("add_tasks_with_rust: Forcing sync"); - await QuickSuggestTestUtils.forceSync(); - info("add_tasks_with_rust: Done forcing sync"); + info("Done clearing rustEnabled"); } return rv; }; diff --git a/browser/components/urlbar/tests/quicksuggest/unit/head.js b/browser/components/urlbar/tests/quicksuggest/unit/head.js index 67e381b77f9b..af67aef4a31b 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/head.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/head.js @@ -32,46 +32,19 @@ function add_tasks_with_rust(...args) { for (let rustEnabled of [false, true]) { let newTaskFn = async (...taskFnArgs) => { - info("add_tasks_with_rust: Setting rustEnabled: " + rustEnabled); + info("Setting rustEnabled: " + rustEnabled); UrlbarPrefs.set("quicksuggest.rustEnabled", rustEnabled); - info("add_tasks_with_rust: Done setting rustEnabled: " + rustEnabled); - - // The current backend may now start syncing, so wait for it to finish. - info("add_tasks_with_rust: Forcing sync"); - await QuickSuggestTestUtils.forceSync(); - info("add_tasks_with_rust: Done forcing sync"); + info("Done setting rustEnabled: " + rustEnabled); let rv; try { - info( - "add_tasks_with_rust: Calling original task function: " + taskFn.name - ); + info("Calling original task function: " + taskFn.name); rv = await taskFn(...taskFnArgs); - } catch (e) { - // Clearly report any unusual errors to make them easier to spot and to - // make the flow of the test clearer. The harness throws NS_ERROR_ABORT - // when a normal assertion fails, so don't report that. - if (e.result != Cr.NS_ERROR_ABORT) { - Assert.ok( - false, - "add_tasks_with_rust: The original task function threw an error: " + - e - ); - } - throw e; } finally { - info( - "add_tasks_with_rust: Done calling original task function: " + - taskFn.name - ); - info("add_tasks_with_rust: Clearing rustEnabled"); + info("Done calling original task function: " + taskFn.name); + info("Clearing rustEnabled"); UrlbarPrefs.clear("quicksuggest.rustEnabled"); - info("add_tasks_with_rust: Done clearing rustEnabled"); - - // The current backend may now start syncing, so wait for it to finish. - info("add_tasks_with_rust: Forcing sync"); - await QuickSuggestTestUtils.forceSync(); - info("add_tasks_with_rust: Done forcing sync"); + info("Done clearing rustEnabled"); } return rv; }; @@ -460,7 +433,7 @@ async function doShowLessFrequentlyTests({ keyword, }) { // Do some sanity checks on the keyword. Any checks that fail are errors in - // the test. + // the test. This function assumes let spaceIndex = keyword.indexOf(" "); if (spaceIndex < 0) { throw new Error("keyword must contain a space"); @@ -512,19 +485,16 @@ async function doShowLessFrequentlyTests({ }, ]; - // The Rust implementation doesn't support the remote settings config. - if (!UrlbarPrefs.get("quicksuggest.rustEnabled")) { - info("Testing 'show less frequently' with cap in remote settings"); - await doOneShowLessFrequentlyTest({ - tests, - feature, - expectedResult, - showLessFrequentlyCountPref, - rs: { - show_less_frequently_cap: 3, - }, - }); - } + info("Testing 'show less frequently' with cap in remote settings"); + await doOneShowLessFrequentlyTest({ + tests, + feature, + expectedResult, + showLessFrequentlyCountPref, + rs: { + show_less_frequently_cap: 3, + }, + }); // Nimbus should override remote settings. info("Testing 'show less frequently' with cap in Nimbus and remote settings"); diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js index b1079fcc8ba6..c1bc5f693eb3 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js @@ -32,7 +32,6 @@ const REMOTE_SETTINGS_RESULTS = [ impression_url: "http://example.com/amp-impression", advertiser: "Amp", iab_category: "22 - Shopping", - icon: "1234", }, { id: 2, @@ -43,7 +42,6 @@ const REMOTE_SETTINGS_RESULTS = [ impression_url: "http://example.com/wikipedia-impression", advertiser: "Wikipedia", iab_category: "5 - Education", - icon: "1234", }, { id: 3, @@ -54,7 +52,6 @@ const REMOTE_SETTINGS_RESULTS = [ impression_url: "http://example.com/http-impression", advertiser: "HttpAdvertiser", iab_category: "22 - Shopping", - icon: "1234", }, { id: 4, @@ -65,7 +62,6 @@ const REMOTE_SETTINGS_RESULTS = [ impression_url: "http://impression.reporting.test.com/prefix", advertiser: "TestAdvertiserPrefix", iab_category: "22 - Shopping", - icon: "1234", }, { id: 5, @@ -76,7 +72,6 @@ const REMOTE_SETTINGS_RESULTS = [ impression_url: "http://impression.reporting.test.com/timestamp", advertiser: "TestAdvertiserTimestamp", iab_category: "22 - Shopping", - icon: "1234", }, ]; @@ -128,6 +123,9 @@ function expectedHttpsResult() { } add_setup(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", false); + // Install a default test engine. let engine = await addTestSuggestionsEngine(); await Services.search.setDefault( @@ -140,7 +138,7 @@ add_setup(async function init() { ]; await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, @@ -183,7 +181,6 @@ add_task(async function telemetryType_nonsponsored() { add_tasks_with_rust(async function nonsponsoredOnly_match() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); - await QuickSuggestTestUtils.forceSync(); let context = createContext(NONSPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -214,7 +211,6 @@ add_tasks_with_rust(async function nonsponsoredOnly_match() { add_tasks_with_rust(async function nonsponsoredOnly_noMatch() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -227,7 +223,6 @@ add_tasks_with_rust(async function nonsponsoredOnly_noMatch() { add_tasks_with_rust(async function sponsoredOnly_sponsored() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -258,7 +253,6 @@ add_tasks_with_rust(async function sponsoredOnly_sponsored() { add_tasks_with_rust(async function sponsoredOnly_nonsponsored() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(NONSPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -272,7 +266,6 @@ add_tasks_with_rust(async function sponsoredOnly_nonsponsored() { add_tasks_with_rust(async function both_sponsored() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -289,7 +282,6 @@ add_tasks_with_rust(async function both_sponsored() { add_tasks_with_rust(async function both_nonsponsored() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(NONSPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -306,7 +298,6 @@ add_tasks_with_rust(async function both_nonsponsored() { add_tasks_with_rust(async function both_noMatch() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext("this doesn't match anything", { providers: [UrlbarProviderQuickSuggest.name], @@ -345,7 +336,6 @@ add_tasks_with_rust(async function neither_nonsponsored() { add_tasks_with_rust(async function caseInsensitiveAndLeadingSpaces() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(" " + SPONSORED_SEARCH_STRING.toUpperCase(), { providers: [UrlbarProviderQuickSuggest.name], @@ -362,7 +352,6 @@ add_tasks_with_rust(async function caseInsensitiveAndLeadingSpaces() { add_tasks_with_rust(async function emptySearchStringsAndSpaces() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let searchStrings = ["", " ", " ", " "]; for (let str of searchStrings) { @@ -390,7 +379,6 @@ add_tasks_with_rust(async function browser_search_suggest_enabled() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); UrlbarPrefs.set("browser.search.suggest.enabled", false); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -410,7 +398,6 @@ add_tasks_with_rust(async function browser_search_suggest_enabled() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); UrlbarPrefs.set("suggest.searches", false); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -429,7 +416,6 @@ add_tasks_with_rust(async function browser_search_suggest_enabled() { add_tasks_with_rust(async function privateContext() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); for (let privateSuggestionsEnabled of [true, false]) { UrlbarPrefs.set( @@ -457,7 +443,6 @@ add_tasks_with_rust(async function suggestionsBeforeGeneral_only() { UrlbarPrefs.set("browser.search.suggest.enabled", true); UrlbarPrefs.set("suggest.searches", true); UrlbarPrefs.set("showSearchSuggestionsFirst", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false }); await check_results({ @@ -496,7 +481,6 @@ add_tasks_with_rust(async function suggestionsBeforeGeneral_others() { UrlbarPrefs.set("browser.search.suggest.enabled", true); UrlbarPrefs.set("suggest.searches", true); UrlbarPrefs.set("showSearchSuggestionsFirst", true); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false }); @@ -552,7 +536,6 @@ add_tasks_with_rust(async function generalBeforeSuggestions_only() { UrlbarPrefs.set("browser.search.suggest.enabled", true); UrlbarPrefs.set("suggest.searches", true); UrlbarPrefs.set("showSearchSuggestionsFirst", false); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false }); await check_results({ @@ -591,7 +574,6 @@ add_tasks_with_rust(async function generalBeforeSuggestions_others() { UrlbarPrefs.set("browser.search.suggest.enabled", true); UrlbarPrefs.set("suggest.searches", true); UrlbarPrefs.set("showSearchSuggestionsFirst", false); - await QuickSuggestTestUtils.forceSync(); let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false }); @@ -727,7 +709,6 @@ async function doDedupeAgainstURLTest({ // Now do another search with quick suggest enabled. UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); context = createContext(searchString, { isPrivate: false }); @@ -753,8 +734,6 @@ async function doDedupeAgainstURLTest({ UrlbarPrefs.clear("suggest.quicksuggest.nonsponsored"); UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); - await QuickSuggestTestUtils.forceSync(); - UrlbarPrefs.clear("suggest.searches"); await PlacesUtils.history.clear(); } @@ -763,7 +742,6 @@ async function doDedupeAgainstURLTest({ add_task(async function latencyTelemetry() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); let histogram = Services.telemetry.getHistogramById( TELEMETRY_REMOTE_SETTINGS_LATENCY @@ -904,13 +882,9 @@ add_task(async function setupAndTeardown() { }); // Timestamp templates in URLs should be replaced with real timestamps. -// -// TODO (bug 1859389): Make this work with Rust enabled once timestamp templates -// are handled. -add_task(async function timestamps() { +add_tasks_with_rust(async function timestamps() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); // Do a search. let context = createContext(TIMESTAMP_SEARCH_STRING, { @@ -952,10 +926,7 @@ add_task(async function timestamps() { // timestamp may be different from the one in the user's history. In that case, // the two URLs should be treated as dupes and only the quick suggest should be // shown, not the URL from history. -// -// TODO (bug 1859389): Make this work with Rust enabled once timestamp templates -// are handled. -add_task(async function dedupeAgainstURL_timestamps() { +add_tasks_with_rust(async function dedupeAgainstURL_timestamps() { // Disable search suggestions. UrlbarPrefs.set("suggest.searches", false); @@ -1022,7 +993,6 @@ add_task(async function dedupeAgainstURL_timestamps() { info("Doing second query"); UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); context = createContext(TIMESTAMP_SEARCH_STRING, { isPrivate: false }); let expectedQuickSuggest = makeAmpResult({ @@ -1138,8 +1108,6 @@ add_task(async function dedupeAgainstURL_timestamps() { // Clean up. UrlbarPrefs.clear("suggest.quicksuggest.nonsponsored"); UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); - await QuickSuggestTestUtils.forceSync(); - UrlbarPrefs.clear("suggest.searches"); await PlacesUtils.history.clear(); }); @@ -1317,7 +1285,6 @@ add_tasks_with_rust(async function block() { // the value of the `quickSuggestRemoteSettingsDataType` Nimbus variable. add_task(async function remoteSettingsDataType() { UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); for (let dataType of [undefined, "test-data-type"]) { // Set up a mock Nimbus rollout with the data type. @@ -1334,8 +1301,9 @@ add_task(async function remoteSettingsDataType() { expected.payload.title = dataType; } - // Re-sync. - await QuickSuggestTestUtils.forceSync(); + // Re-enable to trigger sync from remote settings. + UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); let context = createContext(SPONSORED_SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -1396,14 +1364,13 @@ async function doSponsoredPriorityTest({ }) { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({ ...nimbusSettings, quickSuggestSponsoredPriority: true, }); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "data", attachment: remoteSettingsData, @@ -1430,8 +1397,6 @@ add_tasks_with_rust(async function tabToSearch() { // types of Suggest results can appear as best matches, and they all should // have the same behavior. UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); - Services.prefs.setBoolPref( "browser.urlbar.quicksuggest.sponsoredPriority", true @@ -1505,8 +1470,6 @@ add_tasks_with_rust(async function position() { // types of Suggest results can appear as best matches, and they all should // have the same behavior. UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); - await QuickSuggestTestUtils.forceSync(); - Services.prefs.setBoolPref( "browser.urlbar.quicksuggest.sponsoredPriority", true @@ -1517,7 +1480,7 @@ add_tasks_with_rust(async function position() { // Set the remote settings data with a suggestion containing a position. UrlbarPrefs.set("quicksuggest.allowPositionInSuggestions", true); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "data", attachment: [ @@ -1565,13 +1528,7 @@ add_tasks_with_rust(async function position() { }); await cleanupPlaces(); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ - { - type: "data", - attachment: REMOTE_SETTINGS_RESULTS, - }, - ]); - + await QuickSuggestTestUtils.setRemoteSettingsResults(REMOTE_SETTINGS_RESULTS); UrlbarPrefs.clear("quicksuggest.allowPositionInSuggestions"); Services.prefs.clearUserPref("browser.search.suggest.enabled"); Services.prefs.clearUserPref("browser.urlbar.quicksuggest.sponsoredPriority"); diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js index 1e89dfd8bf21..66c0b53fa9b1 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js @@ -45,7 +45,7 @@ const REMOTE_SETTINGS_RESULTS = [ keywords: ["first", "1st", "two words", "a b c"], description: "Description for the First Addon", number_of_ratings: 1256, - score: 0.25, + is_top_pick: true, }, { url: "https://example.com/second-addon", @@ -56,7 +56,7 @@ const REMOTE_SETTINGS_RESULTS = [ keywords: ["second", "2nd"], description: "Description for the Second Addon", number_of_ratings: 256, - score: 0.25, + is_top_pick: false, }, { url: "https://example.com/third-addon", @@ -67,7 +67,6 @@ const REMOTE_SETTINGS_RESULTS = [ keywords: ["third", "3rd"], description: "Description for the Third Addon", number_of_ratings: 3, - score: 0.25, }, { url: "https://example.com/fourth-addon?utm_medium=aaa&utm_source=bbb", @@ -78,21 +77,23 @@ const REMOTE_SETTINGS_RESULTS = [ keywords: ["fourth", "4th"], description: "Description for the Fourth Addon", number_of_ratings: 4, - score: 0.25, }, ], }, ]; add_setup(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + // Disable search suggestions so we don't hit the network. Services.prefs.setBoolPref("browser.search.suggest.enabled", false); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: REMOTE_SETTINGS_RESULTS, + remoteSettingsResults: REMOTE_SETTINGS_RESULTS, merinoSuggestions: MERINO_SUGGESTIONS, - prefs: [["suggest.quicksuggest.nonsponsored", true]], }); + await waitForRemoteSettingsSuggestions(); }); add_task(async function telemetryType() { @@ -132,7 +133,7 @@ add_tasks_with_rust(async function quickSuggestPrefsDisabled() { }); UrlbarPrefs.set(pref, true); - await QuickSuggestTestUtils.forceSync(); + await waitForRemoteSettingsSuggestions(); } }); @@ -167,7 +168,7 @@ add_tasks_with_rust(async function addonSuggestionsSpecificPrefDisabled() { // Revert. UrlbarPrefs.clear(pref); - await QuickSuggestTestUtils.forceSync(); + await waitForRemoteSettingsSuggestions(); } }); @@ -188,7 +189,7 @@ add_tasks_with_rust(async function nimbus() { const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({ addonsFeatureGate: true, }); - await QuickSuggestTestUtils.forceSync(); + await waitForRemoteSettingsSuggestions(); await check_results({ context: createContext("test", { providers: [UrlbarProviderQuickSuggest.name], @@ -205,7 +206,7 @@ add_tasks_with_rust(async function nimbus() { // Enable locally. UrlbarPrefs.set("addons.featureGate", true); - await QuickSuggestTestUtils.forceSync(); + await waitForRemoteSettingsSuggestions(); // Disable by Nimbus. const cleanUpNimbusDisable = await UrlbarTestUtils.initNimbusFeature({ @@ -222,7 +223,7 @@ add_tasks_with_rust(async function nimbus() { // Revert. UrlbarPrefs.clear("addons.featureGate"); - await QuickSuggestTestUtils.forceSync(); + await waitForRemoteSettingsSuggestions(); }); add_tasks_with_rust(async function hideIfAlreadyInstalled() { @@ -263,7 +264,6 @@ add_tasks_with_rust(async function hideIfAlreadyInstalled() { xpi.remove(false); }); -// TODO (bug 1861220): The Rust implementation doesn't support prefix matching. add_tasks_with_rust(async function remoteSettings() { const testCases = [ { @@ -495,10 +495,6 @@ add_task(async function merinoIsTopPick() { }); // Tests the "show less frequently" behavior. -// -// TODO (bug 1861220, bug 1861228): The Rust implementation doesn't support -// prefix matching or the remote settings config, so this task is not run with -// Rust enabled. add_task(async function showLessFrequently() { await doShowLessFrequentlyTests({ feature: QuickSuggest.getFeature("AddonSuggestions"), @@ -565,3 +561,11 @@ function makeExpectedResult({ suggestion, source, setUtmParams = true }) { }, }; } + +async function waitForRemoteSettingsSuggestions(keyword = "first") { + let feature = QuickSuggest.getFeature("AddonSuggestions"); + await TestUtils.waitForCondition(async () => { + let suggestions = await feature.queryRemoteSettings(keyword); + return !!suggestions.length; + }, "Waiting for AddonSuggestions to serve remote settings suggestions"); +} diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js index e4e51521fc30..2be57427194a 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js @@ -24,12 +24,14 @@ const MERINO_SUGGESTIONS = [ ]; add_setup(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + // Disable search suggestions so we don't hit the network. Services.prefs.setBoolPref("browser.search.suggest.enabled", false); await QuickSuggestTestUtils.ensureQuickSuggestInit({ merinoSuggestions: MERINO_SUGGESTIONS, - prefs: [["suggest.quicksuggest.nonsponsored", true]], }); }); diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js index ce08bb7801d5..6120501378a9 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js @@ -102,22 +102,22 @@ let gDateNowStub; let gStartupDateMsStub; add_task(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("quicksuggest.impressionCaps.sponsoredEnabled", true); + UrlbarPrefs.set("quicksuggest.impressionCaps.nonSponsoredEnabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + // Disable search suggestions so we don't hit the network. Services.prefs.setBoolPref("browser.search.suggest.enabled", false); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, }, ], - prefs: [ - ["quicksuggest.impressionCaps.sponsoredEnabled", true], - ["quicksuggest.impressionCaps.nonSponsoredEnabled", true], - ["suggest.quicksuggest.nonsponsored", true], - ["suggest.quicksuggest.sponsored", true], - ], }); // Set up a sinon stub for the `Date.now()` implementation inside of diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_mdn.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_mdn.js index aca75460ef72..a5e9928b5c07 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_mdn.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_mdn.js @@ -36,18 +36,19 @@ const REMOTE_SETTINGS_DATA = [ ]; add_setup(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); + UrlbarPrefs.set("suggest.mdn", true); + UrlbarPrefs.set("mdn.featureGate", true); + // Disable search suggestions so we don't hit the network. Services.prefs.setBoolPref("browser.search.suggest.enabled", false); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: REMOTE_SETTINGS_DATA, - prefs: [ - ["suggest.quicksuggest.nonsponsored", true], - ["suggest.quicksuggest.sponsored", false], - ["suggest.mdn", true], - ["mdn.featureGate", true], - ], + remoteSettingsResults: REMOTE_SETTINGS_DATA, }); + await waitForSuggestions(); }); add_task(async function basic() { @@ -117,7 +118,7 @@ add_task(async function disableByLocalPref() { // Revert. UrlbarPrefs.set(pref, true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); } }); @@ -150,7 +151,7 @@ add_task(async function nimbus() { "urlbar", "config" ); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); await check_results({ context: createContext(keyword, { providers: [UrlbarProviderQuickSuggest.name], @@ -162,7 +163,7 @@ add_task(async function nimbus() { // Enable locally. defaultPrefs.setBoolPref("mdn.featureGate", true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); // Disable by Nimbus. const cleanUpNimbusDisable = await UrlbarTestUtils.initNimbusFeature( @@ -181,7 +182,7 @@ add_task(async function nimbus() { // Revert. defaultPrefs.setBoolPref("mdn.featureGate", true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); }); function makeExpectedResult({ @@ -219,3 +220,12 @@ function makeExpectedResult({ }, }; } + +async function waitForSuggestions() { + let keyword = REMOTE_SETTINGS_DATA[0].attachment[0].keywords[0]; + let feature = QuickSuggest.getFeature("MDNSuggestions"); + await TestUtils.waitForCondition(async () => { + let suggestions = await feature.queryRemoteSettings(keyword); + return !!suggestions.length; + }, "Waiting for MDNSuggestions to serve remote settings suggestions"); +} diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js index 94c6c249c6ce..b94000f885dd 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js @@ -97,20 +97,21 @@ ChromeUtils.defineLazyGetter( ); add_task(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", false); + await MerinoTestUtils.server.start(); // Set up the remote settings client with the test data. await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, }, ], - prefs: [ - ["suggest.quicksuggest.nonsponsored", true], - ["suggest.quicksuggest.sponsored", true], - ], }); Assert.equal( @@ -130,7 +131,7 @@ add_task(async function merinoDisabled() { // Clear the remote settings suggestions so that if Merino is actually queried // -- which would be a bug -- we don't accidentally mask the Merino suggestion // by also matching an RS suggestion with the same or higher score. - await QuickSuggestTestUtils.setRemoteSettingsRecords([]); + await QuickSuggestTestUtils.setRemoteSettingsResults([]); let histograms = MerinoTestUtils.getAndClearHistograms(); @@ -152,7 +153,7 @@ add_task(async function merinoDisabled() { UrlbarPrefs.set("merino.endpointURL", mockEndpointUrl); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, @@ -168,7 +169,7 @@ add_task(async function dataCollectionDisabled() { // Clear the remote settings suggestions so that if Merino is actually queried // -- which would be a bug -- we don't accidentally mask the Merino suggestion // by also matching an RS suggestion with the same or higher score. - await QuickSuggestTestUtils.setRemoteSettingsRecords([]); + await QuickSuggestTestUtils.setRemoteSettingsResults([]); let context = createContext(SEARCH_STRING, { providers: [UrlbarProviderQuickSuggest.name], @@ -179,7 +180,7 @@ add_task(async function dataCollectionDisabled() { matches: [], }); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js index f56158578e3e..53884023449a 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js @@ -15,13 +15,12 @@ ChromeUtils.defineLazyGetter( ); add_task(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true); + await MerinoTestUtils.server.start(); - await QuickSuggestTestUtils.ensureQuickSuggestInit({ - prefs: [ - ["suggest.quicksuggest.sponsored", true], - ["quicksuggest.dataCollection.enabled", true], - ], - }); + await QuickSuggestTestUtils.ensureQuickSuggestInit(); }); // In a single engagement, all requests should use the same session ID and the diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_nonUniqueKeywords.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_nonUniqueKeywords.js index cf4f8aecc6cf..f73ea1fbc9f9 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_nonUniqueKeywords.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_nonUniqueKeywords.js @@ -134,6 +134,10 @@ let TESTS = { }; add_task(async function () { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + // Create results and suggestions based on `SUGGESTIONS_DATA`. let qsResults = []; let qsSuggestions = []; @@ -206,16 +210,12 @@ add_task(async function () { } await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: qsResults, }, ], - prefs: [ - ["suggest.quicksuggest.sponsored", true], - ["suggest.quicksuggest.nonsponsored", true], - ], }); // Run a test for each keyword. diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js index 0c34bead3182..80219177d3d7 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js @@ -18,32 +18,31 @@ const REMOTE_SETTINGS_DATA = [ title: "Pocket Suggestion 0", description: "Pocket description 0", lowConfidenceKeywords: [LOW_KEYWORD, "how to low"], - highConfidenceKeywords: [HIGH_KEYWORD], - score: 0.25, + highConfidenceKeywords: [HIGH_KEYWORD, "how to high"], }, { url: "https://example.com/pocket-1", title: "Pocket Suggestion 1", description: "Pocket description 1", - lowConfidenceKeywords: ["other low"], - highConfidenceKeywords: ["another high"], - score: 0.25, + lowConfidenceKeywords: ["other low", "both low and high"], + highConfidenceKeywords: ["both low and high"], }, ], }, ]; add_setup(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("pocket.featureGate", true); + // Disable search suggestions so we don't hit the network. Services.prefs.setBoolPref("browser.search.suggest.enabled", false); await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: REMOTE_SETTINGS_DATA, - prefs: [ - ["suggest.quicksuggest.nonsponsored", true], - ["pocket.featureGate", true], - ], + remoteSettingsResults: REMOTE_SETTINGS_DATA, }); + await waitForSuggestions(); }); add_task(async function telemetryType() { @@ -83,7 +82,7 @@ add_tasks_with_rust(async function nonsponsoredDisabled() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); }); // When Pocket-specific preferences are disabled, suggestions should not be @@ -112,7 +111,7 @@ add_tasks_with_rust(async function pocketSpecificPrefsDisabled() { // Revert. UrlbarPrefs.set(pref, true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); } }); @@ -133,7 +132,7 @@ add_tasks_with_rust(async function nimbus() { const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({ pocketFeatureGate: true, }); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); await check_results({ context: createContext(LOW_KEYWORD, { providers: [UrlbarProviderQuickSuggest.name], @@ -145,7 +144,7 @@ add_tasks_with_rust(async function nimbus() { // Enable locally. UrlbarPrefs.set("pocket.featureGate", true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); // Disable by Nimbus. const cleanUpNimbusDisable = await UrlbarTestUtils.initNimbusFeature({ @@ -162,7 +161,7 @@ add_tasks_with_rust(async function nimbus() { // Revert. UrlbarPrefs.set("pocket.featureGate", true); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); }); // The suggestion should be shown as a top pick when a high-confidence keyword @@ -180,7 +179,7 @@ add_tasks_with_rust(async function topPick() { }); // Low-confidence keywords should do prefix matching starting at the first word. -add_tasks_with_rust(async function lowPrefixes() { +add_task(async function lowPrefixes() { // search string -> should match let tests = { l: false, @@ -212,14 +211,7 @@ add_tasks_with_rust(async function lowPrefixes() { // Low-confidence keywords that start with "how to" should do prefix matching // starting at "how to" instead of the first word. -// -// Note: The Rust implementation doesn't support this. add_task(async function lowPrefixes_howTo() { - Assert.ok( - !UrlbarPrefs.get("quicksuggest.rustEnabled"), - "The Rust implementation doesn't support the 'how to' special case" - ); - // search string -> should match let tests = { h: false, @@ -248,7 +240,7 @@ add_task(async function lowPrefixes_howTo() { }); // High-confidence keywords should not do prefix matching at all. -add_tasks_with_rust(async function highPrefixes() { +add_task(async function highPrefixes() { // search string -> should match let tests = { h: false, @@ -283,8 +275,108 @@ add_tasks_with_rust(async function highPrefixes() { } }); +// High-confidence keywords starting with "how to" should also not do prefix +// matching at all. +add_task(async function highPrefixes_howTo() { + // search string -> [should match low, should match high] + let tests = { + h: [false, false], + ho: [false, false], + how: [false, false], + "how ": [false, false], + "how t": [false, false], + "how to": [true, false], + "how to ": [true, false], + "how to h": [false, false], + "how to hi": [false, false], + "how to hig": [false, false], + "how to high": [false, true], + }; + for (let [searchString, [shouldMatchLow, shouldMatchHigh]] of Object.entries( + tests + )) { + info( + "Doing search: " + + JSON.stringify({ searchString, shouldMatchLow, shouldMatchHigh }) + ); + let matches = []; + if (shouldMatchLow) { + matches.push( + makeExpectedResult({ + searchString, + fullKeyword: "how to low", + isTopPick: false, + }) + ); + } + if (shouldMatchHigh) { + matches.push( + makeExpectedResult({ + searchString, + fullKeyword: "how to high", + isTopPick: true, + }) + ); + } + await check_results({ + matches, + context: createContext(searchString, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + }); + } +}); + +// When a search matches both a low and high-confidence keyword, the suggestion +// should be shown as a top pick. +add_task(async function topPickLowAndHigh() { + let suggestion = REMOTE_SETTINGS_DATA[0].attachment[1]; + let finalSearchString = "both low and high"; + Assert.ok( + suggestion.lowConfidenceKeywords.includes(finalSearchString), + "Sanity check: lowConfidenceKeywords includes the final search string" + ); + Assert.ok( + suggestion.highConfidenceKeywords.includes(finalSearchString), + "Sanity check: highConfidenceKeywords includes the final search string" + ); + + // search string -> should be a top pick + let tests = { + "both low": false, + "both low ": false, + "both low a": false, + "both low an": false, + "both low and": false, + "both low and ": false, + "both low and h": false, + "both low and hi": false, + "both low and hig": false, + "both low and high": true, + [finalSearchString]: true, + }; + for (let [searchString, isTopPick] of Object.entries(tests)) { + info("Doing search: " + JSON.stringify({ searchString, isTopPick })); + await check_results({ + context: createContext(searchString, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: [ + makeExpectedResult({ + searchString, + fullKeyword: "both low and high", + isTopPick, + suggestion, + }), + ], + }); + } +}); + // Keyword matching should be case insenstive. -add_tasks_with_rust(async function uppercase() { +add_task(async function uppercase() { await check_results({ context: createContext(LOW_KEYWORD.toUpperCase(), { providers: [UrlbarProviderQuickSuggest.name], @@ -313,7 +405,7 @@ add_tasks_with_rust(async function uppercase() { }); // Tests the "Not relevant" command: a dismissed suggestion shouldn't be added. -add_tasks_with_rust(async function notRelevant() { +add_task(async function notRelevant() { let result = makeExpectedResult({ searchString: LOW_KEYWORD }); info("Triggering the 'Not relevant' command"); @@ -378,7 +470,7 @@ add_tasks_with_rust(async function notRelevant() { // Tests the "Not interested" command: all Pocket suggestions should be disabled // and not added anymore. -add_tasks_with_rust(async function notInterested() { +add_task(async function notInterested() { let result = makeExpectedResult({ searchString: LOW_KEYWORD }); info("Triggering the 'Not interested' command"); @@ -414,11 +506,11 @@ add_tasks_with_rust(async function notInterested() { }); UrlbarPrefs.clear("suggest.pocket"); - await QuickSuggestTestUtils.forceSync(); + await waitForSuggestions(); }); // Tests the "show less frequently" behavior. -add_tasks_with_rust(async function showLessFrequently() { +add_task(async function showLessFrequently() { await doShowLessFrequentlyTests({ feature: QuickSuggest.getFeature("PocketSuggestions"), showLessFrequentlyCountPref: "pocket.showLessFrequentlyCount", @@ -497,3 +589,15 @@ function makeExpectedResult({ }, }; } + +async function waitForSuggestions(keyword = LOW_KEYWORD) { + if (UrlbarPrefs.get("quicksuggest.rustEnabled")) { + return; + } + + let feature = QuickSuggest.getFeature("PocketSuggestions"); + await TestUtils.waitForCondition(async () => { + let suggestions = await feature.queryRemoteSettings(keyword); + return !!suggestions.length; + }, "Waiting for PocketSuggestions to serve remote settings suggestions"); +} diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_positionInSuggestions.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_positionInSuggestions.js index d1845a9b22f7..83ea3c2d042c 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_positionInSuggestions.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_positionInSuggestions.js @@ -415,9 +415,13 @@ const TEST_CASES = [ ]; add_setup(async function () { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + // Setup for quick suggest result. await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: [ @@ -431,10 +435,6 @@ add_setup(async function () { ], }, ], - prefs: [ - ["suggest.quicksuggest.sponsored", true], - ["suggest.quicksuggest.nonsponsored", true], - ], }); // Setup for places result. diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js index 808c2586dd03..389e2327c155 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js @@ -176,16 +176,17 @@ const MERINO_UNKNOWN_SUGGESTION = { }; 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({ - remoteSettingsRecords: REMOTE_SETTINGS_RECORDS, + remoteSettingsResults: REMOTE_SETTINGS_RECORDS, merinoSuggestions: [], - prefs: [ - ["suggest.quicksuggest.sponsored", true], - ["suggest.quicksuggest.nonsponsored", true], - ], }); }); @@ -471,7 +472,7 @@ add_task(async function sponsoredWith_addonWith_addonWins_both() { }); add_task(async function merino_sponsored_addon_sponsoredWins() { - await QuickSuggestTestUtils.setRemoteSettingsRecords([]); + await QuickSuggestTestUtils.setRemoteSettingsResults([]); MerinoTestUtils.server.response.body.suggestions = [ MERINO_SPONSORED_SUGGESTION, @@ -493,11 +494,11 @@ add_task(async function merino_sponsored_addon_sponsoredWins() { }), }); - await QuickSuggestTestUtils.setRemoteSettingsRecords(REMOTE_SETTINGS_RECORDS); + await QuickSuggestTestUtils.setRemoteSettingsResults(REMOTE_SETTINGS_RECORDS); }); add_task(async function merino_sponsored_addon_addonWins() { - await QuickSuggestTestUtils.setRemoteSettingsRecords([]); + await QuickSuggestTestUtils.setRemoteSettingsResults([]); MerinoTestUtils.server.response.body.suggestions = [ MERINO_SPONSORED_SUGGESTION, @@ -518,11 +519,11 @@ add_task(async function merino_sponsored_addon_addonWins() { }), }); - await QuickSuggestTestUtils.setRemoteSettingsRecords(REMOTE_SETTINGS_RECORDS); + await QuickSuggestTestUtils.setRemoteSettingsResults(REMOTE_SETTINGS_RECORDS); }); add_task(async function merino_sponsored_unknown_sponsoredWins() { - await QuickSuggestTestUtils.setRemoteSettingsRecords([]); + await QuickSuggestTestUtils.setRemoteSettingsResults([]); MerinoTestUtils.server.response.body.suggestions = [ MERINO_SPONSORED_SUGGESTION, @@ -544,11 +545,11 @@ add_task(async function merino_sponsored_unknown_sponsoredWins() { }), }); - await QuickSuggestTestUtils.setRemoteSettingsRecords(REMOTE_SETTINGS_RECORDS); + await QuickSuggestTestUtils.setRemoteSettingsResults(REMOTE_SETTINGS_RECORDS); }); add_task(async function merino_sponsored_unknown_unknownWins() { - await QuickSuggestTestUtils.setRemoteSettingsRecords([]); + await QuickSuggestTestUtils.setRemoteSettingsResults([]); MerinoTestUtils.server.response.body.suggestions = [ MERINO_SPONSORED_SUGGESTION, @@ -568,7 +569,7 @@ add_task(async function merino_sponsored_unknown_unknownWins() { }), }); - await QuickSuggestTestUtils.setRemoteSettingsRecords(REMOTE_SETTINGS_RECORDS); + await QuickSuggestTestUtils.setRemoteSettingsResults(REMOTE_SETTINGS_RECORDS); }); add_task(async function stringValue() { diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js index 1b8da5492044..f48e54a65586 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js @@ -35,12 +35,14 @@ const MERINO_SUGGESTIONS = [ ]; add_setup(async function init() { + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + // Disable search suggestions so we don't hit the network. Services.prefs.setBoolPref("browser.search.suggest.enabled", false); await QuickSuggestTestUtils.ensureQuickSuggestInit({ merinoSuggestions: MERINO_SUGGESTIONS, - prefs: [["suggest.quicksuggest.nonsponsored", true]], }); }); diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js b/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js index 5345a8fb1b0b..cec826c43340 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js @@ -7,6 +7,7 @@ "use strict"; ChromeUtils.defineESModuleGetters(this, { + MockRustSuggest: "resource://testing-common/QuickSuggestTestUtils.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs", }); @@ -26,24 +27,28 @@ const REMOTE_SETTINGS_SUGGESTION = { impression_url: "http://example.com/amp-impression", advertiser: "Amp", iab_category: "22 - Shopping", - icon: "1234", }; +let gMockRustSuggest; + add_setup(async function () { initUpdateTimerManager(); - await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + // Since we are specifically testing init behavior of the Rust backend, we + // avoid `QuickSuggestTestUtils.ensureQuickSuggestInit()` so we have more + // control. + gMockRustSuggest = new MockRustSuggest({ + data: [ { type: "data", attachment: [REMOTE_SETTINGS_SUGGESTION], }, ], - prefs: [ - ["suggest.quicksuggest.sponsored", true], - ["quicksuggest.rustEnabled", false], - ], }); + UrlbarPrefs.set("quicksuggest.rustEnabled", false); + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + QuickSuggest.init(); }); // IMPORTANT: This task must run first! @@ -53,11 +58,6 @@ add_setup(async function () { add_task(async function firstRun() { Assert.ok( !UrlbarPrefs.get("quicksuggest.rustEnabled"), - "rustEnabled pref is initially false (this task must run first!)" - ); - Assert.strictEqual( - QuickSuggest.rustBackend.isEnabled, - false, "Rust backend is initially disabled (this task must run first!)" ); Assert.ok( @@ -67,7 +67,6 @@ add_task(async function firstRun() { info("Enabling the Rust backend"); UrlbarPrefs.set("quicksuggest.rustEnabled", true); - Assert.ok(QuickSuggest.rustBackend.isEnabled, "Rust backend is now enabled"); let { ingestPromise } = QuickSuggest.rustBackend; Assert.ok(ingestPromise, "Ingest started"); @@ -117,54 +116,38 @@ add_task(async function interval() { // Wait for a few ingests to happen. for (let i = 0; i < 3; i++) { - info("Preparing for ingest at index " + i); - // Set a new suggestion so we can make sure ingest really happened. let suggestion = { ...REMOTE_SETTINGS_SUGGESTION, url: REMOTE_SETTINGS_SUGGESTION.url + "/" + i, }; - await QuickSuggestTestUtils.setRemoteSettingsRecords( - [ + gMockRustSuggest.update({ + data: [ { type: "data", attachment: [suggestion], }, ], - // Don't force sync since the whole point here is to make sure the backend - // ingests on its own! - { forceSync: false } + }); + + // Wait for ingest. + await TestUtils.waitForCondition( + () => QuickSuggest.rustBackend.ingestPromise != ingestPromise, + "Waiting for ingest timer to fire and backend to start new ingest" ); - // Wait for ingest to start and finish. - info("Waiting for ingest to start at index " + i); - ({ ingestPromise } = await waitForIngestStart(ingestPromise)); - info("Waiting for ingest to finish at index " + i); + ({ ingestPromise } = QuickSuggest.rustBackend); await ingestPromise; await checkSuggestions([suggestion]); } - // In the loop above, there was one additional async call after awaiting the - // ingest promise, to `checkSuggestions()`. It's possible, though unlikely, - // that call took so long that another ingest has started. To be sure, wait - // for one final ingest to start before continuing. - ({ ingestPromise } = await waitForIngestStart(ingestPromise)); - - // Now immediately disable the backend. New ingests should not start, but the - // final one will still be ongoing. - info("Disabling the backend"); + // Disable the backend. Ingestion should stop. + let waitSecs = 3 * intervalSecs; + info(`Disabling the backend and waiting ${waitSecs}s`); UrlbarPrefs.set("quicksuggest.rustEnabled", false); - info("Awaiting final ingest promise"); - await ingestPromise; - - // Wait a few seconds. - let waitSecs = 3 * intervalSecs; - info(`Waiting ${waitSecs}s...`); // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(r => setTimeout(r, 1000 * waitSecs)); - - // No new ingests should have started. Assert.equal( QuickSuggest.rustBackend.ingestPromise, ingestPromise, @@ -174,29 +157,6 @@ add_task(async function interval() { UrlbarPrefs.clear("quicksuggest.rustIngestIntervalSeconds"); }); -async function waitForIngestStart(oldIngestPromise) { - let newIngestPromise; - await TestUtils.waitForCondition(() => { - let { ingestPromise } = QuickSuggest.rustBackend; - if (ingestPromise != oldIngestPromise) { - newIngestPromise = ingestPromise; - return true; - } - return false; - }, "Waiting for a new ingest to start"); - - Assert.equal( - QuickSuggest.rustBackend.ingestPromise, - newIngestPromise, - "Sanity check: ingestPromise hasn't changed since waitForCondition returned" - ); - - // A bare promise can't be returned because it will cause the awaiting caller - // to await that promise! We're simply trying to return the promise, which the - // caller can later await. - return { ingestPromise: newIngestPromise }; -} - async function checkSuggestions(expected = [REMOTE_SETTINGS_SUGGESTION]) { let actual = await QuickSuggest.rustBackend.query("amp"); Assert.deepEqual( diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js b/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js index 203b513d3877..b264708371b1 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js @@ -18,13 +18,14 @@ const { WEATHER_RS_DATA, WEATHER_SUGGESTION } = MerinoTestUtils; add_task(async function init() { await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "weather", weather: WEATHER_RS_DATA, }, ], }); + UrlbarPrefs.set("quicksuggest.enabled", true); await MerinoTestUtils.initWeather(); @@ -129,7 +130,7 @@ add_task(async function keywordsNotDefined() { }); // Set RS data without any keywords. Fetching should immediately stop. - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: {}, @@ -153,7 +154,7 @@ add_task(async function keywordsNotDefined() { // Set keywords. Fetching should immediately start. info("Setting keywords"); let fetchPromise = QuickSuggest.weather.waitForFetches(); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, @@ -1230,7 +1231,7 @@ add_task(async function vpn() { }); // When a Nimbus experiment is installed, it should override the remote settings -// weather record. +// config. add_task(async function nimbusOverride() { // Sanity check initial state. assertEnabled({ @@ -1239,8 +1240,7 @@ add_task(async function nimbusOverride() { pendingFetchCount: 0, }); - // Verify a search works as expected with the default remote settings weather - // record (which was added in the init task). + // Verify a search works as expected with the default remote settings config. await check_results({ context: createContext(MerinoTestUtils.WEATHER_KEYWORD, { providers: [UrlbarProviderWeather.name], diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js b/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js index 8569a026f15d..8964250faf53 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js @@ -14,13 +14,14 @@ const { WEATHER_RS_DATA, WEATHER_SUGGESTION } = MerinoTestUtils; add_task(async function init() { await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "weather", weather: WEATHER_RS_DATA, }, ], }); + UrlbarPrefs.set("quicksuggest.enabled", true); await MerinoTestUtils.initWeather(); }); @@ -747,7 +748,7 @@ async function doKeywordsTest({ nimbusCleanup = await UrlbarTestUtils.initNimbusFeature(nimbusValues); } - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: settingsData, @@ -790,7 +791,7 @@ async function doKeywordsTest({ if (!QuickSuggest.weather.suggestion) { fetchPromise = QuickSuggest.weather.waitForFetches(); } - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, @@ -821,7 +822,7 @@ async function doMatchingQuickSuggestTest(pref, isSponsored) { // Add a remote settings result to quick suggest. UrlbarPrefs.set(pref, true); - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "data", attachment: [ @@ -1258,7 +1259,7 @@ async function doIncrementTest({ desc, setup, tests }) { nimbusCleanup = await UrlbarTestUtils.initNimbusFeature(nimbusValues); } - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: settingsData, @@ -1321,7 +1322,7 @@ async function doIncrementTest({ desc, setup, tests }) { if (!QuickSuggest.weather.suggestion) { fetchPromise = QuickSuggest.weather.waitForFetches(); } - await QuickSuggestTestUtils.setRemoteSettingsRecords([ + await QuickSuggestTestUtils.setRemoteSettingsResults([ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, diff --git a/browser/components/urlbar/tests/unit/test_exposure.js b/browser/components/urlbar/tests/unit/test_exposure.js index af4b62aaae76..7889879e270e 100644 --- a/browser/components/urlbar/tests/unit/test_exposure.js +++ b/browser/components/urlbar/tests/unit/test_exposure.js @@ -101,18 +101,21 @@ add_setup(async function test_setup() { // FOG needs to be initialized in order for data to flow. Services.fog.initializeFOG(); + UrlbarPrefs.set("quicksuggest.enabled", true); + UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", false); + + await MerinoTestUtils.server.start(); + // Set up the remote settings client with the test data. await QuickSuggestTestUtils.ensureQuickSuggestInit({ - remoteSettingsRecords: [ + remoteSettingsResults: [ { type: "data", attachment: REMOTE_SETTINGS_RESULTS, }, ], - prefs: [ - ["suggest.quicksuggest.nonsponsored", true], - ["suggest.quicksuggest.sponsored", true], - ], }); });