lighthouse 8.6.0 → 9.0.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 (209) hide show
  1. package/dist/report/bundle.esm.js +5994 -0
  2. package/dist/report/flow.js +30 -13
  3. package/dist/report/standalone.js +41 -35
  4. package/flow-report/.eslintrc.cjs +2 -0
  5. package/flow-report/assets/standalone-flow-template.html +3 -2
  6. package/flow-report/assets/styles.css +108 -31
  7. package/flow-report/src/app.tsx +18 -18
  8. package/flow-report/src/common.tsx +28 -15
  9. package/flow-report/src/header.tsx +12 -13
  10. package/flow-report/src/help-dialog.tsx +3 -4
  11. package/flow-report/src/i18n/i18n.tsx +57 -16
  12. package/flow-report/src/i18n/ui-strings.js +50 -0
  13. package/flow-report/src/icons.tsx +36 -8
  14. package/flow-report/src/sidebar/flow.tsx +3 -3
  15. package/flow-report/src/sidebar/sidebar.tsx +35 -14
  16. package/flow-report/src/summary/category.tsx +97 -24
  17. package/flow-report/src/summary/summary.tsx +21 -14
  18. package/flow-report/src/topbar.tsx +9 -5
  19. package/flow-report/src/util.ts +33 -26
  20. package/flow-report/src/wrappers/category-score.tsx +0 -3
  21. package/flow-report/src/wrappers/markdown.tsx +27 -0
  22. package/flow-report/src/wrappers/report-renderer.tsx +10 -3
  23. package/flow-report/src/wrappers/report.tsx +27 -6
  24. package/flow-report/standalone-flow.tsx +4 -3
  25. package/flow-report/test/common-test.tsx +32 -17
  26. package/flow-report/test/flow-report-pptr-test.ts +46 -0
  27. package/flow-report/test/header-test.tsx +6 -6
  28. package/flow-report/test/setup/env-setup.ts +1 -1
  29. package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
  30. package/flow-report/test/summary/category-test.tsx +120 -24
  31. package/flow-report/test/summary/summary-test.tsx +1 -1
  32. package/flow-report/test/topbar-test.tsx +1 -0
  33. package/flow-report/test/util-test.tsx +32 -11
  34. package/flow-report/test/wrappers/category-score-test.tsx +87 -0
  35. package/flow-report/test/wrappers/markdown-test.tsx +29 -0
  36. package/flow-report/tsconfig.json +1 -0
  37. package/jest.config.js +4 -2
  38. package/lighthouse-cli/.eslintrc.cjs +2 -0
  39. package/lighthouse-cli/cli-flags.js +1 -1
  40. package/lighthouse-cli/run.js +1 -1
  41. package/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js +0 -0
  42. package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +4 -2
  43. package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
  44. package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
  45. package/lighthouse-core/audits/accessibility/axe-audit.js +7 -1
  46. package/lighthouse-core/audits/audit.js +1 -1
  47. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -0
  48. package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
  49. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
  50. package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
  51. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
  52. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
  53. package/lighthouse-core/audits/deprecations.js +33 -11
  54. package/lighthouse-core/audits/dobetterweb/uses-http2.js +27 -11
  55. package/lighthouse-core/audits/errors-in-console.js +0 -2
  56. package/lighthouse-core/audits/image-aspect-ratio.js +5 -5
  57. package/lighthouse-core/audits/image-size-responsive.js +6 -6
  58. package/lighthouse-core/audits/installable-manifest.js +5 -3
  59. package/lighthouse-core/audits/preload-lcp-image.js +7 -5
  60. package/lighthouse-core/audits/script-treemap-data.js +1 -1
  61. package/lighthouse-core/audits/unsized-images.js +2 -3
  62. package/lighthouse-core/computed/image-records.js +4 -3
  63. package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +3 -4
  64. package/lighthouse-core/computed/metrics/tbt-utils.js +57 -0
  65. package/lighthouse-core/computed/metrics/total-blocking-time.js +3 -53
  66. package/lighthouse-core/computed/resource-summary.js +1 -1
  67. package/lighthouse-core/config/config-helpers.js +21 -10
  68. package/lighthouse-core/config/constants.js +3 -4
  69. package/lighthouse-core/config/default-config.js +61 -71
  70. package/lighthouse-core/fraggle-rock/config/config.js +1 -1
  71. package/lighthouse-core/fraggle-rock/config/default-config.js +4 -5
  72. package/lighthouse-core/fraggle-rock/gather/base-gatherer.js +6 -1
  73. package/lighthouse-core/fraggle-rock/gather/session.js +3 -4
  74. package/lighthouse-core/gather/connections/cri.js +1 -1
  75. package/lighthouse-core/gather/driver/storage.js +0 -1
  76. package/lighthouse-core/gather/driver.js +3 -7
  77. package/lighthouse-core/gather/fetcher.js +4 -7
  78. package/lighthouse-core/gather/gather-runner.js +29 -6
  79. package/lighthouse-core/gather/gatherers/accessibility.js +27 -0
  80. package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +1 -0
  81. package/lighthouse-core/gather/gatherers/image-elements.js +0 -3
  82. package/lighthouse-core/gather/gatherers/inspector-issues.js +5 -0
  83. package/lighthouse-core/lib/asset-saver.js +7 -4
  84. package/lighthouse-core/lib/emulation.js +45 -1
  85. package/lighthouse-core/lib/i18n/i18n.js +19 -8
  86. package/lighthouse-core/lib/lh-env.js +2 -0
  87. package/lighthouse-core/lib/proto-preprocessor.js +14 -2
  88. package/lighthouse-core/lib/tappable-rects.js +8 -15
  89. package/lighthouse-core/lib/url-shim.js +1 -1
  90. package/lighthouse-core/runner.js +1 -1
  91. package/lighthouse-core/util-commonjs.js +65 -46
  92. package/package.json +27 -17
  93. package/readme.md +3 -3
  94. package/report/.eslintrc.cjs +2 -0
  95. package/report/README.md +1 -1
  96. package/report/assets/standalone-template.html +2 -3
  97. package/report/assets/styles.css +360 -254
  98. package/report/assets/templates.html +21 -76
  99. package/report/clients/bundle.js +1 -0
  100. package/report/clients/standalone.js +6 -15
  101. package/report/generator/README.md +1 -1
  102. package/report/renderer/api.js +32 -0
  103. package/report/renderer/category-renderer.js +59 -19
  104. package/report/renderer/components.js +59 -110
  105. package/report/renderer/crc-details-renderer.js +15 -12
  106. package/report/renderer/dom.js +16 -1
  107. package/report/renderer/drop-down-menu.js +2 -2
  108. package/report/renderer/element-screenshot-renderer.js +8 -8
  109. package/report/renderer/features-util.js +1 -1
  110. package/report/renderer/open-tab.js +9 -3
  111. package/report/renderer/performance-category-renderer.js +55 -34
  112. package/report/renderer/pwa-category-renderer.js +0 -10
  113. package/report/renderer/report-renderer.js +91 -44
  114. package/report/renderer/report-ui-features.js +36 -33
  115. package/report/renderer/swap-locale-feature.js +3 -3
  116. package/report/renderer/topbar-features.js +53 -48
  117. package/report/renderer/util.js +66 -46
  118. package/report/test/clients/bundle-test.js +70 -0
  119. package/report/test/renderer/category-renderer-test.js +48 -3
  120. package/report/test/renderer/i18n-test.js +0 -7
  121. package/report/test/renderer/performance-category-renderer-test.js +51 -14
  122. package/report/test/renderer/pwa-category-renderer-test.js +6 -6
  123. package/report/test/renderer/report-renderer-axe-test.js +10 -12
  124. package/report/test/renderer/report-renderer-test.js +23 -22
  125. package/report/test/renderer/report-ui-features-test.js +20 -10
  126. package/report/test/renderer/text-encoding-test.js +1 -1
  127. package/report/test/renderer/util-test.js +35 -31
  128. package/report/test-assets/faux-psi-template.html +3 -11
  129. package/report/test-assets/faux-psi.js +26 -22
  130. package/report/types/html-renderer.d.ts +2 -0
  131. package/report/types/report-renderer.d.ts +23 -0
  132. package/shared/localization/format.js +64 -30
  133. package/shared/localization/locales/ar-XB.json +72 -75
  134. package/shared/localization/locales/ar.json +72 -75
  135. package/shared/localization/locales/bg.json +72 -75
  136. package/shared/localization/locales/ca.json +72 -75
  137. package/shared/localization/locales/cs.json +89 -92
  138. package/shared/localization/locales/da.json +72 -75
  139. package/shared/localization/locales/de.json +89 -92
  140. package/shared/localization/locales/el.json +72 -75
  141. package/shared/localization/locales/en-GB.json +72 -75
  142. package/shared/localization/locales/en-US.json +75 -75
  143. package/shared/localization/locales/en-XA.json +72 -75
  144. package/shared/localization/locales/en-XL.json +75 -75
  145. package/shared/localization/locales/es-419.json +90 -93
  146. package/shared/localization/locales/es.json +90 -93
  147. package/shared/localization/locales/fi.json +72 -75
  148. package/shared/localization/locales/fil.json +72 -75
  149. package/shared/localization/locales/fr.json +89 -92
  150. package/shared/localization/locales/he.json +73 -76
  151. package/shared/localization/locales/hi.json +72 -75
  152. package/shared/localization/locales/hr.json +72 -75
  153. package/shared/localization/locales/hu.json +89 -92
  154. package/shared/localization/locales/id.json +89 -92
  155. package/shared/localization/locales/it.json +89 -92
  156. package/shared/localization/locales/ja.json +89 -92
  157. package/shared/localization/locales/ko.json +89 -92
  158. package/shared/localization/locales/lt.json +89 -92
  159. package/shared/localization/locales/lv.json +95 -98
  160. package/shared/localization/locales/nl.json +89 -92
  161. package/shared/localization/locales/no.json +72 -75
  162. package/shared/localization/locales/pl.json +72 -75
  163. package/shared/localization/locales/pt-PT.json +72 -75
  164. package/shared/localization/locales/pt.json +89 -92
  165. package/shared/localization/locales/ro.json +90 -93
  166. package/shared/localization/locales/ru.json +89 -92
  167. package/shared/localization/locales/sk.json +89 -92
  168. package/shared/localization/locales/sl.json +89 -92
  169. package/shared/localization/locales/sr-Latn.json +89 -92
  170. package/shared/localization/locales/sr.json +89 -92
  171. package/shared/localization/locales/sv.json +72 -75
  172. package/shared/localization/locales/ta.json +89 -92
  173. package/shared/localization/locales/te.json +89 -92
  174. package/shared/localization/locales/th.json +89 -92
  175. package/shared/localization/locales/tr.json +89 -92
  176. package/shared/localization/locales/uk.json +92 -95
  177. package/shared/localization/locales/vi.json +89 -92
  178. package/shared/localization/locales/zh-HK.json +89 -92
  179. package/shared/localization/locales/zh-TW.json +89 -92
  180. package/shared/localization/locales/zh.json +89 -92
  181. package/shared/localization/locales.js +6 -0
  182. package/shared/test/localization/format-test.js +45 -9
  183. package/shared/test/localization/swap-locale-test.js +0 -12
  184. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +19 -11
  185. package/third-party/chromium-synchronization/installability-errors-test.js +2 -0
  186. package/third-party/download-content-shell/download-content-shell.js +2 -2
  187. package/third-party/download-content-shell/utils.js +0 -24
  188. package/tsconfig-all.json +2 -2
  189. package/tsconfig.json +0 -1
  190. package/types/artifacts.d.ts +7 -7
  191. package/types/enquirer.d.ts +3 -1
  192. package/types/lhr/audit-details.d.ts +6 -5
  193. package/types/lhr/flow.d.ts +3 -2
  194. package/types/lhr/lhr.d.ts +1 -1
  195. package/types/node.d.ts +5 -5
  196. package/types/rollup-plugin-postprocess.d.ts +10 -0
  197. package/changelog.md +0 -5490
  198. package/lighthouse-core/audits/dobetterweb/appcache-manifest.js +0 -67
  199. package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
  200. package/lighthouse-core/audits/redirects-http.js +0 -59
  201. package/lighthouse-core/gather/gatherers/dobetterweb/appcache.js +0 -34
  202. package/lighthouse-core/gather/gatherers/http-redirect.js +0 -46
  203. package/lighthouse-core/lib/i18n/locales/en-US.ctc.json +0 -6699
  204. package/lighthouse-core/lib/i18n/locales/en-XL.ctc.json +0 -6672
  205. package/lighthouse-core/scripts/package.json +0 -4
  206. package/report/clients/psi.js +0 -158
  207. package/report/test/clients/psi-test.js +0 -143
  208. package/shared/localization/locales/en-US.ctc.json +0 -6877
  209. package/shared/localization/locales/en-XL.ctc.json +0 -6852
