lighthouse 8.6.0-dev.20211021 → 8.6.0

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.
Files changed (115) hide show
  1. package/dist/report/flow.js +13 -30
  2. package/dist/report/standalone.js +17 -17
  3. package/flow-report/assets/styles.css +30 -51
  4. package/flow-report/src/common.tsx +1 -1
  5. package/flow-report/src/header.tsx +2 -2
  6. package/flow-report/src/help-dialog.tsx +3 -3
  7. package/flow-report/src/i18n/i18n.tsx +13 -50
  8. package/flow-report/src/i18n/ui-strings.js +0 -48
  9. package/flow-report/src/icons.tsx +0 -7
  10. package/flow-report/src/sidebar/sidebar.tsx +6 -8
  11. package/flow-report/src/summary/category.tsx +18 -17
  12. package/flow-report/src/summary/summary.tsx +10 -10
  13. package/flow-report/src/topbar.tsx +5 -9
  14. package/flow-report/src/util.ts +5 -0
  15. package/flow-report/src/wrappers/category-score.tsx +3 -0
  16. package/flow-report/test/common-test.tsx +1 -1
  17. package/flow-report/test/summary/category-test.tsx +5 -9
  18. package/flow-report/test/summary/summary-test.tsx +1 -1
  19. package/flow-report/test/topbar-test.tsx +0 -1
  20. package/flow-report/tsconfig.json +0 -1
  21. package/jest.config.js +0 -1
  22. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  23. package/lighthouse-core/audits/accessibility/axe-audit.js +1 -7
  24. package/lighthouse-core/audits/redirects-http.js +59 -0
  25. package/lighthouse-core/config/config-helpers.js +0 -1
  26. package/lighthouse-core/config/default-config.js +11 -0
  27. package/lighthouse-core/fraggle-rock/config/config.js +1 -1
  28. package/lighthouse-core/gather/gatherers/accessibility.js +0 -27
  29. package/lighthouse-core/gather/gatherers/http-redirect.js +46 -0
  30. package/lighthouse-core/lib/asset-saver.js +3 -3
  31. package/lighthouse-core/lib/i18n/i18n.js +8 -19
  32. package/lighthouse-core/lib/i18n/locales/en-US.ctc.json +6699 -0
  33. package/lighthouse-core/lib/i18n/locales/en-XL.ctc.json +6672 -0
  34. package/lighthouse-core/util-commonjs.js +37 -25
  35. package/package.json +4 -8
  36. package/readme.md +3 -3
  37. package/report/assets/styles.css +28 -157
  38. package/report/assets/templates.html +47 -2
  39. package/report/clients/psi.js +158 -0
  40. package/report/renderer/category-renderer.js +1 -30
  41. package/report/renderer/components.js +34 -16
  42. package/report/renderer/performance-category-renderer.js +10 -5
  43. package/report/renderer/report-renderer.js +33 -56
  44. package/report/renderer/util.js +37 -25
  45. package/report/test/clients/psi-test.js +143 -0
  46. package/report/test/renderer/category-renderer-test.js +1 -5
  47. package/report/test/renderer/i18n-test.js +7 -0
  48. package/report/test/renderer/performance-category-renderer-test.js +1 -1
  49. package/report/test/renderer/pwa-category-renderer-test.js +2 -2
  50. package/report/test/renderer/report-renderer-axe-test.js +10 -5
  51. package/report/test/renderer/report-renderer-test.js +11 -9
  52. package/report/test-assets/faux-psi-template.html +4 -1
  53. package/report/test-assets/faux-psi.js +16 -39
  54. package/shared/localization/format.js +17 -50
  55. package/shared/localization/locales/ar-XB.json +24 -0
  56. package/shared/localization/locales/ar.json +24 -0
  57. package/shared/localization/locales/bg.json +24 -0
  58. package/shared/localization/locales/ca.json +24 -0
  59. package/shared/localization/locales/cs.json +24 -0
  60. package/shared/localization/locales/da.json +24 -0
  61. package/shared/localization/locales/de.json +24 -0
  62. package/shared/localization/locales/el.json +24 -0
  63. package/shared/localization/locales/en-GB.json +24 -0
  64. package/shared/localization/locales/en-US.ctc.json +6877 -0
  65. package/shared/localization/locales/en-US.json +23 -26
  66. package/shared/localization/locales/en-XA.json +24 -0
  67. package/shared/localization/locales/en-XL.ctc.json +6852 -0
  68. package/shared/localization/locales/en-XL.json +23 -26
  69. package/shared/localization/locales/es-419.json +24 -0
  70. package/shared/localization/locales/es.json +24 -0
  71. package/shared/localization/locales/fi.json +24 -0
  72. package/shared/localization/locales/fil.json +24 -0
  73. package/shared/localization/locales/fr.json +24 -0
  74. package/shared/localization/locales/he.json +24 -0
  75. package/shared/localization/locales/hi.json +24 -0
  76. package/shared/localization/locales/hr.json +24 -0
  77. package/shared/localization/locales/hu.json +24 -0
  78. package/shared/localization/locales/id.json +24 -0
  79. package/shared/localization/locales/it.json +24 -0
  80. package/shared/localization/locales/ja.json +24 -0
  81. package/shared/localization/locales/ko.json +24 -0
  82. package/shared/localization/locales/lt.json +24 -0
  83. package/shared/localization/locales/lv.json +24 -0
  84. package/shared/localization/locales/nl.json +24 -0
  85. package/shared/localization/locales/no.json +24 -0
  86. package/shared/localization/locales/pl.json +24 -0
  87. package/shared/localization/locales/pt-PT.json +24 -0
  88. package/shared/localization/locales/pt.json +24 -0
  89. package/shared/localization/locales/ro.json +24 -0
  90. package/shared/localization/locales/ru.json +24 -0
  91. package/shared/localization/locales/sk.json +24 -0
  92. package/shared/localization/locales/sl.json +24 -0
  93. package/shared/localization/locales/sr-Latn.json +24 -0
  94. package/shared/localization/locales/sr.json +24 -0
  95. package/shared/localization/locales/sv.json +24 -0
  96. package/shared/localization/locales/ta.json +24 -0
  97. package/shared/localization/locales/te.json +24 -0
  98. package/shared/localization/locales/th.json +24 -0
  99. package/shared/localization/locales/tr.json +24 -0
  100. package/shared/localization/locales/uk.json +24 -0
  101. package/shared/localization/locales/vi.json +24 -0
  102. package/shared/localization/locales/zh-HK.json +24 -0
  103. package/shared/localization/locales/zh-TW.json +24 -0
  104. package/shared/localization/locales/zh.json +24 -0
  105. package/shared/localization/locales.js +0 -6
  106. package/shared/test/localization/format-test.js +9 -45
  107. package/shared/test/localization/swap-locale-test.js +12 -0
  108. package/third-party/download-content-shell/download-content-shell.js +2 -2
  109. package/third-party/download-content-shell/utils.js +24 -0
  110. package/tsconfig.json +1 -0
  111. package/types/artifacts.d.ts +3 -1
  112. package/types/enquirer.d.ts +1 -3
  113. package/types/node.d.ts +5 -5
  114. package/flow-report/test/wrappers/category-score-test.tsx +0 -87
  115. package/report/test/clients/bundle-test.js +0 -62
