test_quicksuggest_addons.js (14522B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Tests addon quick suggest results. 6 7 "use strict"; 8 9 ChromeUtils.defineESModuleGetters(this, { 10 AddonManager: "resource://gre/modules/AddonManager.sys.mjs", 11 AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs", 12 ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.sys.mjs", 13 }); 14 15 AddonTestUtils.init(this, false); 16 AddonTestUtils.createAppInfo( 17 "xpcshell@tests.mozilla.org", 18 "XPCShell", 19 "42", 20 "42" 21 ); 22 23 // TODO: Firefox no longer uses `rating` and `number_of_ratings` but they are 24 // still present in Merino and RS suggestions, so they are included here for 25 // greater accuracy. We should remove them from Merino, RS, and tests. 26 const MERINO_SUGGESTIONS = [ 27 { 28 provider: "amo", 29 icon: "icon", 30 url: "https://example.com/merino-addon", 31 title: "title", 32 description: "description", 33 custom_details: { 34 amo: { 35 rating: "5", 36 number_of_ratings: "1234567", 37 guid: "test@addon", 38 }, 39 }, 40 }, 41 ]; 42 43 const REMOTE_SETTINGS_RESULTS = [ 44 { 45 type: "amo-suggestions", 46 attachment: [ 47 { 48 url: "https://example.com/first-addon", 49 guid: "first@addon", 50 icon: "https://example.com/first-addon.svg", 51 title: "First Addon", 52 rating: "4.7", 53 keywords: ["first", "1st", "two words", "aa b c"], 54 description: "Description for the First Addon", 55 number_of_ratings: 1256, 56 score: 0.25, 57 }, 58 { 59 url: "https://example.com/second-addon", 60 guid: "second@addon", 61 icon: "https://example.com/second-addon.svg", 62 title: "Second Addon", 63 rating: "1.7", 64 keywords: ["second", "2nd"], 65 description: "Description for the Second Addon", 66 number_of_ratings: 256, 67 score: 0.25, 68 }, 69 { 70 url: "https://example.com/third-addon", 71 guid: "third@addon", 72 icon: "https://example.com/third-addon.svg", 73 title: "Third Addon", 74 rating: "3.7", 75 keywords: ["third", "3rd"], 76 description: "Description for the Third Addon", 77 number_of_ratings: 3, 78 score: 0.25, 79 }, 80 { 81 url: "https://example.com/fourth-addon?utm_medium=aaa&utm_source=bbb", 82 guid: "fourth@addon", 83 icon: "https://example.com/fourth-addon.svg", 84 title: "Fourth Addon", 85 rating: "4.7", 86 keywords: ["fourth", "4th"], 87 description: "Description for the Fourth Addon", 88 number_of_ratings: 4, 89 score: 0.25, 90 }, 91 ], 92 }, 93 ]; 94 95 add_setup(async function init() { 96 await AddonTestUtils.promiseStartupManager(); 97 98 // Disable search suggestions so we don't hit the network. 99 Services.prefs.setBoolPref("browser.search.suggest.enabled", false); 100 101 await QuickSuggestTestUtils.ensureQuickSuggestInit({ 102 remoteSettingsRecords: REMOTE_SETTINGS_RESULTS, 103 merinoSuggestions: MERINO_SUGGESTIONS, 104 prefs: [["suggest.quicksuggest.all", true]], 105 }); 106 }); 107 108 add_task(async function telemetryType() { 109 Assert.equal( 110 QuickSuggest.getFeature("AddonSuggestions").getSuggestionTelemetryType({}), 111 "amo", 112 "Telemetry type should be 'amo'" 113 ); 114 }); 115 116 // When quick suggest prefs are disabled, addon suggestions should be disabled. 117 add_task(async function prefsDisabled() { 118 let prefs = [ 119 "quicksuggest.enabled", 120 "addons.featureGate", 121 "suggest.quicksuggest.all", 122 "suggest.addons", 123 ]; 124 for (let pref of prefs) { 125 info("Testing pref: " + pref); 126 127 // Before disabling the pref, first make sure the suggestion is added. 128 await check_results({ 129 context: createContext("test", { 130 providers: [UrlbarProviderQuickSuggest.name], 131 isPrivate: false, 132 }), 133 matches: [ 134 makeExpectedResult({ 135 suggestion: MERINO_SUGGESTIONS[0], 136 source: "merino", 137 provider: "amo", 138 }), 139 ], 140 }); 141 142 // Now disable the pref. 143 UrlbarPrefs.set(pref, false); 144 await check_results({ 145 context: createContext("test", { 146 providers: [UrlbarProviderQuickSuggest.name], 147 isPrivate: false, 148 }), 149 matches: [], 150 }); 151 152 UrlbarPrefs.set(pref, true); 153 await QuickSuggestTestUtils.forceSync(); 154 } 155 }); 156 157 // Check wheather the addon suggestions will be shown by the setup of Nimbus 158 // variable. 159 add_task(async function nimbus() { 160 // Disable the fature gate. 161 UrlbarPrefs.set("addons.featureGate", false); 162 await check_results({ 163 context: createContext("test", { 164 providers: [UrlbarProviderQuickSuggest.name], 165 isPrivate: false, 166 }), 167 matches: [], 168 }); 169 170 // Enable by Nimbus. 171 const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({ 172 addonsFeatureGate: true, 173 }); 174 await QuickSuggestTestUtils.forceSync(); 175 await check_results({ 176 context: createContext("test", { 177 providers: [UrlbarProviderQuickSuggest.name], 178 isPrivate: false, 179 }), 180 matches: [ 181 makeExpectedResult({ 182 suggestion: MERINO_SUGGESTIONS[0], 183 source: "merino", 184 provider: "amo", 185 }), 186 ], 187 }); 188 await cleanUpNimbusEnable(); 189 190 // Enable locally. 191 UrlbarPrefs.set("addons.featureGate", true); 192 await QuickSuggestTestUtils.forceSync(); 193 194 // Disable by Nimbus. 195 const cleanUpNimbusDisable = await UrlbarTestUtils.initNimbusFeature({ 196 addonsFeatureGate: false, 197 }); 198 await check_results({ 199 context: createContext("test", { 200 providers: [UrlbarProviderQuickSuggest.name], 201 isPrivate: false, 202 }), 203 matches: [], 204 }); 205 await cleanUpNimbusDisable(); 206 207 // Revert. 208 UrlbarPrefs.clear("addons.featureGate"); 209 await QuickSuggestTestUtils.forceSync(); 210 }); 211 212 add_task(async function hideIfAlreadyInstalled() { 213 // Show suggestion. 214 await check_results({ 215 context: createContext("test", { 216 providers: [UrlbarProviderQuickSuggest.name], 217 isPrivate: false, 218 }), 219 matches: [ 220 makeExpectedResult({ 221 suggestion: MERINO_SUGGESTIONS[0], 222 source: "merino", 223 provider: "amo", 224 }), 225 ], 226 }); 227 228 // Install an addon for the suggestion. 229 const xpi = ExtensionTestCommon.generateXPI({ 230 manifest: { 231 browser_specific_settings: { 232 gecko: { id: "test@addon" }, 233 }, 234 }, 235 }); 236 const addon = await AddonManager.installTemporaryAddon(xpi); 237 238 // Show suggestion for the addon installed. 239 await check_results({ 240 context: createContext("test", { 241 providers: [UrlbarProviderQuickSuggest.name], 242 isPrivate: false, 243 }), 244 matches: [], 245 }); 246 247 await addon.uninstall(); 248 xpi.remove(false); 249 }); 250 251 add_task(async function remoteSettings() { 252 const testCases = [ 253 { 254 input: "f", 255 expected: null, 256 }, 257 { 258 input: "fi", 259 expected: null, 260 }, 261 { 262 input: "fir", 263 expected: null, 264 }, 265 { 266 input: "firs", 267 expected: null, 268 }, 269 { 270 input: "first", 271 expected: makeExpectedResult({ 272 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 273 }), 274 }, 275 { 276 input: "1st", 277 expected: makeExpectedResult({ 278 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 279 }), 280 }, 281 { 282 input: "t", 283 expected: null, 284 }, 285 { 286 input: "tw", 287 expected: null, 288 }, 289 { 290 input: "two", 291 expected: makeExpectedResult({ 292 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 293 }), 294 }, 295 { 296 input: "two ", 297 expected: makeExpectedResult({ 298 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 299 }), 300 }, 301 { 302 input: "two w", 303 expected: makeExpectedResult({ 304 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 305 }), 306 }, 307 { 308 input: "two wo", 309 expected: makeExpectedResult({ 310 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 311 }), 312 }, 313 { 314 input: "two wor", 315 expected: makeExpectedResult({ 316 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 317 }), 318 }, 319 { 320 input: "two word", 321 expected: makeExpectedResult({ 322 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 323 }), 324 }, 325 { 326 input: "two words", 327 expected: makeExpectedResult({ 328 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 329 }), 330 }, 331 { 332 input: "aa", 333 expected: makeExpectedResult({ 334 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 335 }), 336 }, 337 { 338 input: "aa ", 339 expected: makeExpectedResult({ 340 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 341 }), 342 }, 343 { 344 input: "aa b", 345 expected: makeExpectedResult({ 346 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 347 }), 348 }, 349 { 350 input: "aa b ", 351 expected: makeExpectedResult({ 352 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 353 }), 354 }, 355 { 356 input: "aa b c", 357 expected: makeExpectedResult({ 358 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 359 }), 360 }, 361 { 362 input: "second", 363 expected: makeExpectedResult({ 364 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1], 365 }), 366 }, 367 { 368 input: "2nd", 369 expected: makeExpectedResult({ 370 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1], 371 }), 372 }, 373 { 374 input: "third", 375 expected: makeExpectedResult({ 376 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[2], 377 }), 378 }, 379 { 380 input: "3rd", 381 expected: makeExpectedResult({ 382 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[2], 383 }), 384 }, 385 { 386 input: "fourth", 387 expected: makeExpectedResult({ 388 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[3], 389 setUtmParams: false, 390 }), 391 }, 392 { 393 input: "FoUrTh", 394 expected: makeExpectedResult({ 395 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[3], 396 setUtmParams: false, 397 }), 398 }, 399 ]; 400 401 // Disable Merino so we trigger only remote settings suggestions. 402 UrlbarPrefs.set("quicksuggest.online.enabled", false); 403 404 for (let { input, expected } of testCases) { 405 await check_results({ 406 context: createContext(input, { 407 providers: [UrlbarProviderQuickSuggest.name], 408 isPrivate: false, 409 }), 410 matches: expected ? [expected] : [], 411 }); 412 } 413 414 UrlbarPrefs.clear("quicksuggest.online.enabled"); 415 }); 416 417 add_task(async function merinoIsTopPick() { 418 const suggestion = JSON.parse(JSON.stringify(MERINO_SUGGESTIONS[0])); 419 420 // is_top_pick is specified as false. 421 suggestion.is_top_pick = false; 422 MerinoTestUtils.server.response.body.suggestions = [suggestion]; 423 await check_results({ 424 context: createContext("test", { 425 providers: [UrlbarProviderQuickSuggest.name], 426 isPrivate: false, 427 }), 428 matches: [ 429 makeExpectedResult({ 430 suggestion, 431 source: "merino", 432 provider: "amo", 433 }), 434 ], 435 }); 436 437 // is_top_pick is undefined. 438 delete suggestion.is_top_pick; 439 MerinoTestUtils.server.response.body.suggestions = [suggestion]; 440 await check_results({ 441 context: createContext("test", { 442 providers: [UrlbarProviderQuickSuggest.name], 443 isPrivate: false, 444 }), 445 matches: [ 446 makeExpectedResult({ 447 suggestion, 448 source: "merino", 449 provider: "amo", 450 }), 451 ], 452 }); 453 }); 454 455 // Tests the "Not relevant" command: a dismissed suggestion shouldn't be added. 456 add_task(async function notRelevant() { 457 // Disable Merino suggestions to make this task simpler. 458 UrlbarPrefs.set("quicksuggest.online.enabled", false); 459 460 await doDismissOneTest({ 461 result: makeExpectedResult({ 462 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 463 }), 464 command: "not_relevant", 465 feature: QuickSuggest.getFeature("AddonSuggestions"), 466 queriesForDismissals: [ 467 { 468 query: REMOTE_SETTINGS_RESULTS[0].attachment[0].keywords[0], 469 }, 470 ], 471 queriesForOthers: [ 472 { 473 query: REMOTE_SETTINGS_RESULTS[0].attachment[1].keywords[0], 474 expectedResults: [ 475 makeExpectedResult({ 476 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1], 477 }), 478 ], 479 }, 480 ], 481 }); 482 483 UrlbarPrefs.clear("quicksuggest.online.enabled"); 484 }); 485 486 // Tests the "Not interested" command: all addon suggestions should be disabled 487 // and not added anymore. 488 add_task(async function notInterested() { 489 await doDismissAllTest({ 490 result: makeExpectedResult({ 491 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 492 }), 493 command: "not_interested", 494 feature: QuickSuggest.getFeature("AddonSuggestions"), 495 pref: "suggest.addons", 496 queries: [ 497 { 498 query: REMOTE_SETTINGS_RESULTS[0].attachment[0].keywords[0], 499 }, 500 { 501 query: REMOTE_SETTINGS_RESULTS[0].attachment[1].keywords[0], 502 expectedResults: [ 503 makeExpectedResult({ 504 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[1], 505 }), 506 ], 507 }, 508 ], 509 }); 510 }); 511 512 // Tests the "show less frequently" behavior. 513 add_task(async function showLessFrequently() { 514 await doShowLessFrequentlyTests({ 515 feature: QuickSuggest.getFeature("AddonSuggestions"), 516 showLessFrequentlyCountPref: "addons.showLessFrequentlyCount", 517 nimbusCapVariable: "addonsShowLessFrequentlyCap", 518 expectedResult: makeExpectedResult({ 519 suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], 520 }), 521 keyword: "two words", 522 }); 523 }); 524 525 // The `Amo` Rust provider should be passed to the Rust component when querying 526 // depending on whether addon suggestions are enabled. 527 add_task(async function rustProviders() { 528 await doRustProvidersTests({ 529 searchString: "first", 530 tests: [ 531 { 532 prefs: { 533 "suggest.addons": true, 534 }, 535 expectedUrls: ["https://example.com/first-addon"], 536 }, 537 { 538 prefs: { 539 "suggest.addons": false, 540 }, 541 expectedUrls: [], 542 }, 543 ], 544 }); 545 546 UrlbarPrefs.clear("suggest.addons"); 547 await QuickSuggestTestUtils.forceSync(); 548 }); 549 550 function makeExpectedResult({ 551 suggestion, 552 source, 553 provider, 554 setUtmParams = true, 555 }) { 556 return QuickSuggestTestUtils.amoResult({ 557 source, 558 provider, 559 setUtmParams, 560 title: suggestion.title, 561 description: suggestion.description, 562 url: suggestion.url, 563 originalUrl: suggestion.url, 564 icon: suggestion.icon, 565 }); 566 }