test_quicksuggest_exposures.js (16590B)
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 Suggest exposure suggestions. 6 7 // Notes on the following mock suggestions: 8 // 9 // * `rsSuggestionType` echoes the `suggestion_type` in the RS record so that 10 // the test can verify that matched results correspond to the expected 11 // suggestions/records. It's not a necessary or special property for exposure 12 // suggestions. 13 // * `score` ensures a stable sort vs. other suggestion types for the test. It's 14 // not necessary for exposure suggestions. 15 const REMOTE_SETTINGS_RECORDS = [ 16 { 17 type: "dynamic-suggestions", 18 suggestion_type: "test-exposure-aaa", 19 score: 1.0, 20 attachment: [ 21 { 22 keywords: ["aaa keyword", "aaa bbb keyword", "wikipedia"], 23 data: { 24 result: { 25 isHiddenExposure: true, 26 payload: { 27 rsSuggestionType: "test-exposure-aaa", 28 }, 29 }, 30 }, 31 }, 32 ], 33 }, 34 { 35 type: "dynamic-suggestions", 36 suggestion_type: "test-exposure-bbb", 37 score: 1.0, 38 attachment: { 39 keywords: ["bbb keyword", "aaa bbb keyword", "wikipedia"], 40 data: { 41 result: { 42 isHiddenExposure: true, 43 payload: { 44 rsSuggestionType: "test-exposure-bbb", 45 telemetryType: "bbb_telemetry_type", 46 }, 47 }, 48 }, 49 }, 50 }, 51 { 52 type: QuickSuggestTestUtils.RS_TYPE.WIKIPEDIA, 53 attachment: [QuickSuggestTestUtils.wikipediaRemoteSettings()], 54 }, 55 ]; 56 57 const EXPECTED_AAA_RESULT = makeExpectedResult({ 58 rsSuggestionType: "test-exposure-aaa", 59 }); 60 61 const EXPECTED_BBB_RESULT = makeExpectedResult({ 62 rsSuggestionType: "test-exposure-bbb", 63 telemetryType: "bbb_telemetry_type", 64 }); 65 66 const EXPECTED_WIKIPEDIA_RESULT = { 67 ...QuickSuggestTestUtils.wikipediaResult(), 68 exposureTelemetry: UrlbarUtils.EXPOSURE_TELEMETRY.NONE, 69 }; 70 71 add_setup(async function () { 72 // Add many exposure and AMP suggestions that have the "maxresults" keyword. 73 let maxResults = UrlbarPrefs.get("maxRichResults"); 74 Assert.greater(maxResults, 0, "This test expects maxRichResults to be > 0"); 75 let ampRecord = { 76 collection: QuickSuggestTestUtils.RS_COLLECTION.AMP, 77 type: QuickSuggestTestUtils.RS_TYPE.AMP, 78 attachment: [], 79 }; 80 REMOTE_SETTINGS_RECORDS.push(ampRecord); 81 for (let i = 0; i < maxResults; i++) { 82 ampRecord.attachment.push( 83 QuickSuggestTestUtils.ampRemoteSettings({ 84 keywords: ["maxresults"], 85 title: "maxresults " + i, 86 url: "https://example.com/maxresults/" + i, 87 }) 88 ); 89 REMOTE_SETTINGS_RECORDS.push({ 90 type: "dynamic-suggestions", 91 suggestion_type: "test-exposure-maxresults-" + i, 92 score: 1.0, 93 attachment: [ 94 { 95 keywords: ["maxresults"], 96 data: { 97 result: { 98 isHiddenExposure: true, 99 payload: { 100 rsSuggestionType: "test-exposure-maxresults-" + i, 101 }, 102 }, 103 }, 104 }, 105 ], 106 }); 107 } 108 109 await QuickSuggestTestUtils.ensureQuickSuggestInit({ 110 remoteSettingsRecords: REMOTE_SETTINGS_RECORDS, 111 prefs: [ 112 [ 113 "quicksuggest.dynamicSuggestionTypes", 114 "test-exposure-aaa,test-exposure-bbb", 115 ], 116 ["suggest.quicksuggest.all", true], 117 ["quicksuggest.ampTopPickCharThreshold", 0], 118 ], 119 }); 120 }); 121 122 add_task(async function telemetryType_default() { 123 Assert.equal( 124 QuickSuggest.getFeature("DynamicSuggestions").getSuggestionTelemetryType({ 125 data: { 126 result: { 127 isHiddenExposure: true, 128 }, 129 }, 130 }), 131 "exposure", 132 "Telemetry type should be correct when using default" 133 ); 134 }); 135 136 add_task(async function telemetryType_override() { 137 Assert.equal( 138 QuickSuggest.getFeature("DynamicSuggestions").getSuggestionTelemetryType({ 139 data: { 140 result: { 141 isHiddenExposure: true, 142 payload: { 143 telemetryType: "telemetry_type_override", 144 }, 145 }, 146 }, 147 }), 148 "telemetry_type_override", 149 "Telemetry type should be correct when overridden" 150 ); 151 }); 152 153 // Results for enabled exposure suggestions should always be added and have 154 // hidden `exposureTelemetry` no matter the value of the `exposureResults` or 155 // `showExposureResults` prefs. 156 add_task(async function basic() { 157 let queries = [ 158 { 159 query: "no match", 160 expected: [], 161 }, 162 { 163 query: "aaa keyword", 164 expected: [EXPECTED_AAA_RESULT], 165 }, 166 { 167 query: "bbb keyword", 168 expected: [EXPECTED_BBB_RESULT], 169 }, 170 { 171 query: "aaa bbb keyword", 172 // The order of the results is reversed since they have the same suggested 173 // index, due to how the muxer handles that. 174 expected: [EXPECTED_BBB_RESULT, EXPECTED_AAA_RESULT], 175 }, 176 ]; 177 178 let exposureResultsList = [ 179 undefined, 180 "", 181 "some_other_result", 182 "exposure", 183 "some_other_result,exposure", 184 ]; 185 186 for (let exposureResults of exposureResultsList) { 187 if (exposureResults === undefined) { 188 UrlbarPrefs.clear("exposureResults"); 189 } else { 190 UrlbarPrefs.set("exposureResults", exposureResults); 191 } 192 193 for (let showExposureResults of [undefined, true, false]) { 194 if (showExposureResults === undefined) { 195 UrlbarPrefs.clear("showExposureResults"); 196 } else { 197 UrlbarPrefs.set("showExposureResults", showExposureResults); 198 } 199 200 info( 201 "Doing basic subtest: " + 202 JSON.stringify({ 203 exposureResults, 204 showExposureResults, 205 }) 206 ); 207 208 await doQueries(queries); 209 } 210 } 211 212 UrlbarPrefs.clear("exposureResults"); 213 UrlbarPrefs.clear("showExposureResults"); 214 }); 215 216 // When only one exposure suggestion type is enabled, only its result should be 217 // returned. This task assumes multiples types were added to remote settings in 218 // the setup task. 219 add_task(async function oneSuggestionType() { 220 await withSuggestionTypesPref("test-exposure-bbb", async () => { 221 await doQueries([ 222 { 223 query: "aaa keyword", 224 expected: [], 225 }, 226 { 227 query: "bbb keyword", 228 expected: [EXPECTED_BBB_RESULT], 229 }, 230 { 231 query: "aaa bbb keyword", 232 expected: [EXPECTED_BBB_RESULT], 233 }, 234 ]); 235 }); 236 }); 237 238 // When no exposure suggestion types are enabled, no results should be added. 239 add_task(async function disabled() { 240 await withSuggestionTypesPref("", async () => { 241 await doQueries( 242 ["aaa keyword", "bbb keyword", "aaa bbb keyword"].map(query => ({ 243 query, 244 expected: [], 245 })) 246 ); 247 }); 248 }); 249 250 // When another visible suggestion also matches, both it and the exposure 251 // suggestion should be added. 252 add_task(async function otherVisibleSuggestionsAlsoMatched_1() { 253 await withSuggestionTypesPref("test-exposure-aaa", async () => { 254 await doQueries([ 255 { 256 query: "aaa keyword", 257 expected: [EXPECTED_AAA_RESULT], 258 }, 259 { 260 query: "wikipedia", 261 expected: [EXPECTED_WIKIPEDIA_RESULT, EXPECTED_AAA_RESULT], 262 }, 263 ]); 264 }); 265 }); 266 267 // When another visible suggestion also matches, both it and all specified 268 // exposure suggestions should be added. 269 add_task(async function otherVisibleSuggestionsAlsoMatched_2() { 270 await withSuggestionTypesPref( 271 "test-exposure-aaa,test-exposure-bbb", 272 async () => { 273 await doQueries([ 274 { 275 query: "aaa keyword", 276 expected: [EXPECTED_AAA_RESULT], 277 }, 278 { 279 query: "bbb keyword", 280 expected: [EXPECTED_BBB_RESULT], 281 }, 282 { 283 query: "aaa bbb keyword", 284 expected: [EXPECTED_BBB_RESULT, EXPECTED_AAA_RESULT], 285 }, 286 { 287 query: "wikipedia", 288 expected: [ 289 EXPECTED_WIKIPEDIA_RESULT, 290 EXPECTED_BBB_RESULT, 291 EXPECTED_AAA_RESULT, 292 ], 293 }, 294 ]); 295 } 296 ); 297 }); 298 299 // Tests with `maxResults` exposures. All should be added. 300 add_task(async function maxResults_exposuresOnly() { 301 let maxResults = UrlbarPrefs.get("maxRichResults"); 302 let exposureResults = []; 303 for (let i = 0; i < maxResults; i++) { 304 exposureResults.unshift( 305 makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i }) 306 ); 307 } 308 309 await doMaxResultsTest({ 310 expectedResults: [...exposureResults], 311 }); 312 }); 313 314 // Tests with `maxResults` exposures and and `maxResults` history. All exposures 315 // and history should be added since exposures are hidden. 316 add_task(async function maxResults_exposuresHistory() { 317 let maxResults = UrlbarPrefs.get("maxRichResults"); 318 let exposureResults = []; 319 let historyResults = []; 320 for (let i = 0; i < maxResults; i++) { 321 exposureResults.unshift( 322 makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i }) 323 ); 324 historyResults.push( 325 new UrlbarResult({ 326 type: UrlbarUtils.RESULT_TYPE.URL, 327 source: UrlbarUtils.RESULT_SOURCE.HISTORY, 328 payload: { url: "http://example.com/history/" + i }, 329 }) 330 ); 331 } 332 333 await doMaxResultsTest({ 334 includeHistory: true, 335 expectedResults: [...historyResults, ...exposureResults], 336 }); 337 }); 338 339 // Tests with `maxResults` exposures and and `maxResults` AMP suggestions. The 340 // first AMP result should be added since it's visible and only one visible 341 // Suggest result should be added. All exposures should be added since exposures 342 // are hidden. 343 add_task(async function maxResults_exposuresAmp() { 344 let maxResults = UrlbarPrefs.get("maxRichResults"); 345 let exposureResults = []; 346 for (let i = 0; i < maxResults; i++) { 347 exposureResults.unshift( 348 makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i }) 349 ); 350 } 351 352 await doMaxResultsTest({ 353 includeAmp: true, 354 expectedResults: [ 355 QuickSuggestTestUtils.ampResult({ 356 keyword: "maxresults", 357 title: "maxresults 0", 358 url: "https://example.com/maxresults/0", 359 suggestedIndex: -1, 360 }), 361 ...exposureResults, 362 ], 363 }); 364 }); 365 366 // Tests with `maxResults` exposures, history, and AMP suggestions. The first 367 // AMP result should be added since it's visible and only one visible Suggest 368 // result should be added. `maxResults` - 1 history results should be added. All 369 // exposures should be added since exposures are hidden. 370 add_task(async function maxResults_exposuresHistoryAmp() { 371 let maxResults = UrlbarPrefs.get("maxRichResults"); 372 let exposureResults = []; 373 let historyResults = []; 374 for (let i = 0; i < maxResults; i++) { 375 exposureResults.unshift( 376 makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i }) 377 ); 378 historyResults.push( 379 new UrlbarResult({ 380 type: UrlbarUtils.RESULT_TYPE.URL, 381 source: UrlbarUtils.RESULT_SOURCE.HISTORY, 382 payload: { url: "http://example.com/history/" + i }, 383 }) 384 ); 385 } 386 387 await doMaxResultsTest({ 388 includeAmp: true, 389 includeHistory: true, 390 expectedResults: [ 391 ...historyResults.slice(0, maxResults - 1), 392 QuickSuggestTestUtils.ampResult({ 393 keyword: "maxresults", 394 title: "maxresults 0", 395 url: "https://example.com/maxresults/0", 396 suggestedIndex: -1, 397 }), 398 ...exposureResults, 399 ], 400 }); 401 }); 402 403 async function doMaxResultsTest({ 404 expectedResults, 405 includeAmp = false, 406 includeHistory = false, 407 }) { 408 let maxResults = UrlbarPrefs.get("maxRichResults"); 409 let providerNames = [UrlbarProviderQuickSuggest.name]; 410 411 // If history results should be included, register a test provider that adds a 412 // bunch of history results. 413 let historyProvider; 414 let historyResults = []; 415 if (includeHistory) { 416 for (let i = 0; i < maxResults; i++) { 417 historyResults.push( 418 new UrlbarResult({ 419 type: UrlbarUtils.RESULT_TYPE.URL, 420 source: UrlbarUtils.RESULT_SOURCE.HISTORY, 421 payload: { url: "http://example.com/history/" + i }, 422 }) 423 ); 424 } 425 historyProvider = new UrlbarTestUtils.TestProvider({ 426 results: historyResults, 427 }); 428 UrlbarProvidersManager.registerProvider(historyProvider); 429 providerNames.push(historyProvider.name); 430 } 431 432 // Enable sponsored suggestions according to whether AMP results should be 433 // included. 434 UrlbarPrefs.set("suggest.quicksuggest.sponsored", includeAmp); 435 await QuickSuggestTestUtils.forceSync(); 436 437 // Generate the list of exposure suggestion types so we can trigger the 438 // exposure suggestions. 439 let exposureTypes = []; 440 for (let i = 0; i < maxResults; i++) { 441 exposureTypes.push("test-exposure-maxresults-" + i); 442 } 443 444 await withSuggestionTypesPref(exposureTypes.join(","), async () => { 445 await check_results({ 446 context: createContext("maxresults", { 447 providers: providerNames, 448 isPrivate: false, 449 }), 450 matches: expectedResults, 451 }); 452 }); 453 454 if (historyProvider) { 455 UrlbarProvidersManager.unregisterProvider(historyProvider); 456 } 457 UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); 458 await QuickSuggestTestUtils.forceSync(); 459 } 460 461 // Exposure suggestions should automatically bypass the usual `all` and 462 // sponsored prefs. 463 add_task(async function bypassPrefs() { 464 UrlbarPrefs.set("suggest.quicksuggest.all", false); 465 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false); 466 467 await withSuggestionTypesPref("test-exposure-aaa", async () => { 468 await doQueries([ 469 { 470 query: "aaa keyword", 471 expected: [EXPECTED_AAA_RESULT], 472 }, 473 { 474 query: "aaa bbb keyword", 475 expected: [EXPECTED_AAA_RESULT], 476 }, 477 { 478 query: "wikipedia", 479 expected: [EXPECTED_AAA_RESULT], 480 }, 481 { 482 query: "doesn't match", 483 expected: [], 484 }, 485 ]); 486 }); 487 488 UrlbarPrefs.set("suggest.quicksuggest.all", true); 489 UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); 490 await QuickSuggestTestUtils.forceSync(); 491 }); 492 493 // Tests the `quickSuggestDynamicSuggestionTypes` Nimbus variable with exposure 494 // suggestions. 495 add_task(async function nimbus() { 496 // Clear `dynamicSuggestionTypes` to make sure the value comes from the Nimbus 497 // variable and not the pref. 498 await withSuggestionTypesPref("", async () => { 499 let cleanup = await UrlbarTestUtils.initNimbusFeature({ 500 quickSuggestDynamicSuggestionTypes: "test-exposure-aaa,test-exposure-bbb", 501 }); 502 await QuickSuggestTestUtils.forceSync(); 503 await doQueries([ 504 { 505 query: "aaa keyword", 506 expected: [EXPECTED_AAA_RESULT], 507 }, 508 { 509 query: "aaa bbb keyword", 510 expected: [EXPECTED_BBB_RESULT, EXPECTED_AAA_RESULT], 511 }, 512 { 513 query: "wikipedia", 514 expected: [ 515 EXPECTED_WIKIPEDIA_RESULT, 516 EXPECTED_BBB_RESULT, 517 EXPECTED_AAA_RESULT, 518 ], 519 }, 520 { 521 query: "doesn't match", 522 expected: [], 523 }, 524 ]); 525 526 await cleanup(); 527 }); 528 }); 529 530 async function doQueries(queries) { 531 for (let { query, expected } of queries) { 532 info( 533 "Doing query: " + 534 JSON.stringify({ 535 query, 536 expected, 537 }) 538 ); 539 540 await check_results({ 541 context: createContext(query, { 542 providers: [UrlbarProviderQuickSuggest.name], 543 isPrivate: false, 544 }), 545 matches: expected, 546 }); 547 } 548 } 549 550 async function withSuggestionTypesPref(prefValue, callback) { 551 // Use `Services` to get the original pref value since `UrlbarPrefs` will 552 // parse the string value into a `Set`. 553 let originalPrefValue = Services.prefs.getCharPref( 554 "browser.urlbar.quicksuggest.dynamicSuggestionTypes" 555 ); 556 557 // Changing the pref (or Nimbus variable) to a different value will trigger 558 // ingest, so force sync afterward (or at least wait for ingest to finish). 559 UrlbarPrefs.set("quicksuggest.dynamicSuggestionTypes", prefValue); 560 await QuickSuggestTestUtils.forceSync(); 561 562 await callback(); 563 564 UrlbarPrefs.set("quicksuggest.dynamicSuggestionTypes", originalPrefValue); 565 await QuickSuggestTestUtils.forceSync(); 566 } 567 568 function makeExpectedResult({ rsSuggestionType, telemetryType = "exposure" }) { 569 return { 570 type: UrlbarUtils.RESULT_TYPE.DYNAMIC, 571 source: UrlbarUtils.RESULT_SOURCE.SEARCH, 572 heuristic: false, 573 exposureTelemetry: UrlbarUtils.EXPOSURE_TELEMETRY.HIDDEN, 574 payload: { 575 telemetryType, 576 rsSuggestionType, 577 source: "rust", 578 dynamicType: "exposure", 579 provider: "Dynamic", 580 suggestionType: rsSuggestionType, 581 isSponsored: false, 582 }, 583 }; 584 }