lighthouse 9.5.0-dev.20230126 → 9.5.0-dev.20230128

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -103,19 +103,29 @@ describe('ReportUIFeatures', () => {
103
103
  });
104
104
 
105
105
  describe('third-party filtering', () => {
106
- let container;
106
+ let lhrJson;
107
107
 
108
108
  before(() => {
109
109
  const lhr = JSON.parse(JSON.stringify(sampleResults));
110
110
  lhr.requestedUrl = lhr.finalDisplayedUrl = 'http://www.example.com';
111
+
111
112
  const webpAuditItemTemplate = {
112
113
  ...sampleResults.audits['modern-image-formats'].details.items[0],
113
114
  wastedBytes: 8.8 * 1024,
115
+ entity: undefined, // Remove entity classification from previous result.
116
+ };
117
+ const renderBlockingAuditItemTemplate = {
118
+ ...sampleResults.audits['render-blocking-resources'].details.items[0],
119
+ entity: undefined, // Remove entity classification from previous result.
120
+ };
121
+ const textCompressionAuditItemTemplate = {
122
+ ...sampleResults.audits['uses-text-compression'].details.items[0],
123
+ entity: undefined, // Remove entity classification from previous result.
124
+ };
125
+ const bootupTimeAuditItemTemplate = {
126
+ ...sampleResults.audits['bootup-time'].details.items[0],
127
+ entity: undefined, // Remove entity classification from previous result.
114
128
  };
115
- const renderBlockingAuditItemTemplate =
116
- sampleResults.audits['render-blocking-resources'].details.items[0];
117
- const textCompressionAuditItemTemplate =
118
- sampleResults.audits['uses-text-compression'].details.items[0];
119
129
 
120
130
  // Interleave first/third party URLs to test restoring order.
121
131
  lhr.audits['modern-image-formats'].details.items = [
@@ -202,79 +212,296 @@ describe('ReportUIFeatures', () => {
202
212
  },
203
213
  ];
204
214
 
205
- // render a report onto the UIFeature dom
206
- container = render(lhr);
207
- });
208
-
209
- it('filters out third party resources in on click', () => {
210
- const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
211
-
212
- function getUrlsInTable() {
213
- return dom
214
- .findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
215
- .map(el => el.textContent);
216
- }
217
-
218
- expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
219
- filterCheckbox.click();
220
- expect(getUrlsInTable()).toEqual(['/img2.jpg']);
221
- filterCheckbox.click();
222
- expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
223
- });
224
-
225
- it('filters out sub-item rows of third party resources on click', () => {
226
- dom.find('#unused-javascript', container);
227
- const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
228
-
229
- function getRowIdentifiers() {
230
- return dom
231
- .findAll(
232
- '#unused-javascript tbody tr:not(.lh-row--hidden)', container)
233
- .map(el => el.textContent);
234
- }
235
-
236
- const initialExpected = [
237
- '/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
238
- '10.0 KiB0.0 KiB',
239
- '20.0 KiB0.0 KiB',
240
- '/script2.js(www.example.com)24.0 KiB8.8 KiB',
241
- '30.0 KiB0.0 KiB',
242
- '40.0 KiB0.0 KiB',
243
- '/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
244
- '50.0 KiB0.0 KiB',
245
- '60.0 KiB0.0 KiB',
215
+ // A mix of 3P, 1P and Unattributable.
216
+ lhr.audits['bootup-time'].details.items = [
217
+ {
218
+ ...bootupTimeAuditItemTemplate,
219
+ url: 'http://www.example.com/dobetterweb/dbw_tester.html',
220
+ },
221
+ {
222
+ ...bootupTimeAuditItemTemplate,
223
+ url: 'Unattributable',
224
+ },
225
+ {
226
+ ...bootupTimeAuditItemTemplate,
227
+ url: 'http://www.example.com/dobetterweb/third_party/aggressive-promise-polyfill.js',
228
+ },
229
+ {
230
+ ...bootupTimeAuditItemTemplate,
231
+ url: 'http://www.cdn.com/script1.js',
232
+ },
246
233
  ];
247
234
 
248
- expect(getRowIdentifiers()).toEqual(initialExpected);
249
- filterCheckbox.click();
250
- expect(getRowIdentifiers()).toEqual([
251
- '/script2.js(www.example.com)24.0 KiB8.8 KiB',
252
- '30.0 KiB0.0 KiB',
253
- '40.0 KiB0.0 KiB',
254
- ]);
255
- filterCheckbox.click();
256
- expect(getRowIdentifiers()).toEqual(initialExpected);
235
+ lhrJson = lhr;
257
236
  });
258
237
 
259
- it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
260
- const checkboxClassName = 'lh-3p-filter-input';
261
-
262
- const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
263
- expect(yesCheckbox).toBeTruthy();
264
-
265
- expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
266
- .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
267
- });
268
-
269
- it('filter is hidden when just third party resources', () => {
270
- const filterControl =
271
- dom.find('#render-blocking-resources .lh-3p-filter', container);
272
- expect(filterControl.hidden).toEqual(true);
238
+ describe('entity-classification based filtering works', () => {
239
+ let container;
240
+
241
+ before(() => {
242
+ // Setup entity-classification with recognized entities first.
243
+ lhrJson.entities = {
244
+ firstParty: 'example.com',
245
+ list: [
246
+ {
247
+ name: 'example.com',
248
+ isFirstParty: true,
249
+ isUnrecognized: true,
250
+ },
251
+ {
252
+ name: 'cdn.com',
253
+ isUnrecognized: true,
254
+ },
255
+ {
256
+ name: 'notexample.com',
257
+ isUnrecognized: true,
258
+ },
259
+ {
260
+ name: 'google.com',
261
+ },
262
+ ],
263
+ entityIndexByOrigin: {
264
+ 'http://www.example.com': 0,
265
+ 'http://www.cdn.com': 1,
266
+ 'http://www.notexample.com': 2,
267
+ 'http://www.google.com': 3,
268
+ },
269
+ entityIndexByName: {
270
+ 'example.com': 0,
271
+ 'cdn.com': 1,
272
+ 'notexample.com': 2,
273
+ 'google.com': 3,
274
+ },
275
+ };
276
+
277
+ // Entity resolution is done during prepareReportResult
278
+ const result = ReportUtils.prepareReportResult(lhrJson);
279
+
280
+ // render a report onto the UIFeature dom
281
+ container = render(result);
282
+ });
283
+
284
+ it('all items that contain a url have been marked with an entity for filtering', () => {
285
+ ['modern-image-formats', 'render-blocking-resources', 'unused-javascript',
286
+ 'uses-text-compression'].forEach(audit => {
287
+ dom.findAll(`#${audit} tr`, container)
288
+ .filter(el => dom.findAll('.lh-text__url', el).length > 0)
289
+ .forEach(el => expect(el.dataset.entity).toBeTruthy());
290
+ });
291
+ });
292
+
293
+ it('filters out third party resources in on click', () => {
294
+ const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
295
+
296
+ function getUrlsInTable() {
297
+ return dom
298
+ .findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
299
+ .map(el => el.textContent);
300
+ }
301
+
302
+ expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
303
+ filterCheckbox.click();
304
+ expect(getUrlsInTable()).toEqual(['/img2.jpg']);
305
+ filterCheckbox.click();
306
+ expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
307
+
308
+ const filter3pCount = dom.find('#modern-image-formats .lh-3p-filter-count', container);
309
+ expect(filter3pCount.textContent).toEqual('2');
310
+ });
311
+
312
+ it('filters out sub-item rows of third party resources on click', () => {
313
+ dom.find('#unused-javascript', container);
314
+ const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
315
+
316
+ function getRowIdentifiers() {
317
+ return dom
318
+ .findAll(
319
+ '#unused-javascript tbody tr:not(.lh-row--hidden)', container)
320
+ .map(el => el.textContent);
321
+ }
322
+
323
+ const initialExpected = [
324
+ '/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
325
+ '10.0 KiB0.0 KiB',
326
+ '20.0 KiB0.0 KiB',
327
+ '/script2.js(www.example.com)24.0 KiB8.8 KiB',
328
+ '30.0 KiB0.0 KiB',
329
+ '40.0 KiB0.0 KiB',
330
+ '/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
331
+ '50.0 KiB0.0 KiB',
332
+ '60.0 KiB0.0 KiB',
333
+ ];
334
+
335
+ expect(getRowIdentifiers()).toEqual(initialExpected);
336
+ filterCheckbox.click();
337
+ expect(getRowIdentifiers()).toEqual([
338
+ '/script2.js(www.example.com)24.0 KiB8.8 KiB',
339
+ '30.0 KiB0.0 KiB',
340
+ '40.0 KiB0.0 KiB',
341
+ ]);
342
+ filterCheckbox.click();
343
+ expect(getRowIdentifiers()).toEqual(initialExpected);
344
+
345
+ const filter3pCount = dom.find('#unused-javascript .lh-3p-filter-count', container);
346
+ expect(filter3pCount.textContent).toEqual('2');
347
+ });
348
+
349
+ it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
350
+ const checkboxClassName = 'lh-3p-filter-input';
351
+
352
+ const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
353
+ expect(yesCheckbox).toBeTruthy();
354
+
355
+ expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
356
+ .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
357
+ });
358
+
359
+ it('treats non-classifiable url values as first-party', () => {
360
+ const auditEl = dom.find('#bootup-time', container);
361
+ const filterCheckboxEl = dom.find('.lh-3p-filter-input', auditEl);
362
+
363
+ // Ensure 3p filter is visible.
364
+ expect(dom.find('.lh-3p-filter', auditEl).hidden).toBeFalsy();
365
+
366
+ function getUrlsInTable() {
367
+ return dom.findAll('tbody tr:not(.lh-row--hidden) td:first-child', auditEl)
368
+ .map(el => el.textContent);
369
+ }
370
+
371
+ const preFilterRowSet = [
372
+ '/dobetterweb/dbw_tester.html(www.example.com)',
373
+ 'Unattributable',
374
+ '…third_party/aggressive-promise-polyfill.js(www.example.com)',
375
+ '/script1.js(www.cdn.com)',
376
+ ];
377
+
378
+ expect(getUrlsInTable()).toEqual(preFilterRowSet);
379
+ filterCheckboxEl.click();
380
+ expect(getUrlsInTable()).toEqual(preFilterRowSet.filter(
381
+ text => !text.includes('cdn.com')));
382
+ filterCheckboxEl.click();
383
+ expect(getUrlsInTable()).toEqual(preFilterRowSet);
384
+
385
+ const filter3pCountEl = dom.find('.lh-3p-filter-count', auditEl);
386
+ expect(filter3pCountEl.textContent).toEqual('1');
387
+ });
388
+
389
+ it('filter is hidden when just third party resources', () => {
390
+ const filterControl =
391
+ dom.find('#render-blocking-resources .lh-3p-filter', container);
392
+ expect(filterControl.hidden).toEqual(true);
393
+ // Expect the hidden filter to still count third parties correctly.
394
+ const filter3pCount = dom.find('#render-blocking-resources .lh-3p-filter-count',
395
+ container);
396
+ expect(filter3pCount.textContent).toEqual('3');
397
+ });
398
+
399
+ it('filter is hidden for just first party resources', () => {
400
+ const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
401
+ expect(filterControl.hidden).toEqual(true);
402
+ // Expect the hidden filter to still count third parties correctly.
403
+ const filter3pCount = dom.find('#uses-text-compression .lh-3p-filter-count', container);
404
+ expect(filter3pCount.textContent).toEqual('0');
405
+ });
273
406
  });
274
407
 
275
- it('filter is hidden for just first party resources', () => {
276
- const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
277
- expect(filterControl.hidden).toEqual(true);
408
+ describe('legacy third-party filtering continues to work', () => {
409
+ let container;
410
+
411
+ before(() => {
412
+ // Remove entity-classification audit to fall back to origin string match
413
+ // based third-party filtering (legacy)
414
+ delete lhrJson.entities;
415
+
416
+ const result = ReportUtils.prepareReportResult(lhrJson);
417
+
418
+ // render a report onto the UIFeature dom
419
+ container = render(result);
420
+ });
421
+
422
+ it('filters out third party resources in on click', () => {
423
+ const filterCheckbox = dom.find('#modern-image-formats .lh-3p-filter-input', container);
424
+
425
+ function getUrlsInTable() {
426
+ return dom
427
+ .findAll('#modern-image-formats tr:not(.lh-row--hidden) .lh-text__url a:first-child', container) // eslint-disable-line max-len
428
+ .map(el => el.textContent);
429
+ }
430
+
431
+ expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
432
+ filterCheckbox.click();
433
+ expect(getUrlsInTable()).toEqual(['/img2.jpg']);
434
+ filterCheckbox.click();
435
+ expect(getUrlsInTable()).toEqual(['/img1.jpg', '/img2.jpg', '/img3.jpg']);
436
+ const filter3pCount = dom.find('#modern-image-formats .lh-3p-filter-count', container);
437
+ expect(filter3pCount.textContent).toEqual('2');
438
+ });
439
+
440
+ it('filters out sub-item rows of third party resources on click', () => {
441
+ dom.find('#unused-javascript', container);
442
+ const filterCheckbox = dom.find('#unused-javascript .lh-3p-filter-input', container);
443
+
444
+ function getRowIdentifiers() {
445
+ return dom
446
+ .findAll(
447
+ '#unused-javascript tbody tr:not(.lh-row--hidden)', container)
448
+ .map(el => el.textContent);
449
+ }
450
+
451
+ const initialExpected = [
452
+ '/script1.js(www.cdn.com)24.0 KiB8.8 KiB',
453
+ '10.0 KiB0.0 KiB',
454
+ '20.0 KiB0.0 KiB',
455
+ '/script2.js(www.example.com)24.0 KiB8.8 KiB',
456
+ '30.0 KiB0.0 KiB',
457
+ '40.0 KiB0.0 KiB',
458
+ '/script3.js(www.notexample.com)24.0 KiB8.8 KiB',
459
+ '50.0 KiB0.0 KiB',
460
+ '60.0 KiB0.0 KiB',
461
+ ];
462
+
463
+ expect(getRowIdentifiers()).toEqual(initialExpected);
464
+ filterCheckbox.click();
465
+ expect(getRowIdentifiers()).toEqual([
466
+ '/script2.js(www.example.com)24.0 KiB8.8 KiB',
467
+ '30.0 KiB0.0 KiB',
468
+ '40.0 KiB0.0 KiB',
469
+ ]);
470
+
471
+ filterCheckbox.click();
472
+ expect(getRowIdentifiers()).toEqual(initialExpected);
473
+ const filter3pCount = dom.find('#unused-javascript .lh-3p-filter-count', container);
474
+ expect(filter3pCount.textContent).toEqual('2');
475
+ });
476
+
477
+ it('adds no filter for audits in thirdPartyFilterAuditExclusions', () => {
478
+ const checkboxClassName = 'lh-3p-filter-input';
479
+
480
+ const yesCheckbox = dom.find(`#modern-image-formats .${checkboxClassName}`, container);
481
+ expect(yesCheckbox).toBeTruthy();
482
+
483
+ expect(() => dom.find(`#uses-rel-preconnect .${checkboxClassName}`, container))
484
+ .toThrowError('query #uses-rel-preconnect .lh-3p-filter-input not found');
485
+ });
486
+
487
+ it('filter is hidden when just third party resources', () => {
488
+ const filterControl =
489
+ dom.find('#render-blocking-resources .lh-3p-filter', container);
490
+ expect(filterControl.hidden).toEqual(true);
491
+ // Expect the hidden filter to still count third parties correctly.
492
+ const filter3pCount = dom.find('#render-blocking-resources .lh-3p-filter-count',
493
+ container);
494
+ expect(filter3pCount.textContent).toEqual('3');
495
+ });
496
+
497
+ it('filter is hidden for just first party resources', () => {
498
+ const filterControl = dom.find('#uses-text-compression .lh-3p-filter', container);
499
+ expect(filterControl.hidden).toEqual(true);
500
+ // Expect the hidden filter to still count third parties correctly.
501
+ const filter3pCount = dom.find('#uses-text-compression .lh-3p-filter-count',
502
+ container);
503
+ expect(filter3pCount.textContent).toEqual('0');
504
+ });
278
505
  });
279
506
  });
280
507
 
@@ -98,6 +98,10 @@ describe('util helpers', () => {
98
98
  it('corrects underscored `notApplicable` scoreDisplayMode', () => {
99
99
  const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
100
100
 
101
+ // Avoid injecting entity names into audits that would would
102
+ // make the diff at the end of this test difficult.
103
+ delete clonedSampleResult.entities;
104
+
101
105
  let notApplicableCount = 0;
102
106
  Object.values(clonedSampleResult.audits).forEach(audit => {
103
107
  if (audit.scoreDisplayMode === 'notApplicable') {
@@ -117,6 +121,10 @@ describe('util helpers', () => {
117
121
  it('corrects undefined auditDetails.type to `debugdata`', () => {
118
122
  const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
119
123
 
124
+ // Avoid injecting entity names into audits that would would
125
+ // make the diff at the end of this test difficult.
126
+ delete clonedSampleResult.entities;
127
+
120
128
  // Delete debugdata details types.
121
129
  let undefinedCount = 0;
122
130
  for (const audit of Object.values(clonedSampleResult.audits)) {
@@ -136,6 +144,10 @@ describe('util helpers', () => {
136
144
  it('corrects `diagnostic` auditDetails.type to `debugdata`', () => {
137
145
  const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
138
146
 
147
+ // Avoid injecting entity names into audits that would would
148
+ // make the diff at the end of this test difficult.
149
+ delete clonedSampleResult.entities;
150
+
139
151
  // Change debugdata details types.
140
152
  let diagnosticCount = 0;
141
153
  for (const audit of Object.values(clonedSampleResult.audits)) {
@@ -155,6 +167,10 @@ describe('util helpers', () => {
155
167
  it('corrects screenshots in the `filmstrip` auditDetails.type', () => {
156
168
  const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
157
169
 
170
+ // Avoid injecting entity names into audits that would would
171
+ // make the diff at the end of this test difficult.
172
+ delete clonedSampleResult.entities;
173
+
158
174
  // Strip filmstrip screenshots of data URL prefix.
159
175
  let filmstripCount = 0;
160
176
  for (const audit of Object.values(clonedSampleResult.audits)) {
@@ -183,6 +199,9 @@ describe('util helpers', () => {
183
199
  },
184
200
  };
185
201
  delete clonedSampleResult.fullPageScreenshot;
202
+ // Avoid injecting entity names into audits that would would
203
+ // make the diff at the end of this test difficult.
204
+ delete clonedSampleResult.entities;
186
205
 
187
206
  assert.ok(clonedSampleResult.audits['full-page-screenshot'].details.nodes); // Make sure something's being tested.
188
207
  assert.notDeepStrictEqual(clonedSampleResult.audits, sampleResult.audits);
@@ -218,6 +237,10 @@ describe('util helpers', () => {
218
237
  it('converts old opportunity table column headings to consolidated table headings', () => {
219
238
  const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
220
239
 
240
+ // Avoid injecting entity names into audits that would would
241
+ // make the diff at the end of this test difficult.
242
+ delete clonedSampleResult.entities;
243
+
221
244
  const auditsWithTableDetails = Object.values(clonedSampleResult.audits)
222
245
  .filter(audit => audit.details?.type === 'table');
223
246
  assert.notEqual(auditsWithTableDetails.length, 0);
@@ -266,6 +289,34 @@ describe('util helpers', () => {
266
289
  const interactiveRef = perfAuditRefs.find(ref => ref.id === 'interactive');
267
290
  assert.strictEqual(interactiveRef.stackPacks, undefined);
268
291
  });
292
+
293
+ it('identifies entities on items of tables with urls', () => {
294
+ const clonedSampleResult = JSON.parse(JSON.stringify(sampleResult));
295
+
296
+ const auditsWithTableDetails = Object.values(clonedSampleResult.audits)
297
+ .filter(audit => audit.details?.type === 'table');
298
+ assert.notEqual(auditsWithTableDetails.length, 0);
299
+
300
+ // collect audit names that might have urls
301
+ const auditsThatDontHaveUrls = ['bf-cache', 'font-size']; // no urls in data-set
302
+ const auditsWithUrls = auditsWithTableDetails.filter(audit => {
303
+ if (auditsThatDontHaveUrls.includes(audit.id)) return false;
304
+ const urlFields = ['url', 'source-location'];
305
+ return audit.details.headings.some(heading =>
306
+ urlFields.includes(heading.valueType) ||
307
+ urlFields.includes(heading.subItemsHeading?.valueType)
308
+ );
309
+ }).map(audit => audit.id);
310
+ assert.notEqual(auditsWithUrls.length, 0);
311
+
312
+ const preparedResult = ReportUtils.prepareReportResult(clonedSampleResult);
313
+
314
+ // ensure each audit that had urls detected to have marked entities.
315
+ for (const id of auditsWithUrls) {
316
+ const foundEntities = preparedResult.audits[id].details.items.some(item => item.entity);
317
+ assert.equal(foundEntities, true);
318
+ }
319
+ });
269
320
  });
270
321
 
271
322
  describe('#shouldDisplayAsFraction', () => {