lighthouse 8.5.1 → 8.6.0-dev.20211016

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 (173) hide show
  1. package/changelog.md +5490 -0
  2. package/dist/report/flow.js +35 -12
  3. package/dist/report/standalone.js +20 -21
  4. package/flow-report/.eslintrc.cjs +49 -0
  5. package/flow-report/assets/styles.css +272 -9
  6. package/flow-report/jest.config.js +1 -1
  7. package/flow-report/package.json +4 -0
  8. package/flow-report/src/app.tsx +31 -9
  9. package/flow-report/src/common.tsx +86 -7
  10. package/flow-report/src/header.tsx +88 -0
  11. package/flow-report/src/help-dialog.tsx +120 -0
  12. package/flow-report/src/i18n/i18n.tsx +52 -0
  13. package/flow-report/src/i18n/localized-strings.js +11 -0
  14. package/flow-report/src/i18n/ui-strings.js +134 -0
  15. package/flow-report/src/icons.tsx +20 -3
  16. package/flow-report/src/sidebar/sidebar.tsx +11 -16
  17. package/flow-report/src/summary/category.tsx +39 -24
  18. package/flow-report/src/summary/summary.tsx +35 -32
  19. package/flow-report/src/topbar.tsx +54 -4
  20. package/flow-report/src/util.ts +26 -1
  21. package/flow-report/src/wrappers/report-renderer.tsx +1 -1
  22. package/flow-report/src/wrappers/report.tsx +6 -21
  23. package/flow-report/test/app-test.tsx +17 -13
  24. package/flow-report/test/common-test.tsx +89 -0
  25. package/flow-report/test/header-test.tsx +55 -0
  26. package/flow-report/test/sample-flow.ts +17 -0
  27. package/flow-report/test/setup/env-setup.ts +1 -0
  28. package/flow-report/test/sidebar/flow-test.tsx +6 -12
  29. package/flow-report/test/sidebar/sidebar-test.tsx +7 -13
  30. package/flow-report/test/summary/category-test.tsx +83 -34
  31. package/flow-report/test/summary/summary-test.tsx +12 -18
  32. package/flow-report/test/topbar-test.tsx +68 -0
  33. package/flow-report/test/util-test.tsx +1 -12
  34. package/flow-report/tsconfig.json +2 -0
  35. package/flow-report/types/flow-report.d.ts +2 -1
  36. package/jest.config.js +1 -15
  37. package/lighthouse-cli/bin.js +5 -0
  38. package/lighthouse-cli/cli-flags.js +10 -3
  39. package/lighthouse-cli/commands/commands.js +1 -0
  40. package/lighthouse-cli/commands/list-locales.js +16 -0
  41. package/lighthouse-cli/run.js +2 -2
  42. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  43. package/lighthouse-cli/test/smokehouse/report-assert.js +20 -4
  44. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -1
  45. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -1
  46. package/lighthouse-core/audits/seo/crawlable-anchors.js +10 -12
  47. package/lighthouse-core/computed/js-bundles.js +1 -2
  48. package/lighthouse-core/computed/unused-javascript-summary.js +3 -3
  49. package/lighthouse-core/config/config-helpers.js +1 -0
  50. package/lighthouse-core/config/config.js +2 -2
  51. package/lighthouse-core/config/default-config.js +5 -0
  52. package/lighthouse-core/fraggle-rock/api.js +10 -0
  53. package/lighthouse-core/fraggle-rock/config/filters.js +22 -3
  54. package/lighthouse-core/fraggle-rock/gather/driver.js +4 -0
  55. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +2 -2
  56. package/lighthouse-core/fraggle-rock/user-flow.js +35 -6
  57. package/lighthouse-core/gather/driver/navigation.js +5 -0
  58. package/lighthouse-core/gather/driver/prepare.js +14 -0
  59. package/lighthouse-core/gather/driver/storage.js +5 -0
  60. package/lighthouse-core/gather/gather-runner.js +2 -2
  61. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +5 -1
  62. package/lighthouse-core/gather/gatherers/full-page-screenshot.js +30 -24
  63. package/lighthouse-core/lib/cdt/Platform.js +55 -0
  64. package/lighthouse-core/lib/cdt/SDK.js +3 -123
  65. package/lighthouse-core/lib/cdt/generated/SourceMap.js +148 -312
  66. package/lighthouse-core/lib/csp-evaluator.js +2 -1
  67. package/lighthouse-core/lib/i18n/README.md +6 -6
  68. package/lighthouse-core/lib/i18n/i18n.js +26 -348
  69. package/lighthouse-core/lib/page-functions.js +4 -4
  70. package/lighthouse-core/lib/stack-packs.js +1 -13
  71. package/lighthouse-core/runner.js +6 -6
  72. package/lighthouse-core/scripts/manual-chrome-launcher.js +4 -1
  73. package/lighthouse-core/scripts/package.json +4 -0
  74. package/lighthouse-core/util-commonjs.js +20 -4
  75. package/package.json +19 -16
  76. package/readme.md +1 -1
  77. package/report/assets/standalone-template.html +0 -1
  78. package/report/assets/styles.css +16 -41
  79. package/report/assets/templates.html +42 -4
  80. package/report/clients/psi.js +1 -0
  81. package/report/clients/standalone.js +1 -2
  82. package/report/generator/file-namer.js +17 -8
  83. package/report/generator/report-generator.js +0 -1
  84. package/report/renderer/category-renderer.js +4 -16
  85. package/report/renderer/components.js +107 -71
  86. package/report/renderer/dom.js +19 -0
  87. package/report/renderer/logger.js +35 -2
  88. package/report/renderer/performance-category-renderer.js +23 -21
  89. package/report/renderer/pwa-category-renderer.js +1 -2
  90. package/report/renderer/report-renderer.js +21 -0
  91. package/report/renderer/report-ui-features.js +7 -19
  92. package/report/renderer/swap-locale-feature.js +82 -0
  93. package/report/renderer/util.js +20 -4
  94. package/report/test/clients/psi-test.js +0 -4
  95. package/report/test/generator/file-namer-test.js +2 -2
  96. package/report/test/renderer/category-renderer-test.js +1 -1
  97. package/report/test/renderer/performance-category-renderer-test.js +12 -0
  98. package/report/test/renderer/report-renderer-axe-test.js +86 -0
  99. package/report/test/renderer/report-renderer-test.js +0 -55
  100. package/report/test/renderer/util-test.js +68 -8
  101. package/report/test-assets/faux-psi-template.html +0 -1
  102. package/report/tsconfig.json +2 -1
  103. package/report/types/html-renderer.d.ts +2 -1
  104. package/root.js +18 -0
  105. package/shared/localization/format.js +455 -0
  106. package/{lighthouse-core/lib/cdt/Common.js → shared/localization/i18n-module.js} +3 -8
  107. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ar-XB.json +139 -4
  108. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ar.json +141 -6
  109. package/{lighthouse-core/lib/i18n → shared/localization}/locales/bg.json +139 -4
  110. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ca.json +140 -5
  111. package/{lighthouse-core/lib/i18n → shared/localization}/locales/cs.json +139 -4
  112. package/{lighthouse-core/lib/i18n → shared/localization}/locales/da.json +139 -4
  113. package/{lighthouse-core/lib/i18n → shared/localization}/locales/de.json +139 -4
  114. package/{lighthouse-core/lib/i18n → shared/localization}/locales/el.json +141 -6
  115. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-GB.json +139 -4
  116. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-US.json +129 -0
  117. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-XA.json +139 -4
  118. package/{lighthouse-core/lib/i18n → shared/localization}/locales/en-XL.json +129 -0
  119. package/{lighthouse-core/lib/i18n → shared/localization}/locales/es-419.json +139 -4
  120. package/{lighthouse-core/lib/i18n → shared/localization}/locales/es.json +139 -4
  121. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fi.json +139 -4
  122. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fil.json +139 -4
  123. package/{lighthouse-core/lib/i18n → shared/localization}/locales/fr.json +139 -4
  124. package/{lighthouse-core/lib/i18n → shared/localization}/locales/he.json +139 -4
  125. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hi.json +142 -7
  126. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hr.json +139 -4
  127. package/{lighthouse-core/lib/i18n → shared/localization}/locales/hu.json +139 -4
  128. package/{lighthouse-core/lib/i18n → shared/localization}/locales/id.json +139 -4
  129. package/{lighthouse-core/lib/i18n → shared/localization}/locales/it.json +139 -4
  130. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ja.json +139 -4
  131. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ko.json +139 -4
  132. package/{lighthouse-core/lib/i18n → shared/localization}/locales/lt.json +139 -4
  133. package/{lighthouse-core/lib/i18n → shared/localization}/locales/lv.json +139 -4
  134. package/{lighthouse-core/lib/i18n → shared/localization}/locales/nl.json +139 -4
  135. package/{lighthouse-core/lib/i18n → shared/localization}/locales/no.json +139 -4
  136. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pl.json +139 -4
  137. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pt-PT.json +139 -4
  138. package/{lighthouse-core/lib/i18n → shared/localization}/locales/pt.json +139 -4
  139. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ro.json +139 -4
  140. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ru.json +139 -4
  141. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sk.json +139 -4
  142. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sl.json +139 -4
  143. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sr-Latn.json +139 -4
  144. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sr.json +139 -4
  145. package/{lighthouse-core/lib/i18n → shared/localization}/locales/sv.json +139 -4
  146. package/{lighthouse-core/lib/i18n → shared/localization}/locales/ta.json +146 -11
  147. package/{lighthouse-core/lib/i18n → shared/localization}/locales/te.json +179 -44
  148. package/{lighthouse-core/lib/i18n → shared/localization}/locales/th.json +139 -4
  149. package/{lighthouse-core/lib/i18n → shared/localization}/locales/tr.json +139 -4
  150. package/{lighthouse-core/lib/i18n → shared/localization}/locales/uk.json +139 -4
  151. package/{lighthouse-core/lib/i18n → shared/localization}/locales/vi.json +139 -4
  152. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh-HK.json +139 -4
  153. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh-TW.json +139 -4
  154. package/{lighthouse-core/lib/i18n → shared/localization}/locales/zh.json +139 -4
  155. package/{lighthouse-core/lib/i18n → shared/localization}/locales.js +8 -1
  156. package/shared/localization/swap-flow-locale.js +20 -0
  157. package/{lighthouse-core/lib/i18n → shared/localization}/swap-locale.js +9 -7
  158. package/shared/test/localization/format-test.js +421 -0
  159. package/shared/test/localization/locales-test.js +47 -0
  160. package/shared/test/localization/swap-locale-test.js +140 -0
  161. package/shared/tsconfig.json +22 -0
  162. package/{lighthouse-core/lib → shared}/type-verifiers.js +0 -0
  163. package/shared/types/shared.d.ts +24 -0
  164. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +5 -3
  165. package/third-party/chromium-synchronization/installability-errors-test.js +2 -1
  166. package/tsconfig-all.json +1 -0
  167. package/tsconfig-base.json +1 -1
  168. package/tsconfig.json +2 -3
  169. package/types/config.d.ts +1 -0
  170. package/types/es-main.d.ts +18 -0
  171. package/types/externs.d.ts +2 -0
  172. package/types/lhr/flow.d.ts +7 -0
  173. package/types/lhr/i18n.d.ts +0 -3