@@ -85,64 +85,44 @@ export class ReportRenderer {
85
85
  _renderReportFooter(report) {
86
86
  const footer = this._dom.createComponent('footer');
87
87
 
88
- this._renderMetaBlock(report, footer);
88
+ const env = this._dom.find('.lh-env__items', footer);
89
+ env.id = 'runtime-settings';
90
+ this._dom.find('.lh-env__title', footer).textContent = Util.i18n.strings.runtimeSettingsTitle;
91
+
92
+ const envValues = Util.getEnvironmentDisplayValues(report.configSettings || {});
93
+ const runtimeValues = [
94
+ {name: Util.i18n.strings.runtimeSettingsUrl, description: report.finalUrl},
95
+ {name: Util.i18n.strings.runtimeSettingsFetchTime,
96
+ description: Util.i18n.formatDateTime(report.fetchTime)},
97
+ ...envValues,
98
+ {name: Util.i18n.strings.runtimeSettingsChannel, description: report.configSettings.channel},
99
+ {name: Util.i18n.strings.runtimeSettingsUA, description: report.userAgent},
100
+ {name: Util.i18n.strings.runtimeSettingsUANetwork, description: report.environment &&
101
+ report.environment.networkUserAgent},
102
+ {name: Util.i18n.strings.runtimeSettingsBenchmark, description: report.environment &&
103
+ report.environment.benchmarkIndex.toFixed(0)},
104
+ ];
105
+ if (report.environment.credits && report.environment.credits['axe-core']) {
106
+ runtimeValues.push({
107
+ name: Util.i18n.strings.runtimeSettingsAxeVersion,
108
+ description: report.environment.credits['axe-core'],
109
+ });
110
+ }
111
+
112
+ for (const runtime of runtimeValues) {
113
+ if (!runtime.description) continue;
114
+
115
+ const item = this._dom.createComponent('envItem');
116
+ this._dom.find('.lh-env__name', item).textContent = runtime.name;
117
+ this._dom.find('.lh-env__description', item).textContent = runtime.description;
118
+ env.appendChild(item);
119
+ }
89
120
 
90
121
  this._dom.find('.lh-footer__version_issue', footer).textContent = Util.i18n.strings.footerIssue;
91
122
  this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion;
92
123
  return footer;
93
124
  }
94
125
 
95
- /**
96
- * @param {LH.ReportResult} report
97
- * @param {DocumentFragment} footer
98
- */
99
- _renderMetaBlock(report, footer) {
100
- const envValues = Util.getEmulationDescriptions(report.configSettings || {});
101
-
102
-
103
- const match = report.userAgent.match(/(\w*Chrome\/[\d.]+)/); // \w* to include 'HeadlessChrome'
104
- const chromeVer = Array.isArray(match)
105
- ? match[1].replace('/', ' ').replace('Chrome', 'Chromium')
106
- : 'Chromium';
107
- const channel = report.configSettings.channel;
108
- const benchmarkIndex = report.environment.benchmarkIndex.toFixed(0);
109
- const axeVersion = report.environment.credits['axe-core'];
110
-
111
- // [CSS icon class, textContent, tooltipText]
112
- const metaItems = [
113
- ['date',
114
- `Captured at ${Util.i18n.formatDateTime(report.fetchTime)}`],
115
- ['devices',
116
- `${envValues.deviceEmulation} with Lighthouse ${report.lighthouseVersion}`,
117
- `${Util.i18n.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}` +
118
- `\n${Util.i18n.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}` +
119
- (axeVersion ? `\n${Util.i18n.strings.runtimeSettingsAxeVersion}: ${axeVersion}` : '')],
120
- ['samples-one',
121
- Util.i18n.strings.runtimeSingleLoad,
122
- Util.i18n.strings.runtimeSingleLoadTooltip],
123
- ['stopwatch',
124
- Util.i18n.strings.runtimeAnalysisWindow],
125
- ['networkspeed',
126
- `${envValues.summary}`,
127
- `${Util.i18n.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`],
128
- ['chrome',
129
- `Using ${chromeVer}` + (channel ? ` with ${channel}` : ''),
130
- `${Util.i18n.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`],
131
- ];
132
-
133
- const metaItemsEl = this._dom.find('.lh-meta__items', footer);
134
- for (const [iconname, text, tooltip] of metaItems) {
135
- const itemEl = this._dom.createChildOf(metaItemsEl, 'li', 'lh-meta__item');
136
- itemEl.textContent = text;
137
- if (tooltip) {
138
- itemEl.classList.add('lh-tooltip-boundary');
139
- const tooltipEl = this._dom.createChildOf(itemEl, 'div', 'lh-tooltip');
140
- tooltipEl.textContent = tooltip;
141
- }
142
- itemEl.classList.add('lh-report-icon', `lh-report-icon--${iconname}`);
143
- }
144
- }
145
-
146
126
  /**
147
127
  * Returns a div with a list of top-level warnings, or an empty div if no warnings.
148
128
  * @param {LH.ReportResult} report
@@ -267,9 +247,8 @@ export class ReportRenderer {
267
247
  headerContainer.classList.add('lh-header--solo-category');
268
248
  }
269
249
 
270
- const scoreScale = this._dom.createElement('div');
271
- scoreScale.append(this._dom.createComponent('scorescale'));
272
250
  if (scoreHeader) {
251
+ const scoreScale = this._dom.createComponent('scorescale');
273
252
  const scoresContainer = this._dom.find('.lh-scores-container', headerContainer);
274
253
  scoreHeader.append(
275
254
  ...this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers));
@@ -296,8 +275,6 @@ export class ReportRenderer {
296
275
  ));
297
276
  }
298
277
 
299
- categoryRenderer.injectFinalScreenshot(categories, report.audits, scoreScale);
300
-
301
278
  const reportFragment = this._dom.createFragment();
302
279
  reportFragment.append(this._dom.createComponent('styles'));
303
280
  const topbarDocumentFragment = this._renderReportTopbar(report);
@@ -167,8 +167,6 @@ export class Util {
167
167
 
168
168
  /**
169
169
  * Convert a score to a rating label.
170
- * TODO: Return `'error'` for `score === null && !scoreDisplayMode`.
171
- *
172
170
  * @param {number|null} score
173
171
  * @param {string=} scoreDisplayMode
174
172
  * @return {string}
@@ -389,21 +387,43 @@ export class Util {
389
387
  return hostname.split('.').slice(-splitTld.length).join('.');
390
388
  }
391
389
 
390
+ /**
391
+ * @param {LH.Result['configSettings']} settings
392
+ * @return {!Array<{name: string, description: string}>}
393
+ */
394
+ static getEnvironmentDisplayValues(settings) {
395
+ const emulationDesc = Util.getEmulationDescriptions(settings);
396
+
397
+ return [
398
+ {
399
+ name: Util.i18n.strings.runtimeSettingsDevice,
400
+ description: emulationDesc.deviceEmulation,
401
+ },
402
+ {
403
+ name: Util.i18n.strings.runtimeSettingsNetworkThrottling,
404
+ description: emulationDesc.networkThrottling,
405
+ },
406
+ {
407
+ name: Util.i18n.strings.runtimeSettingsCPUThrottling,
408
+ description: emulationDesc.cpuThrottling,
409
+ },
410
+ ];
411
+ }
392
412
 
393
413
  /**
394
414
  * @param {LH.Result['configSettings']} settings
395
- * @return {!{deviceEmulation: string, networkThrottling: string, cpuThrottling: string, summary: string}}
415
+ * @return {{deviceEmulation: string, networkThrottling: string, cpuThrottling: string}}
396
416
  */
397
417
  static getEmulationDescriptions(settings) {
398
418
  let cpuThrottling;
399
419
  let networkThrottling;
400
- let summary;
401
420
 
402
421
  const throttling = settings.throttling;
403
422
 
404
423
  switch (settings.throttlingMethod) {
405
424
  case 'provided':
406
- summary = networkThrottling = cpuThrottling = Util.i18n.strings.throttlingProvided;
425
+ cpuThrottling = Util.i18n.strings.throttlingProvided;
426
+ networkThrottling = Util.i18n.strings.throttlingProvided;
407
427
  break;
408
428
  case 'devtools': {
409
429
  const {cpuSlowdownMultiplier, requestLatencyMs} = throttling;
@@ -411,13 +431,6 @@ export class Util {
411
431
  networkThrottling = `${Util.i18n.formatNumber(requestLatencyMs)}${NBSP}ms HTTP RTT, ` +
412
432
  `${Util.i18n.formatNumber(throttling.downloadThroughputKbps)}${NBSP}Kbps down, ` +
413
433
  `${Util.i18n.formatNumber(throttling.uploadThroughputKbps)}${NBSP}Kbps up (DevTools)`;
414
-
415
- const isSlow4G = () => {
416
- return requestLatencyMs === 150 * 3.75 &&
417
- throttling.downloadThroughputKbps === 1.6 * 1024 * 0.9 &&
418
- throttling.uploadThroughputKbps === 750 * 0.9;
419
- };
420
- summary = `${isSlow4G() ? 'Slow 4G' : 'Custom'} throttling by DevTools`;
421
434
  break;
422
435
  }
423
436
  case 'simulate': {
@@ -425,15 +438,11 @@ export class Util {
425
438
  cpuThrottling = `${Util.i18n.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`;
426
439
  networkThrottling = `${Util.i18n.formatNumber(rttMs)}${NBSP}ms TCP RTT, ` +
427
440
  `${Util.i18n.formatNumber(throughputKbps)}${NBSP}Kbps throughput (Simulated)`;
428
-
429
- const isSlow4G = () => {
430
- return rttMs === 150 && throughputKbps === 1.6 * 1024;
431
- };
432
- summary = isSlow4G() ? 'Simulated slow 4G' : 'Custom simulated throttling';
433
441
  break;
434
442
  }
435
443
  default:
436
- summary = cpuThrottling = networkThrottling = Util.i18n.strings.runtimeUnknown;
444
+ cpuThrottling = Util.i18n.strings.runtimeUnknown;
445
+ networkThrottling = Util.i18n.strings.runtimeUnknown;
437
446
  }
438
447
 
439
448
  // TODO(paulirish): revise Runtime Settings strings: https://github.com/GoogleChrome/lighthouse/pull/11796
@@ -446,7 +455,6 @@ export class Util {
446
455
  deviceEmulation,
447
456
  cpuThrottling,
448
457
  networkThrottling,
449
- summary,
450
458
  };
451
459
  }
452
460
 
@@ -629,12 +637,22 @@ Util.UIStrings = {
629
637
  /** Option in a dropdown menu that toggles the themeing of the report between Light(default) and Dark themes. */
630
638
  dropdownDarkTheme: 'Toggle Dark Theme',
631
639
 
640
+ /** Title of the Runtime settings table in a Lighthouse report. Runtime settings are the environment configurations that a specific report used at auditing time. */
641
+ runtimeSettingsTitle: 'Runtime Settings',
642
+ /** Label for a row in a table that shows the URL that was audited during a Lighthouse run. */
643
+ runtimeSettingsUrl: 'URL',
644
+ /** Label for a row in a table that shows the time at which a Lighthouse run was conducted; formatted as a timestamp, e.g. Jan 1, 1970 12:00 AM UTC. */
645
+ runtimeSettingsFetchTime: 'Fetch Time',
632
646
  /** Label for a row in a table that describes the kind of device that was emulated for the Lighthouse run. Example values for row elements: 'No Emulation', 'Emulated Desktop', etc. */
633
647
  runtimeSettingsDevice: 'Device',
634
648
  /** Label for a row in a table that describes the network throttling conditions that were used during a Lighthouse run, if any. */
635
649
  runtimeSettingsNetworkThrottling: 'Network throttling',
636
650
  /** Label for a row in a table that describes the CPU throttling conditions that were used during a Lighthouse run, if any.*/
637
651
  runtimeSettingsCPUThrottling: 'CPU throttling',
652
+ /** Label for a row in a table that shows in what tool Lighthouse is being run (e.g. The lighthouse CLI, Chrome DevTools, Lightrider, WebPageTest, etc). */
653
+ runtimeSettingsChannel: 'Channel',
654
+ /** Label for a row in a table that shows the User Agent that was detected on the Host machine that ran Lighthouse. */
655
+ runtimeSettingsUA: 'User agent (host)',
638
656
  /** Label for a row in a table that shows the User Agent that was used to send out all network requests during the Lighthouse run. */
639
657
  runtimeSettingsUANetwork: 'User agent (network)',
640
658
  /** Label for a row in a table that shows the estimated CPU power of the machine running Lighthouse. Example row values: 532, 1492, 783. */
@@ -653,12 +671,6 @@ Util.UIStrings = {
653
671
  runtimeDesktopEmulation: 'Emulated Desktop',
654
672
  /** Descriptive explanation for a runtime setting that is set to an unknown value. */
655
673
  runtimeUnknown: 'Unknown',
656
- /** Descriptive label that this analysis run was from a single pageload of a browser (not a summary of hundreds of loads) */
657
- runtimeSingleLoad: 'Single page load',
658
- /** Descriptive label that this analysis only considers the initial load of the page, and no interaction beyond when the page had "fully loaded" */
659
- runtimeAnalysisWindow: 'Initial page load',
660
- /** Descriptive explanation that this analysis run was from a single pageload of a browser, whereas field data often summarizes hundreds+ of page loads */
661
- runtimeSingleLoadTooltip: 'This data is taken from a single page load, as opposed to field data summarizing many sessions.', // eslint-disable-line max-len
662
674
 
663
675
  /** Descriptive explanation for environment throttling that was provided by the runtime environment instead of provided by Lighthouse throttling. */
664
676
  throttlingProvided: 'Provided by environment',
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @license Copyright 2017 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+ 'use strict';
7
+
8
+ import {strict as assert} from 'assert';
9
+ import fs from 'fs';
10
+
11
+ import jsdom from 'jsdom';
12
+
13
+ import testUtils from '../../../lighthouse-core/test/test-utils.js';
14
+ import {prepareLabData} from '../../clients/psi.js';
15
+ import {Util} from '../../renderer/util.js';
16
+ import {I18n} from '../../renderer/i18n.js';
17
+ import {DOM} from '../../renderer/dom.js';
18
+ import {CategoryRenderer} from '../../renderer/category-renderer.js';
19
+ import {PerformanceCategoryRenderer} from '../../renderer/performance-category-renderer.js';
20
+ import {DetailsRenderer} from '../../renderer/details-renderer.js';
21
+ import {CriticalRequestChainRenderer} from '../../renderer/crc-details-renderer.js';
22
+ import {ElementScreenshotRenderer} from '../../renderer/element-screenshot-renderer.js';
23
+ import {ReportUIFeatures} from '../../renderer/report-ui-features.js';
24
+ import {LH_ROOT} from '../../../root.js';
25
+
26
+ const {itIfProtoExists, sampleResultsRoundtripStr} = testUtils.getProtoRoundTrip();
27
+ const sampleResultsStr =
28
+ fs.readFileSync(LH_ROOT + '/lighthouse-core/test/results/sample_v2.json', 'utf-8');
29
+
30
+ /* eslint-env jest */
31
+
32
+ describe('PSI', () => {
33
+ let document;
34
+ beforeAll(() => {
35
+ global.Util = Util;
36
+ global.I18n = I18n;
37
+ global.DOM = DOM;
38
+ global.CategoryRenderer = CategoryRenderer;
39
+ global.DetailsRenderer = DetailsRenderer;
40
+ global.PerformanceCategoryRenderer = PerformanceCategoryRenderer;
41
+ global.CriticalRequestChainRenderer = CriticalRequestChainRenderer;
42
+ global.ElementScreenshotRenderer = ElementScreenshotRenderer;
43
+ global.ReportUIFeatures = ReportUIFeatures;
44
+
45
+ const {window} = new jsdom.JSDOM();
46
+ document = window.document;
47
+ });
48
+
49
+ afterAll(() => {
50
+ global.I18n = undefined;
51
+ global.Util = undefined;
52
+ global.DOM = undefined;
53
+ global.CategoryRenderer = undefined;
54
+ global.DetailsRenderer = undefined;
55
+ global.PerformanceCategoryRenderer = undefined;
56
+ global.CriticalRequestChainRenderer = undefined;
57
+ global.ElementScreenshotRenderer = undefined;
58
+ global.ReportUIFeatures = undefined;
59
+ });
60
+
61
+ describe('psi prepareLabData helpers', () => {
62
+ describe('prepareLabData', () => {
63
+ itIfProtoExists('succeeds with LHResult object (roundtrip) input', () => {
64
+ const roundTripLHResult = /** @type {LH.Result} */ JSON.parse(sampleResultsRoundtripStr);
65
+ const result = prepareLabData(roundTripLHResult, document);
66
+
67
+ // Check that the report exists and has some content.
68
+ assert.ok(result.perfCategoryEl instanceof document.defaultView.Element);
69
+ assert.ok(result.perfCategoryEl.outerHTML.length > 50000, 'perfCategory HTML is populated');
70
+ // Assume using default locale.
71
+ const title = result.perfCategoryEl.querySelector('.lh-audit-group--metrics')
72
+ .querySelector('.lh-audit-group__title').textContent;
73
+ assert.equal(title, Util.UIStrings.labDataTitle);
74
+ });
75
+
76
+ it('succeeds with stringified LHResult input', () => {
77
+ const result = prepareLabData(sampleResultsStr, document);
78
+ assert.ok(result.scoreGaugeEl instanceof document.defaultView.Element);
79
+ assert.equal(result.scoreGaugeEl.querySelector('.lh-gauge__wrapper').href, '');
80
+ assert.ok(result.scoreGaugeEl.outerHTML.includes('<svg'), 'score gauge comes with SVG');
81
+
82
+ assert.ok(result.perfCategoryEl instanceof document.defaultView.Element);
83
+ assert.ok(result.perfCategoryEl.outerHTML.length > 50000, 'perfCategory HTML is populated');
84
+
85
+ assert.equal(typeof result.finalScreenshotDataUri, 'string');
86
+ assert.ok(result.finalScreenshotDataUri.startsWith('data:image/jpeg;base64,'));
87
+ });
88
+
89
+ it('throws if there is no perf category', () => {
90
+ const lhrWithoutPerf = JSON.parse(sampleResultsStr);
91
+ delete lhrWithoutPerf.categories.performance;
92
+ const lhrWithoutPerfStr = JSON.stringify(lhrWithoutPerf);
93
+
94
+ assert.throws(() => {
95
+ prepareLabData(lhrWithoutPerfStr, document);
96
+ }, /no performance category/i);
97
+ });
98
+
99
+ it('throws if there is no category groups', () => {
100
+ const lhrWithoutGroups = JSON.parse(sampleResultsStr);
101
+ delete lhrWithoutGroups.categoryGroups;
102
+ const lhrWithoutGroupsStr = JSON.stringify(lhrWithoutGroups);
103
+
104
+ assert.throws(() => {
105
+ prepareLabData(lhrWithoutGroupsStr, document);
106
+ }, /no category groups/i);
107
+ });
108
+
109
+ it('includes custom title and description', () => {
110
+ const {perfCategoryEl} = prepareLabData(sampleResultsStr, document);
111
+ const metricsGroupEl = perfCategoryEl.querySelector('.lh-audit-group--metrics');
112
+
113
+ // Assume using default locale.
114
+ // Replacing markdown because ".textContent" will be post-markdown.
115
+ const expectedDescription = Util.UIStrings.lsPerformanceCategoryDescription
116
+ .replace('[Lighthouse](https://developers.google.com/web/tools/lighthouse/)', 'Lighthouse');
117
+
118
+ // Assume using default locale.
119
+ const title = metricsGroupEl.querySelector('.lh-audit-group__title').textContent;
120
+ const description =
121
+ metricsGroupEl.querySelector('.lh-audit-group__description').textContent;
122
+ assert.equal(title, Util.UIStrings.labDataTitle);
123
+ assert.equal(description, expectedDescription);
124
+ });
125
+ });
126
+ });
127
+
128
+ describe('_getFinalScreenshot', () => {
129
+ it('gets a datauri as a string', () => {
130
+ const datauri = prepareLabData(sampleResultsStr, document).finalScreenshotDataUri;
131
+ assert.equal(typeof datauri, 'string');
132
+ assert.ok(datauri.startsWith('data:image/jpeg;base64,'));
133
+ });
134
+
135
+ it('returns null if there is no final-screenshot audit', () => {
136
+ const clonedResults = JSON.parse(sampleResultsStr);
137
+ delete clonedResults.audits['final-screenshot'];
138
+ const LHResultJsonString = JSON.stringify(clonedResults);
139
+ const datauri = prepareLabData(LHResultJsonString, document).finalScreenshotDataUri;
140
+ assert.equal(datauri, null);
141
+ });
142
+ });
143
+ });
@@ -409,10 +409,6 @@ describe('CategoryRenderer', () => {
409
409
  const passingRefs = categoryClone.auditRefs.filter(ref => ref.result.score === 1);
410
410
  passingRefs[0].result.warnings = ['Some warning'];
411
411
  passingRefs[1].result.warnings = ['Some warning'];
412
- // Make one audit n/a
413
- const audit = categoryClone.auditRefs.find(ref => ref.id === 'themed-omnibox');
414
- audit.result.scoreDisplayMode = 'notApplicable';
415
- audit.result.score = null;
416
412
 
417
413
  const elem = renderer.render(categoryClone, sampleResults.categoryGroups);
418
414
  const passedAudits = elem.querySelectorAll('.lh-clump--passed .lh-audit');
@@ -422,7 +418,7 @@ describe('CategoryRenderer', () => {
422
418
  const naAudits = elem.querySelectorAll('.lh-clump--notapplicable .lh-audit');
423
419
 
424
420
  assert.equal(passedAudits.length, 0);
425
- assert.equal(failedAudits.length, 5);
421
+ assert.equal(failedAudits.length, 6);
426
422
  assert.equal(warningAudits.length, 2);
427
423
  assert.equal(manualAudits.length, 3);
428
424
  assert.equal(naAudits.length, 1);
@@ -9,6 +9,7 @@ import {strict as assert} from 'assert';
9
9
 
10
10
  import {Util} from '../../renderer/util.js';
11
11
  import {I18n} from '../../renderer/i18n.js';
12
+ import {isNode12SmallIcu} from '../../../lighthouse-core/test/test-utils.js';
12
13
 
13
14
  // Require i18n to make sure Intl is polyfilled in Node without full-icu for testing.
14
15
  // When Util is run in a browser, Intl will be supplied natively (IE11+).
@@ -107,6 +108,9 @@ describe('util helpers', () => {
107
108
  });
108
109
 
109
110
  it('formats numbers based on locale', () => {
111
+ // COMPAT: Node 12 only has 'en' by default.
112
+ if (isNode12SmallIcu()) return;
113
+
110
114
  // Requires full-icu or Intl polyfill.
111
115
  const number = 12346.858558;
112
116
 
@@ -118,6 +122,9 @@ describe('util helpers', () => {
118
122
  });
119
123
 
120
124
  it('uses decimal comma with en-XA test locale', () => {
125
+ // COMPAT: Node 12 only has 'en' by default.
126
+ if (isNode12SmallIcu()) return;
127
+
121
128
  // Requires full-icu or Intl polyfill.
122
129
  const number = 12346.858558;
123
130
 
@@ -84,7 +84,7 @@ describe('PerfCategoryRenderer', () => {
84
84
  it('renders the metrics variance disclaimer as markdown', () => {
85
85
  const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
86
86
  const disclaimerEl =
87
- categoryDOM.querySelector('.lh-category-header__description > .lh-metrics__disclaimer');
87
+ categoryDOM.querySelector('.lh-audit-group--metrics > .lh-metrics__disclaimer');
88
88
 
89
89
  assert.ok(disclaimerEl.textContent.includes('Values are estimated'));
90
90
  const disclamerLink = disclaimerEl.querySelector('a');
@@ -107,12 +107,12 @@ describe('PwaCategoryRenderer', () => {
107
107
  const clone = JSON.parse(JSON.stringify(sampleResults));
108
108
  const category = clone.categories.pwa;
109
109
 
110
- // Set everything to passing, except for one. (themed-omnibox chosen randomly)
110
+ // Set everything to passing, except redirects-http set to n/a (as it is on localhost)
111
111
  for (const auditRef of category.auditRefs) {
112
112
  auditRef.result.score = 1;
113
113
  auditRef.result.scoreDisplayMode = 'binary';
114
114
  }
115
- const audit = category.auditRefs.find(ref => ref.id === 'themed-omnibox');
115
+ const audit = category.auditRefs.find(ref => ref.id === 'redirects-http');
116
116
  audit.result.scoreDisplayMode = 'notApplicable';
117
117
  audit.result.score = null;
118
118
 
@@ -46,7 +46,7 @@ describe('ReportRendererAxe', () => {
46
46
  global.Element = undefined;
47
47
  });
48
48
 
49
- it('renders without axe violations', async () => {
49
+ it('renders without axe violations', () => {
50
50
  const container = renderer._dom._document.createElement('main');
51
51
  const output = renderer.renderReport(sampleResults, container);
52
52
 
@@ -70,11 +70,16 @@ describe('ReportRendererAxe', () => {
70
70
  },
71
71
  };
72
72
 
73
- const axeResults = await axe.run(output, config);
74
- expect(axeResults.violations).toEqual([]);
73
+ return new Promise(resolve => {
74
+ axe.run(output, config, (error, {violations}) => {
75
+ expect(error).toBeNull();
76
+ expect(violations).toEqual([]);
77
+ resolve();
78
+ });
79
+ });
80
+ // This test takes 40s on fast hardware, and 50-60s on GHA.
81
+ // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
75
82
  },
76
- // This test takes 40s on fast hardware, and 50-60s on GHA.
77
- // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
78
83
  /* timeout= */ 100 * 1000
79
84
  );
80
85
  });
@@ -179,15 +179,17 @@ describe('ReportRenderer', () => {
179
179
  assert.ok(/Generated by Lighthouse \d/.test(footerContent), 'includes lh version');
180
180
  assert.ok(footerContent.match(TIMESTAMP_REGEX), 'includes timestamp');
181
181
 
182
- // Check env items were populated.
183
- const items = Array.from(footer.querySelectorAll('.lh-meta__item'));
184
- expect(items.length).toBeGreaterThanOrEqual(6);
185
-
186
- const itemsTxt = items.map(el => `${el.textContent} ${el.title}`).join('\n');
187
- expect(itemsTxt).toContain('Moto G4');
188
- expect(itemsTxt).toContain('RTT');
189
- expect(itemsTxt).toMatch(/\dx/);
190
- expect(itemsTxt).toContain(sampleResults.environment.networkUserAgent);
182
+ // Check runtime settings were populated.
183
+ const names = Array.from(footer.querySelectorAll('.lh-env__name'));
184
+ const descriptions = Array.from(footer.querySelectorAll('.lh-env__description'));
185
+ assert.ok(names.length >= 3);
186
+ assert.ok(descriptions.length >= 3);
187
+
188
+ const descriptionsTxt = descriptions.map(el => el.textContent).join('\n');
189
+ expect(descriptionsTxt).toContain('Moto G4');
190
+ expect(descriptionsTxt).toContain('RTT');
191
+ expect(descriptionsTxt).toMatch(/\dx/);
192
+ expect(descriptionsTxt).toContain(sampleResults.userAgent);
191
193
  });
192
194
  });
193
195
 
@@ -92,7 +92,10 @@ body {
92
92
  <body >
93
93
  <noscript>PSI requires JavaScript. Please enable.</noscript>
94
94
 
95
- <div class="tabset">
95
+ <div class="element-screenshots-container"></div>
96
+
97
+
98
+ <div class="tabset lh-vars lh-root">
96
99
 
97
100
  <input type="radio" name="tabset" id="tab1" aria-controls="mobile" checked>
98
101
  <label for="tab1">Mobile</label>
@@ -5,13 +5,9 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- /** @fileoverview This file exercises two LH reports within the same DOM. */
8
+ /** @fileoverview This file is a glorified call of prepareLabData. */
9
9
 
10
- /** @typedef {import('../clients/bundle.js')} lighthouseRenderer */
11
-
12
- /** @type {lighthouseRenderer} */
13
- // @ts-expect-error
14
- const lighthouseRenderer = window['report'];
10
+ /** @typedef {import('../clients/psi.js').PrepareLabDataResult} PrepareLabDataResult */
15
11
 
16
12
  (async function __initPsiReports__() {
17
13
  // @ts-expect-error
@@ -26,43 +22,24 @@ const lighthouseRenderer = window['report'];
26
22
  for (const [tabId, lhr] of Object.entries(lhrs)) {
27
23
  await distinguishLHR(lhr, tabId);
28
24
 
25
+ // @ts-expect-error
26
+ const pldd = /** @type {PrepareLabDataResult} */ (window.prepareLabData(lhr, document));
27
+ const {scoreGaugeEl, perfCategoryEl,
28
+ finalScreenshotDataUri, scoreScaleEl, installFeatures} = pldd;
29
+
29
30
  const container = document.querySelector(`#${tabId} main`);
30
31
  if (!container) throw new Error('Unexpected DOM. Bailing.');
31
-
32
- renderLHReport(lhr, container);
33
- }
34
- })();
35
-
36
- /**
37
- * @param {LH.Result} lhrData
38
- * @param {HTMLElement} reportContainer
39
- */
40
- function renderLHReport(lhrData, reportContainer) {
41
- /**
42
- * @param {Document} doc
43
- */
44
- function getRenderer(doc) {
45
- const dom = new lighthouseRenderer.DOM(doc);
46
- return new lighthouseRenderer.ReportRenderer(dom);
47
- }
48
-
49
- const renderer = getRenderer(reportContainer.ownerDocument);
50
- reportContainer.classList.add('lh-root', 'lh-vars');
51
-
52
- try {
53
- renderer.renderReport(lhrData, reportContainer);
54
- // TODO: handle topbar removal better
55
- // TODO: display warnings if appropriate.
56
- for (const el of reportContainer.querySelectorAll('.lh-topbar, .lh-warnings')) {
57
- el.setAttribute('hidden', 'true');
32
+ container.append(scoreGaugeEl);
33
+ container.append(scoreScaleEl);
34
+ if (finalScreenshotDataUri) {
35
+ const imgEl = document.createElement('img');
36
+ imgEl.src = finalScreenshotDataUri;
37
+ container.append(imgEl);
58
38
  }
59
- const features = new lighthouseRenderer.ReportUIFeatures(renderer._dom);
60
- features.initFeatures(lhrData);
61
- } catch (e) {
62
- console.error(e);
63
- reportContainer.textContent = 'Error: LHR failed to render.';
39
+ container.append(perfCategoryEl);
40
+ installFeatures(container);
64
41
  }
65
- }
42
+ })();
66
43
 
67
44
 
68
45
  /**