@@ -9,8 +9,8 @@ import {useState} from 'preact/hooks';
9
9
 
10
10
  import {HelpDialog} from './help-dialog';
11
11
  import {getFilenamePrefix} from '../../report/generator/file-namer';
12
- import {useUIStrings} from './i18n/i18n';
13
- import {HamburgerIcon} from './icons';
12
+ import {useLocalizedStrings} from './i18n/i18n';
13
+ import {HamburgerIcon, InfoIcon} from './icons';
14
14
  import {useFlowResult} from './util';
15
15
  import {useReportRenderer} from './wrappers/report-renderer';
16
16
 
@@ -79,7 +79,7 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
79
79
  ({onMenuClick}) => {
80
80
  const flowResult = useFlowResult();
81
81
  const {dom} = useReportRenderer();
82
- const strings = useUIStrings();
82
+ const strings = useLocalizedStrings();
83
83
  const [showHelpDialog, setShowHelpDialog] = useState(false);
84
84
 
85
85
  return (
@@ -98,8 +98,12 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
98
98
  <div style={{flexGrow: 1}} />
99
99
  <TopbarButton
100
100
  onClick={() => setShowHelpDialog(previous => !previous)}
101
- label="Button that toggles the help dialog">
102
- {strings.helpLabel}
101
+ label="Button that toggles the help dialog"
102
+ >
103
+ <div className="Topbar__help-label">
104
+ <InfoIcon/>
105
+ {strings.helpLabel}
106
+ </div>
103
107
  </TopbarButton>
104
108
  {showHelpDialog ?
105
109
  <HelpDialog onClose={() => setShowHelpDialog(false)} /> :
@@ -9,14 +9,14 @@ import {useContext, useEffect, useMemo, useState} from 'preact/hooks';
9
9
 
10
10
  import type {UIStringsType} from './i18n/ui-strings';
11
11
 
12
- export const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
12
+ const FlowResultContext = createContext<LH.FlowResult|undefined>(undefined);
13
13
 
14
14
  function getHashParam(param: string): string|null {
15
15
  const params = new URLSearchParams(location.hash.replace('#', '?'));
16
16
  return params.get(param);
17
17
  }
18
18
 
19
- export function classNames(...args: Array<string|undefined|Record<string, boolean>>): string {
19
+ function classNames(...args: Array<string|undefined|Record<string, boolean>>): string {
20
20
  const classes = [];
21
21
  for (const arg of args) {
22
22
  if (!arg) continue;
@@ -35,24 +35,24 @@ export function classNames(...args: Array<string|undefined|Record<string, boolea
35
35
  return classes.join(' ');
36
36
  }
37
37
 
38
- export function getScreenDimensions(reportResult: LH.ReportResult) {
38
+ function getScreenDimensions(reportResult: LH.Result) {
39
39
  const {width, height} = reportResult.configSettings.screenEmulation;
40
40
  return {width, height};
41
41
  }
42
42
 
43
- export function getScreenshot(reportResult: LH.ReportResult) {
43
+ function getFullPageScreenshot(reportResult: LH.Result) {
44
44
  const fullPageScreenshotAudit = reportResult.audits['full-page-screenshot'];
45
45
  const fullPageScreenshot =
46
46
  fullPageScreenshotAudit &&
47
47
  fullPageScreenshotAudit.details &&
48
48
  fullPageScreenshotAudit.details.type === 'full-page-screenshot' &&
49
- fullPageScreenshotAudit.details.screenshot.data;
49
+ fullPageScreenshotAudit.details;
50
50
 
51
51
  return fullPageScreenshot || null;
52
52
  }
53
53
 
54
- export function getFilmstripFrames(
55
- reportResult: LH.ReportResult
54
+ function getFilmstripFrames(
55
+ reportResult: LH.Result
56
56
  ): Array<{data: string}> | undefined {
57
57
  const filmstripAudit = reportResult.audits['screenshot-thumbnails'];
58
58
  if (!filmstripAudit) return undefined;
@@ -65,7 +65,7 @@ export function getFilmstripFrames(
65
65
  return frameItems || undefined;
66
66
  }
67
67
 
68
- export function getModeDescription(mode: LH.Result.GatherMode, strings: UIStringsType) {
68
+ function getModeDescription(mode: LH.Result.GatherMode, strings: UIStringsType) {
69
69
  switch (mode) {
70
70
  case 'navigation': return strings.navigationDescription;
71
71
  case 'timespan': return strings.timespanDescription;
@@ -73,38 +73,33 @@ export function getModeDescription(mode: LH.Result.GatherMode, strings: UIString
73
73
  }
74
74
  }
75
75
 
76
- export function useFlowResult(): LH.FlowResult {
76
+ function useFlowResult(): LH.FlowResult {
77
77
  const flowResult = useContext(FlowResultContext);
78
78
  if (!flowResult) throw Error('useFlowResult must be called in the FlowResultContext');
79
79
  return flowResult;
80
80
  }
81
81
 
82
- export function useLocale(): LH.Locale {
83
- const flowResult = useFlowResult();
84
- return flowResult.steps[0].lhr.configSettings.locale;
85
- }
86
-
87
- export function useHashParam(param: string) {
88
- const [paramValue, setParamValue] = useState(getHashParam(param));
82
+ function useHashParams(...params: string[]) {
83
+ const [paramValues, setParamValues] = useState(params.map(getHashParam));
89
84
 
90
85
  // Use two-way-binding on the URL hash.
91
- // Triggers a re-render if the param changes.
86
+ // Triggers a re-render if any param changes.
92
87
  useEffect(() => {
93
88
  function hashListener() {
94
- const newIndexString = getHashParam(param);
95
- if (newIndexString === paramValue) return;
96
- setParamValue(newIndexString);
89
+ const newParamValues = params.map(getHashParam);
90
+ if (newParamValues.every((value, i) => value === paramValues[i])) return;
91
+ setParamValues(newParamValues);
97
92
  }
98
93
  window.addEventListener('hashchange', hashListener);
99
94
  return () => window.removeEventListener('hashchange', hashListener);
100
- }, [paramValue]);
95
+ }, [paramValues]);
101
96
 
102
- return paramValue;
97
+ return paramValues;
103
98
  }
104
99
 
105
- export function useCurrentLhr(): LH.FlowResult.LhrRef|null {
100
+ function useHashState(): LH.FlowResult.HashState|null {
106
101
  const flowResult = useFlowResult();
107
- const indexString = useHashParam('index');
102
+ const [indexString, anchor] = useHashParams('index', 'anchor');
108
103
 
109
104
  // Memoize result so a new object is not created on every call.
110
105
  return useMemo(() => {
@@ -122,6 +117,18 @@ export function useCurrentLhr(): LH.FlowResult.LhrRef|null {
122
117
  return null;
123
118
  }
124
119
 
125
- return {value: step.lhr, index};
126
- }, [indexString, flowResult]);
120
+ return {currentLhr: step.lhr, index, anchor};
121
+ }, [indexString, flowResult, anchor]);
127
122
  }
123
+
124
+ export {
125
+ FlowResultContext,
126
+ classNames,
127
+ getScreenDimensions,
128
+ getFullPageScreenshot,
129
+ getFilmstripFrames,
130
+ getModeDescription,
131
+ useFlowResult,
132
+ useHashParams,
133
+ useHashState,
134
+ };
@@ -23,9 +23,6 @@ export const CategoryScore: FunctionComponent<{
23
23
  // Category label is displayed in the navigation header.
24
24
  const label = el.querySelector('.lh-gauge__label,.lh-fraction__label');
25
25
  if (label) label.remove();
26
- // Background is displayed in individual reports, but not on the summary page.
27
- const bg = el.querySelector('.lh-fraction__background');
28
- if (bg) bg.remove();
29
26
 
30
27
  if (ref.current) ref.current.append(el);
31
28
  return () => {
@@ -0,0 +1,27 @@
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
+
7
+ import {FunctionComponent} from 'preact';
8
+ import {useLayoutEffect, useRef} from 'preact/hooks';
9
+
10
+ import {useReportRenderer} from '../wrappers/report-renderer';
11
+
12
+ export const Markdown: FunctionComponent<{text: string}> = ({text}) => {
13
+ const {dom} = useReportRenderer();
14
+ const ref = useRef<HTMLSpanElement>(null);
15
+
16
+ useLayoutEffect(() => {
17
+ if (ref.current) {
18
+ const md = dom.convertMarkdownCodeSnippets(text);
19
+ ref.current.appendChild(md);
20
+ }
21
+ return () => {
22
+ if (ref.current) ref.current.innerHTML = '';
23
+ };
24
+ }, [text]);
25
+
26
+ return <span ref={ref}/>;
27
+ };
@@ -19,16 +19,17 @@ interface ReportRendererGlobals {
19
19
  reportRenderer: ReportRenderer,
20
20
  }
21
21
 
22
- export const ReportRendererContext = createContext<ReportRendererGlobals|undefined>(undefined);
22
+ const ReportRendererContext = createContext<ReportRendererGlobals|undefined>(undefined);
23
23
 
24
- export function useReportRenderer() {
24
+ function useReportRenderer() {
25
25
  const globals = useContext(ReportRendererContext);
26
26
  if (!globals) throw Error('Globals not defined');
27
27
  return globals;
28
28
  }
29
29
 
30
- export const ReportRendererProvider: FunctionComponent = ({children}) => {
30
+ const ReportRendererProvider: FunctionComponent = ({children}) => {
31
31
  const globals = useMemo(() => {
32
+ // @ts-expect-error Still using legacy
32
33
  const dom = new DOM(document);
33
34
  const detailsRenderer = new DetailsRenderer(dom);
34
35
  const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);
@@ -44,3 +45,9 @@ export const ReportRendererProvider: FunctionComponent = ({children}) => {
44
45
  <ReportRendererContext.Provider value={globals}>{children}</ReportRendererContext.Provider>
45
46
  );
46
47
  };
48
+
49
+ export {
50
+ ReportRendererContext,
51
+ ReportRendererProvider,
52
+ useReportRenderer,
53
+ };
@@ -7,6 +7,8 @@
7
7
  import {FunctionComponent} from 'preact';
8
8
  import {useLayoutEffect, useRef} from 'preact/hooks';
9
9
 
10
+ import {ElementScreenshotRenderer} from '../../../report/renderer/element-screenshot-renderer';
11
+ import {getFullPageScreenshot} from '../util';
10
12
  import {useReportRenderer} from './report-renderer';
11
13
 
12
14
  /**
@@ -14,7 +16,7 @@ import {useReportRenderer} from './report-renderer';
14
16
  * This function converts any anchor links under the provided element to a flow report link.
15
17
  * e.g. <a href="#link"> -> <a href="#index=0&anchor=link">
16
18
  */
17
- export function convertChildAnchors(element: HTMLElement, index: number) {
19
+ function convertChildAnchors(element: HTMLElement, index: number) {
18
20
  const links = element.querySelectorAll('a[href]') as NodeListOf<HTMLAnchorElement>;
19
21
  for (const link of links) {
20
22
  // Check if the link destination is in the report.
@@ -28,19 +30,33 @@ export function convertChildAnchors(element: HTMLElement, index: number) {
28
30
 
29
31
  const nodeId = link.hash.substr(1);
30
32
  link.hash = `#index=${index}&anchor=${nodeId}`;
33
+ link.onclick = e => {
34
+ e.preventDefault();
35
+ const el = document.getElementById(nodeId);
36
+ if (el) el.scrollIntoView();
37
+ };
31
38
  }
32
39
  }
33
40
 
34
- export const Report: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
35
- ({currentLhr}) => {
41
+ const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
42
+ ({hashState}) => {
36
43
  const {dom, reportRenderer} = useReportRenderer();
37
44
  const ref = useRef<HTMLDivElement>(null);
38
45
 
39
46
  useLayoutEffect(() => {
40
47
  if (ref.current) {
41
48
  dom.clearComponentCache();
42
- reportRenderer.renderReport(currentLhr.value, ref.current);
43
- convertChildAnchors(ref.current, currentLhr.index);
49
+ reportRenderer.renderReport(hashState.currentLhr, ref.current);
50
+ convertChildAnchors(ref.current, hashState.index);
51
+ const fullPageScreenshot = getFullPageScreenshot(hashState.currentLhr);
52
+ if (fullPageScreenshot) {
53
+ ElementScreenshotRenderer.installOverlayFeature({
54
+ dom,
55
+ rootEl: ref.current,
56
+ overlayContainerEl: ref.current,
57
+ fullPageScreenshot,
58
+ });
59
+ }
44
60
  const topbar = ref.current.querySelector('.lh-topbar');
45
61
  if (topbar) topbar.remove();
46
62
  }
@@ -48,9 +64,14 @@ export const Report: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
48
64
  return () => {
49
65
  if (ref.current) ref.current.textContent = '';
50
66
  };
51
- }, [reportRenderer, currentLhr]);
67
+ }, [reportRenderer, hashState]);
52
68
 
53
69
  return (
54
70
  <div ref={ref} className="lh-root" data-testid="Report"/>
55
71
  );
56
72
  };
73
+
74
+ export {
75
+ convertChildAnchors,
76
+ Report,
77
+ };
@@ -15,9 +15,10 @@ import {App} from './src/app';
15
15
 
16
16
  // Used by standalone-flow.html
17
17
  function __initLighthouseFlowReport__() {
18
- const root = document.body.querySelector('main');
19
- if (!root) throw Error('Root element not found');
20
- render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, root);
18
+ // TODO(adamraine): add lh-vars, etc classes programmatically instead of in the HTML template
19
+ const container = document.body.querySelector('main');
20
+ if (!container) throw Error('Container element not found');
21
+ render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, container);
21
22
  }
22
23
 
23
24
  window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
@@ -9,7 +9,7 @@ import {act, render} from '@testing-library/preact';
9
9
 
10
10
  import {FlowStepThumbnail} from '../src/common';
11
11
 
12
- let lhr: LH.ReportResult;
12
+ let lhr: LH.Result;
13
13
 
14
14
  jest.useFakeTimers();
15
15
 
@@ -24,16 +24,25 @@ describe('FlowStepThumbnail', () => {
24
24
  'full-page-screenshot': {
25
25
  details: {
26
26
  type: 'full-page-screenshot',
27
- screenshot: {data: 'baef01', width: 400, height: 600},
27
+ screenshot: {data: 'FPS', width: 400, height: 600},
28
28
  nodes: {},
29
29
  },
30
30
  },
31
+ 'screenshot-thumbnails': {
32
+ details: {
33
+ type: 'filmstrip',
34
+ items: [
35
+ {data: 'frame1'},
36
+ {data: 'frame2'},
37
+ ],
38
+ },
39
+ },
31
40
  },
32
41
  } as any;
33
42
  });
34
43
 
35
44
  it('renders a thumbnail', () => {
36
- const root = render(<FlowStepThumbnail reportResult={lhr} width={200} height={200} />);
45
+ const root = render(<FlowStepThumbnail lhr={lhr} width={200} height={200} />);
37
46
 
38
47
  const thumbnail = root.getByAltText(/Screenshot/);
39
48
  expect(thumbnail.style.width).toEqual('200px');
@@ -41,14 +50,14 @@ describe('FlowStepThumbnail', () => {
41
50
  });
42
51
 
43
52
  it('renders nothing without dimensions', () => {
44
- const root = render(<FlowStepThumbnail reportResult={lhr} />);
53
+ const root = render(<FlowStepThumbnail lhr={lhr} />);
45
54
 
46
55
  expect(() => root.getByAltText(/Screenshot/)).toThrow();
47
56
  expect(global.console.warn).toHaveBeenCalled();
48
57
  });
49
58
 
50
59
  it('interpolates height', () => {
51
- const root = render(<FlowStepThumbnail reportResult={lhr} width={200} />);
60
+ const root = render(<FlowStepThumbnail lhr={lhr} width={200} />);
52
61
 
53
62
  const thumbnail = root.getByAltText(/Screenshot/);
54
63
  expect(thumbnail.style.width).toEqual('200px');
@@ -56,25 +65,31 @@ describe('FlowStepThumbnail', () => {
56
65
  });
57
66
 
58
67
  it('interpolates width', () => {
59
- const root = render(<FlowStepThumbnail reportResult={lhr} height={150} />);
68
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
60
69
 
61
70
  const thumbnail = root.getByAltText(/Screenshot/);
62
71
  expect(thumbnail.style.width).toEqual('100px');
63
72
  expect(thumbnail.style.height).toEqual('150px');
64
73
  });
65
74
 
75
+ it('uses last filmstrip thumbnail', () => {
76
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
77
+
78
+ const thumbnail = root.getByAltText(/Screenshot/) as HTMLImageElement;
79
+ expect(thumbnail.src).toContain('frame2');
80
+ });
81
+
82
+ it('uses full page screenshot if filmstrip unavailable', () => {
83
+ delete lhr.audits['screenshot-thumbnails'];
84
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
85
+
86
+ const thumbnail = root.getByAltText(/Screenshot/) as HTMLImageElement;
87
+ expect(thumbnail.src).toContain('FPS');
88
+ });
89
+
66
90
  it('renders animated thumbnail for timespan', async () => {
67
91
  lhr.gatherMode = 'timespan';
68
- lhr.audits['screenshot-thumbnails'] = {
69
- details: {
70
- type: 'filmstrip',
71
- items: [
72
- {data: 'frame1'},
73
- {data: 'frame2'},
74
- ],
75
- },
76
- } as any;
77
- const root = render(<FlowStepThumbnail reportResult={lhr} height={150} />);
92
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
78
93
 
79
94
  const thumbnail = root.getByAltText(/Animated/) as HTMLImageElement;
80
95
  expect(thumbnail.style.width).toEqual('100px');
@@ -82,7 +97,7 @@ describe('FlowStepThumbnail', () => {
82
97
 
83
98
  expect(thumbnail.src).toContain('frame1');
84
99
  await act(() => {
85
- jest.advanceTimersByTime(251);
100
+ jest.advanceTimersByTime(501);
86
101
  });
87
102
  expect(thumbnail.src).toContain('frame2');
88
103
  });
@@ -0,0 +1,46 @@
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
+
7
+ import {jest} from '@jest/globals';
8
+ import puppeteer, {Browser, Page} from 'puppeteer';
9
+
10
+ import ReportGenerator from '../../report/generator/report-generator.js';
11
+ import {flowResult} from './sample-flow';
12
+
13
+ jest.setTimeout(35_000);
14
+
15
+ describe('Lighthouse Flow Report', () => {
16
+ console.log('\n✨ Be sure to have recently run this: yarn build-report');
17
+
18
+ let browser: Browser;
19
+ let page: Page;
20
+ const pageErrors: Error[] = [];
21
+
22
+ beforeAll(async () => {
23
+ browser = await puppeteer.launch({
24
+ headless: true,
25
+ });
26
+ page = await browser.newPage();
27
+ page.on('pageerror', pageError => pageErrors.push(pageError));
28
+ });
29
+
30
+ afterAll(async () => {
31
+ if (pageErrors.length > 0) console.error(pageErrors);
32
+
33
+ await browser.close();
34
+ });
35
+
36
+ describe('Renders the flow report', () => {
37
+ beforeAll(async () => {
38
+ const html = ReportGenerator.generateFlowReportHtml(flowResult);
39
+ await page.setContent(html);
40
+ });
41
+
42
+ it('should load with no errors', async () => {
43
+ expect(pageErrors).toHaveLength(0);
44
+ });
45
+ });
46
+ });
@@ -25,8 +25,8 @@ beforeEach(() => {
25
25
  });
26
26
 
27
27
  it('renders all sections for a middle step', () => {
28
- const currentLhr = {index: 1} as any;
29
- const root = render(<Header currentLhr={currentLhr}/>, {wrapper});
28
+ const hashState = {index: 1} as any;
29
+ const root = render(<Header hashState={hashState}/>, {wrapper});
30
30
 
31
31
  expect(root.baseElement.querySelector('.Header__prev-thumbnail')).toBeTruthy();
32
32
  expect(root.baseElement.querySelector('.Header__prev-title')).toBeTruthy();
@@ -35,8 +35,8 @@ it('renders all sections for a middle step', () => {
35
35
  });
36
36
 
37
37
  it('renders only next section for first step', () => {
38
- const currentLhr = {index: 0} as any;
39
- const root = render(<Header currentLhr={currentLhr}/>, {wrapper});
38
+ const hashState = {index: 0} as any;
39
+ const root = render(<Header hashState={hashState}/>, {wrapper});
40
40
 
41
41
  expect(root.baseElement.querySelector('.Header__prev-thumbnail')).toBeFalsy();
42
42
  expect(root.baseElement.querySelector('.Header__prev-title')).toBeFalsy();
@@ -45,8 +45,8 @@ it('renders only next section for first step', () => {
45
45
  });
46
46
 
47
47
  it('renders only previous section for last step', () => {
48
- const currentLhr = {index: 3} as any;
49
- const root = render(<Header currentLhr={currentLhr}/>, {wrapper});
48
+ const hashState = {index: 3} as any;
49
+ const root = render(<Header hashState={hashState}/>, {wrapper});
50
50
 
51
51
  expect(root.baseElement.querySelector('.Header__prev-thumbnail')).toBeTruthy();
52
52
  expect(root.baseElement.querySelector('.Header__prev-title')).toBeTruthy();
@@ -10,7 +10,7 @@ import {JSDOM} from 'jsdom';
10
10
  /**
11
11
  * The jest environment "jsdom" does not work when preact is combined with the report renderer.
12
12
  */
13
- export function setupJsDom() {
13
+ function setupJsDom() {
14
14
  const {window} = new JSDOM(undefined, {
15
15
  url: 'file:///Users/example/report.html/',
16
16
  });
@@ -8,7 +8,7 @@ import {render} from '@testing-library/preact';
8
8
  import {FunctionComponent} from 'preact';
9
9
 
10
10
  import {I18nProvider} from '../../src/i18n/i18n';
11
- import {SidebarHeader, SidebarSummary} from '../../src/sidebar/sidebar';
11
+ import {SidebarHeader, SidebarRuntimeSettings, SidebarSummary} from '../../src/sidebar/sidebar';
12
12
  import {FlowResultContext} from '../../src/util';
13
13
  import {flowResult} from '../sample-flow';
14
14
 
@@ -45,3 +45,45 @@ describe('SidebarSummary', () => {
45
45
  expect(link.classList).toContain('Sidebar--current');
46
46
  });
47
47
  });
48
+
49
+ describe('SidebarRuntimeSettings', () => {
50
+ it('displays default runtime settings', async () => {
51
+ const settings = {
52
+ formFactor: 'mobile',
53
+ throttlingMethod: 'simulate',
54
+ throttling: {
55
+ cpuSlowdownMultiplier: 4,
56
+ requestLatencyMs: 150 * 3.75,
57
+ downloadThroughputKbps: 1.6 * 1024 * 0.9,
58
+ uploadThroughputKbps: 750 * 0.9,
59
+ throughputKbps: 1.6 * 1024,
60
+ rttMs: 150,
61
+ },
62
+ } as any;
63
+ const root = render(<SidebarRuntimeSettings settings={settings}/>, {wrapper});
64
+
65
+ expect(root.getByText('Emulated Moto G4')).toBeTruthy();
66
+ expect(root.getByText('Slow 4G throttling')).toBeTruthy();
67
+ expect(root.getByText('4x slowdown'));
68
+ });
69
+
70
+ it('displays custom runtime settings', async () => {
71
+ const settings = {
72
+ formFactor: 'desktop',
73
+ throttlingMethod: 'devtools',
74
+ throttling: {
75
+ cpuSlowdownMultiplier: 1,
76
+ requestLatencyMs: 1,
77
+ downloadThroughputKbps: 1,
78
+ uploadThroughputKbps: 1,
79
+ throughputKbps: 1,
80
+ rttMs: 1,
81
+ },
82
+ } as any;
83
+ const root = render(<SidebarRuntimeSettings settings={settings}/>, {wrapper});
84
+
85
+ expect(root.getByText('Emulated Desktop')).toBeTruthy();
86
+ expect(root.getByText('Custom throttling')).toBeTruthy();
87
+ expect(root.getByText('1x slowdown'));
88
+ });
89
+ });