@@ -28,7 +28,7 @@ import {toggleDarkTheme} from './features-util.js';
28
28
  import {openTreemap} from './open-tab.js';
29
29
  import {TopbarFeatures} from './topbar-features.js';
30
30
  import {Util} from './util.js';
31
- import {getFilenamePrefix} from '../generator/file-namer.js';
31
+ import {getLhrFilenamePrefix} from '../generator/file-namer.js';
32
32
 
33
33
  /**
34
34
  * @param {HTMLTableElement} tableEl
@@ -324,25 +324,13 @@ export class ReportUIFeatures {
324
324
  }
325
325
 
326
326
  /**
327
- * Downloads a file (blob) using a[download].
328
- * @param {Blob|File} blob The file to save.
327
+ * DevTools uses its own file manager to download files, so it redefines this function.
328
+ * Wrapper is necessary so DevTools can still override this function.
329
+ *
330
+ * @param {Blob|File} blob
329
331
  */
330
332
  _saveFile(blob) {
331
- const filename = getFilenamePrefix({
332
- finalUrl: this.json.finalUrl,
333
- fetchTime: this.json.fetchTime,
334
- });
335
-
336
- const ext = blob.type.match('json') ? '.json' : '.html';
337
-
338
- const a = this._dom.createElement('a');
339
- a.download = `${filename}${ext}`;
340
- this._dom.safelySetBlobHref(a, blob);
341
- this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM.
342
- a.click();
343
-
344
- // cleanup.
345
- this._document.body.removeChild(a);
346
- setTimeout(() => URL.revokeObjectURL(a.href), 500);
333
+ const filename = getLhrFilenamePrefix(this.json);
334
+ this._dom.saveFile(blob, filename);
347
335
  }
348
336
  }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @license Copyright 2021 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
+ /**
9
+ * @fileoverview Creates a <select> element, filled with all supported locales
10
+ */
11
+
12
+ /** @typedef {import('./dom.js').DOM} DOM */
13
+ /** @typedef {import('./report-ui-features').ReportUIFeatures} ReportUIFeatures */
14
+
15
+ export class SwapLocaleFeature {
16
+ /**
17
+ * @param {ReportUIFeatures} reportUIFeatures
18
+ * @param {DOM} dom
19
+ * @param {{onLocaleSelected: (localeModuleName: LH.Locale) => Promise<void>}} callbacks
20
+ * Specifiy the URL where the i18n module script can be found, and the URLS for the locale JSON files.
21
+ */
22
+ constructor(reportUIFeatures, dom, callbacks) {
23
+ this._reportUIFeatures = reportUIFeatures;
24
+ this._dom = dom;
25
+ this._localeSelectedCallback = callbacks.onLocaleSelected;
26
+ }
27
+
28
+ /**
29
+ * @param {Array<LH.Locale>} locales
30
+ */
31
+ enable(locales) {
32
+ if (!this._reportUIFeatures.json.i18n.icuMessagePaths) {
33
+ throw new Error('missing icuMessagePaths');
34
+ }
35
+
36
+ this._dom.find('.lh-tools-locale', this._dom.document()).classList.remove('lh-hidden');
37
+
38
+ const currentLocale = this._reportUIFeatures.json.configSettings.locale;
39
+
40
+ const containerEl = this._dom.find('.lh-tools-locale__selector-wrapper', this._dom.document());
41
+ containerEl.removeAttribute('aria-hidden');
42
+ const selectEl = this._dom.createChildOf(containerEl, 'select', 'lh-locale-selector');
43
+ selectEl.name = 'lh-locale-list';
44
+ selectEl.setAttribute('role', 'menuitem');
45
+
46
+ const toggleEl = this._dom.find('.lh-tool-locale__button', this._dom.document());
47
+ toggleEl.addEventListener('click', () => {
48
+ toggleEl.classList.toggle('lh-active');
49
+ });
50
+
51
+ for (const locale of locales) {
52
+ const optionEl = this._dom.createChildOf(selectEl, 'option', '');
53
+ optionEl.value = locale;
54
+ if (locale === currentLocale) optionEl.selected = true;
55
+
56
+ // @ts-expect-error: waiting for typescript 4.5. Might need to add "ES2020.Intl"
57
+ // to tsconfig libs.
58
+ // https://github.com/microsoft/TypeScript/pull/44022#issuecomment-915087098
59
+ if (window.Intl && Intl.DisplayNames) {
60
+ // @ts-expect-error
61
+ const currentLocaleDisplay = new Intl.DisplayNames([currentLocale], {type: 'language'});
62
+ // @ts-expect-error
63
+ const optionLocaleDisplay = new Intl.DisplayNames([locale], {type: 'language'});
64
+
65
+ const optionLocaleName = optionLocaleDisplay.of(locale);
66
+ const currentLocaleName = currentLocaleDisplay.of(locale);
67
+ if (optionLocaleName !== currentLocaleName) {
68
+ optionEl.textContent = `${optionLocaleName} – ${currentLocaleName}`;
69
+ } else {
70
+ optionEl.textContent = currentLocaleName;
71
+ }
72
+ } else {
73
+ optionEl.textContent = locale;
74
+ }
75
+ }
76
+
77
+ selectEl.addEventListener('change', () => {
78
+ const locale = /** @type {LH.Locale} */ (selectEl.value);
79
+ this._localeSelectedCallback(locale);
80
+ });
81
+ }
82
+ }
@@ -518,15 +518,31 @@ export class Util {
518
518
  * @param {LH.ReportResult.Category} category
519
519
  */
520
520
  static calculateCategoryFraction(category) {
521
- const numAudits = category.auditRefs.length;
522
-
521
+ let numPassableAudits = 0;
523
522
  let numPassed = 0;
523
+ let numInformative = 0;
524
524
  let totalWeight = 0;
525
525
  for (const auditRef of category.auditRefs) {
526
+ const auditPassed = Util.showAsPassed(auditRef.result);
527
+ const notDisplayed = !auditRef.group && category.id === 'performance';
528
+
529
+ // Don't count the audit if it's manual, N/A, or isn't displayed.
530
+ if (notDisplayed ||
531
+ auditRef.result.scoreDisplayMode === 'manual' ||
532
+ auditRef.result.scoreDisplayMode === 'notApplicable') {
533
+ continue;
534
+ } else if (auditRef.result.scoreDisplayMode === 'informative') {
535
+ if (!auditPassed) {
536
+ ++numInformative;
537
+ }
538
+ continue;
539
+ }
540
+
541
+ ++numPassableAudits;
526
542
  totalWeight += auditRef.weight;
527
- if (Util.showAsPassed(auditRef.result)) numPassed++;
543
+ if (auditPassed) numPassed++;
528
544
  }
529
- return {numPassed, numAudits, totalWeight};
545
+ return {numPassed, numPassableAudits, numInformative, totalWeight};
530
546
  }
531
547
  }
532
548
 
@@ -67,8 +67,6 @@ describe('PSI', () => {
67
67
  // Check that the report exists and has some content.
68
68
  assert.ok(result.perfCategoryEl instanceof document.defaultView.Element);
69
69
  assert.ok(result.perfCategoryEl.outerHTML.length > 50000, 'perfCategory HTML is populated');
70
- assert.ok(!result.perfCategoryEl.outerHTML.includes('lh-permalink'),
71
- 'PSI\'s perfCategory HTML doesn\'t include a lh-permalink element');
72
70
  // Assume using default locale.
73
71
  const title = result.perfCategoryEl.querySelector('.lh-audit-group--metrics')
74
72
  .querySelector('.lh-audit-group__title').textContent;
@@ -83,8 +81,6 @@ describe('PSI', () => {
83
81
 
84
82
  assert.ok(result.perfCategoryEl instanceof document.defaultView.Element);
85
83
  assert.ok(result.perfCategoryEl.outerHTML.length > 50000, 'perfCategory HTML is populated');
86
- assert.ok(!result.perfCategoryEl.outerHTML.includes('lh-permalink'),
87
- 'PSI\'s perfCategory HTML doesn\'t include a lh-permalink element');
88
84
 
89
85
  assert.equal(typeof result.finalScreenshotDataUri, 'string');
90
86
  assert.ok(result.finalScreenshotDataUri.startsWith('data:image/jpeg;base64,'));
@@ -7,7 +7,7 @@
7
7
 
8
8
  const assert = require('assert').strict;
9
9
 
10
- const getFilenamePrefix = require('../../generator/file-namer.js').getFilenamePrefix;
10
+ const getLhrFilenamePrefix = require('../../generator/file-namer.js').getLhrFilenamePrefix;
11
11
 
12
12
  /* eslint-env jest */
13
13
  describe('file-namer helper', () => {
@@ -16,7 +16,7 @@ describe('file-namer helper', () => {
16
16
  finalUrl: 'https://testexample.com',
17
17
  fetchTime: '2017-01-06T02:34:56.217Z',
18
18
  };
19
- const str = getFilenamePrefix(results);
19
+ const str = getLhrFilenamePrefix(results);
20
20
  // we want the filename to match user timezone, however these tests will run on multiple TZs
21
21
  assert.ok(str.startsWith('testexample.com'), 'hostname is missing');
22
22
  assert.ok(str.includes('2017-'), 'full year is missing');
@@ -297,7 +297,7 @@ describe('CategoryRenderer', () => {
297
297
  );
298
298
 
299
299
  const gauge = categoryDOM.querySelector('.lh-fraction__content');
300
- assert.equal(gauge.textContent.trim(), '49/54', 'fraction is included');
300
+ assert.equal(gauge.textContent.trim(), '13/18', 'fraction is included');
301
301
 
302
302
  const score = categoryDOM.querySelector('.lh-category-header');
303
303
  const title = score.querySelector('.lh-fraction__label');
@@ -69,6 +69,18 @@ describe('PerfCategoryRenderer', () => {
69
69
  assert.equal(timelineElements.length + nontimelineElements.length, metricAudits.length);
70
70
  });
71
71
 
72
+ it('does not render metrics section if no metric group audits', () => {
73
+ // Remove metrics from category
74
+ const newCategory = JSON.parse(JSON.stringify(category));
75
+ newCategory.auditRefs = category.auditRefs.filter(audit => audit.group !== 'metrics');
76
+
77
+ const categoryDOM = renderer.render(newCategory, sampleResults.categoryGroups);
78
+ const sections = categoryDOM.querySelectorAll('.lh-category > .lh-audit-group');
79
+ const metricSection = categoryDOM.querySelector('.lh-audit-group--metrics');
80
+ assert.ok(!metricSection);
81
+ assert.equal(sections.length, 4);
82
+ });
83
+
72
84
  it('renders the metrics variance disclaimer as markdown', () => {
73
85
  const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
74
86
  const disclaimerEl =
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @license Copyright 2021 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
+ /* eslint-env jest */
9
+
10
+ import jsdom from 'jsdom';
11
+
12
+ import {Util} from '../../renderer/util.js';
13
+ import {DOM} from '../../renderer/dom.js';
14
+ import {DetailsRenderer} from '../../renderer/details-renderer.js';
15
+ import {CategoryRenderer} from '../../renderer/category-renderer.js';
16
+ import {ReportRenderer} from '../../renderer/report-renderer.js';
17
+ import sampleResultsOrig from '../../../lighthouse-core/test/results/sample_v2.json';
18
+
19
+ describe('ReportRendererAxe', () => {
20
+ describe('with aXe', () => {
21
+ let axe;
22
+ let renderer;
23
+ let sampleResults;
24
+
25
+ beforeAll(async () => {
26
+ const {window} = new jsdom.JSDOM();
27
+ const dom = new DOM(window.document);
28
+ const detailsRenderer = new DetailsRenderer(dom);
29
+ const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
30
+ renderer = new ReportRenderer(dom, categoryRenderer);
31
+ sampleResults = Util.prepareReportResult(sampleResultsOrig);
32
+
33
+ // Needed by axe-core
34
+ // https://github.com/dequelabs/axe-core/blob/581c441c/doc/examples/jsdom/test/a11y.js#L24
35
+ global.window = global.self = window;
36
+ global.Node = global.self.Node;
37
+ global.Element = global.self.Element;
38
+
39
+ // axe-core must be imported after the global polyfills
40
+ axe = (await import('axe-core')).default;
41
+ });
42
+
43
+ afterAll(() => {
44
+ global.window = undefined;
45
+ global.Node = undefined;
46
+ global.Element = undefined;
47
+ });
48
+
49
+ it('renders without axe violations', () => {
50
+ const container = renderer._dom._document.createElement('main');
51
+ const output = renderer.renderReport(sampleResults, container);
52
+
53
+ renderer._dom._document.body.appendChild(container);
54
+
55
+ const config = {
56
+ rules: {
57
+ // Reports may have duplicate ids
58
+ // https://github.com/GoogleChrome/lighthouse/issues/9432
59
+ 'duplicate-id': {enabled: false},
60
+ 'duplicate-id-aria': {enabled: false},
61
+ 'landmark-no-duplicate-contentinfo': {enabled: false},
62
+ // The following rules are disable for axe-core + jsdom compatibility
63
+ // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#to-run-the-example
64
+ 'color-contrast': {enabled: false},
65
+ 'link-in-text-block': {enabled: false},
66
+ // Report has empty links prior to i18n-ing.
67
+ 'link-name': {enabled: false},
68
+ // May not be a real issue. https://github.com/dequelabs/axe-core/issues/2958
69
+ 'nested-interactive': {enabled: false},
70
+ },
71
+ };
72
+
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
82
+ },
83
+ /* timeout= */ 100 * 1000
84
+ );
85
+ });
86
+ });
@@ -233,59 +233,4 @@ describe('ReportRenderer', () => {
233
233
  .querySelectorAll('.lh-audit--notapplicable').length;
234
234
  assert.strictEqual(notApplicableCount, notApplicableElementCount);
235
235
  });
236
-
237
- describe('axe-core', () => {
238
- let axe;
239
-
240
- beforeAll(async () =>{
241
- // Needed by axe-core
242
- // https://github.com/dequelabs/axe-core/blob/581c441c/doc/examples/jsdom/test/a11y.js#L24
243
- global.window = global.self;
244
- global.Node = global.self.Node;
245
- global.Element = global.self.Element;
246
-
247
- // axe-core must be imported after the global polyfills
248
- axe = (await import('axe-core')).default;
249
- });
250
-
251
- afterAll(() => {
252
- global.window = undefined;
253
- global.Node = undefined;
254
- global.Element = undefined;
255
- });
256
-
257
- it('renders without axe violations', () => {
258
- const container = renderer._dom._document.createElement('main');
259
- const output = renderer.renderReport(sampleResults, container);
260
- renderer._dom._document.body.appendChild(container);
261
-
262
- const config = {
263
- rules: {
264
- // Reports may have duplicate ids
265
- // https://github.com/GoogleChrome/lighthouse/issues/9432
266
- 'duplicate-id': {enabled: false},
267
- 'duplicate-id-aria': {enabled: false},
268
- 'landmark-no-duplicate-contentinfo': {enabled: false},
269
- // The following rules are disable for axe-core + jsdom compatibility
270
- // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#to-run-the-example
271
- 'color-contrast': {enabled: false},
272
- 'link-in-text-block': {enabled: false},
273
- // Report has empty links prior to i18n-ing.
274
- 'link-name': {enabled: false},
275
- // May not be a real issue. https://github.com/dequelabs/axe-core/issues/2958
276
- 'nested-interactive': {enabled: false},
277
- },
278
- };
279
-
280
- return new Promise(resolve => {
281
- axe.run(output, config, (error, {violations}) => {
282
- expect(error).toBeNull();
283
- expect(violations).toEqual([]);
284
- resolve();
285
- });
286
- });
287
- // This test takes 40s on fast hardware, and 50-60s on GHA.
288
- // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
289
- }, /* timeout= */ 100 * 1000);
290
- });
291
236
  });
@@ -399,17 +399,77 @@ describe('util helpers', () => {
399
399
  describe('#calculateCategoryFraction', () => {
400
400
  it('returns passed audits and total audits', () => {
401
401
  const category = {
402
+ id: 'performance',
402
403
  auditRefs: [
403
- {weight: 3, result: {score: 1, scoreDisplayMode: 'binary'}},
404
- {weight: 2, result: {score: 1, scoreDisplayMode: 'binary'}},
405
- {weight: 0, result: {score: 1, scoreDisplayMode: 'binary'}},
406
- {weight: 1, result: {score: 0, scoreDisplayMode: 'binary'}},
404
+ {weight: 3, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
405
+ {weight: 2, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
406
+ {weight: 0, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
407
+ {weight: 1, result: {score: 0, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
407
408
  ],
408
409
  };
409
- const {numPassed, numAudits, totalWeight} = Util.calculateCategoryFraction(category);
410
- expect(numPassed).toEqual(3);
411
- expect(numAudits).toEqual(4);
412
- expect(totalWeight).toEqual(6);
410
+ const fraction = Util.calculateCategoryFraction(category);
411
+ expect(fraction).toEqual({
412
+ numPassableAudits: 4,
413
+ numPassed: 3,
414
+ numInformative: 0,
415
+ totalWeight: 6,
416
+ });
417
+ });
418
+
419
+ it('ignores manual audits, N/A audits, and performance audits with no group', () => {
420
+ const category = {
421
+ id: 'performance',
422
+ auditRefs: [
423
+ {weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
424
+ {weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}},
425
+ {weight: 1, result: {score: 0, scoreDisplayMode: 'manual'}, group: 'diagnostics'},
426
+ {weight: 1, result: {score: 0, scoreDisplayMode: 'notApplicable'}, group: 'diagnostics'},
427
+ ],
428
+ };
429
+ const fraction = Util.calculateCategoryFraction(category);
430
+ expect(fraction).toEqual({
431
+ numPassableAudits: 1,
432
+ numPassed: 1,
433
+ numInformative: 0,
434
+ totalWeight: 1,
435
+ });
436
+ });
437
+
438
+ it('does not ignore audits with no group in non-performance category', () => {
439
+ const category = {
440
+ id: 'seo',
441
+ auditRefs: [
442
+ {weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
443
+ {weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}},
444
+ {weight: 1, result: {score: 0, scoreDisplayMode: 'manual'}, group: 'diagnostics'},
445
+ ],
446
+ };
447
+ const fraction = Util.calculateCategoryFraction(category);
448
+ expect(fraction).toEqual({
449
+ numPassableAudits: 2,
450
+ numPassed: 2,
451
+ numInformative: 0,
452
+ totalWeight: 2,
453
+ });
454
+ });
455
+
456
+ it('tracks informative audits separately', () => {
457
+ const category = {
458
+ id: 'performance',
459
+ auditRefs: [
460
+ {weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
461
+ {weight: 1, result: {score: 1, scoreDisplayMode: 'binary'}, group: 'diagnostics'},
462
+ {weight: 0, result: {score: 1, scoreDisplayMode: 'informative'}, group: 'diagnostics'},
463
+ {weight: 1, result: {score: 0, scoreDisplayMode: 'informative'}, group: 'diagnostics'},
464
+ ],
465
+ };
466
+ const fraction = Util.calculateCategoryFraction(category);
467
+ expect(fraction).toEqual({
468
+ numPassableAudits: 2,
469
+ numPassed: 2,
470
+ numInformative: 2,
471
+ totalWeight: 2,
472
+ });
413
473
  });
414
474
  });
415
475
  });
@@ -16,7 +16,6 @@
16
16
  <meta charset="utf-8">
17
17
  <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
18
18
  <title>Faux PageSpeed Report</title>
19
- <style>/*%%LIGHTHOUSE_CSS%%*/</style>
20
19
 
21
20
  <style>
22
21
  /* Inspired by https://codepen.io/markcaron/pen/MvGRYV?editors=1100 but improved. s*/
@@ -10,7 +10,8 @@
10
10
  },
11
11
  "references": [
12
12
  {"path": "../types/lhr/"},
13
- {"path": "./generator/"}
13
+ {"path": "./generator/"},
14
+ {"path": "../shared/"}
14
15
  ],
15
16
  "include": [
16
17
  "**/*.js",
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import AuditDetails from '../../types/lhr/audit-details';
8
- import {FormattedIcu as FormattedIcu_} from '../../types/lhr/i18n';
8
+ import {FormattedIcu as FormattedIcu_, IcuMessage as IcuMessage_} from '../../types/lhr/i18n';
9
9
  import LHResult from '../../types/lhr/lhr';
10
10
  import ReportResult_ from './report-result';
11
11
  import * as Settings from '../../types/lhr/settings';
@@ -17,6 +17,7 @@ declare global {
17
17
  export import Result = LHResult;
18
18
  export import ReportResult = ReportResult_;
19
19
  export import Locale = Settings.Locale;
20
+ export type IcuMessage = IcuMessage_;
20
21
  export type FormattedIcu<T> = FormattedIcu_<T>;
21
22
 
22
23
  module Audit {
package/root.js CHANGED
@@ -5,6 +5,24 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const url = require('url');
11
+
12
+ // import {dirname} from 'path';
13
+ // import {fileURLToPath} from 'url';
14
+
8
15
  module.exports = {
9
16
  LH_ROOT: __dirname,
17
+ /**
18
+ * Return parsed json object.
19
+ * Resolves path relative to importMeta.url (if provided) or LH_ROOT (if not provided).
20
+ * @param {string} filePath Can be an absolute or relative path.
21
+ * @param {ImportMeta=} importMeta
22
+ */
23
+ readJson(filePath, importMeta) {
24
+ const dir = importMeta ? path.dirname(url.fileURLToPath(importMeta.url)) : __dirname;
25
+ filePath = path.resolve(dir, filePath);
26
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
27
+ },
10
28
  };