lighthouse 8.6.0 → 9.0.0-dev.20211118
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.
- package/changelog.md +121 -0
- package/dist/report/bundle.esm.js +6009 -0
- package/dist/report/flow.js +98 -25
- package/dist/report/standalone.js +41 -35
- package/flow-report/.eslintrc.cjs +2 -0
- package/flow-report/assets/standalone-flow-template.html +3 -4
- package/flow-report/assets/styles.css +108 -31
- package/flow-report/src/app.tsx +25 -28
- package/flow-report/src/common.tsx +28 -15
- package/flow-report/src/header.tsx +12 -13
- package/flow-report/src/help-dialog.tsx +3 -4
- package/flow-report/src/i18n/i18n.tsx +57 -16
- package/flow-report/src/i18n/ui-strings.js +50 -0
- package/flow-report/src/icons.tsx +36 -8
- package/flow-report/src/sidebar/flow.tsx +3 -3
- package/flow-report/src/sidebar/sidebar.tsx +35 -14
- package/flow-report/src/summary/category.tsx +97 -24
- package/flow-report/src/summary/summary.tsx +21 -14
- package/flow-report/src/topbar.tsx +13 -12
- package/flow-report/src/util.ts +61 -27
- package/flow-report/src/wrappers/category-score.tsx +9 -27
- package/flow-report/src/wrappers/markdown.tsx +18 -0
- package/flow-report/src/wrappers/report.tsx +26 -37
- package/flow-report/standalone-flow.tsx +5 -4
- package/flow-report/test/common-test.tsx +32 -17
- package/flow-report/test/flow-report-pptr-test.ts +46 -0
- package/flow-report/test/header-test.tsx +6 -6
- package/flow-report/test/setup/env-setup.ts +11 -5
- package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
- package/flow-report/test/summary/category-test.tsx +114 -21
- package/flow-report/test/summary/summary-test.tsx +4 -7
- package/flow-report/test/topbar-test.tsx +15 -14
- package/flow-report/test/util-test.tsx +69 -11
- package/flow-report/test/wrappers/category-score-test.tsx +84 -0
- package/flow-report/test/wrappers/markdown-test.tsx +17 -0
- package/flow-report/tsconfig.json +1 -0
- package/jest.config.js +4 -2
- package/lighthouse-cli/.eslintrc.cjs +2 -0
- package/lighthouse-cli/cli-flags.js +1 -1
- package/lighthouse-cli/run.js +1 -1
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/bundle.js +4 -2
- package/lighthouse-cli/test/smokehouse/lighthouse-runners/cli.js +1 -1
- package/lighthouse-cli/test/smokehouse/smokehouse.js +1 -1
- package/lighthouse-core/audits/accessibility/axe-audit.js +7 -1
- package/lighthouse-core/audits/audit.js +1 -1
- package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +2 -0
- package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
- package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
- package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
- package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
- package/lighthouse-core/audits/deprecations.js +33 -11
- package/lighthouse-core/audits/dobetterweb/uses-http2.js +27 -11
- package/lighthouse-core/audits/errors-in-console.js +0 -2
- package/lighthouse-core/audits/image-aspect-ratio.js +5 -5
- package/lighthouse-core/audits/image-size-responsive.js +6 -6
- package/lighthouse-core/audits/installable-manifest.js +16 -7
- package/lighthouse-core/audits/preload-lcp-image.js +7 -5
- package/lighthouse-core/audits/script-treemap-data.js +1 -1
- package/lighthouse-core/audits/unsized-images.js +2 -3
- package/lighthouse-core/computed/image-records.js +4 -3
- package/lighthouse-core/computed/metrics/lantern-total-blocking-time.js +3 -4
- package/lighthouse-core/computed/metrics/tbt-utils.js +57 -0
- package/lighthouse-core/computed/metrics/total-blocking-time.js +3 -53
- package/lighthouse-core/computed/resource-summary.js +1 -1
- package/lighthouse-core/config/config-helpers.js +21 -10
- package/lighthouse-core/config/constants.js +3 -4
- package/lighthouse-core/config/default-config.js +61 -71
- package/lighthouse-core/fraggle-rock/config/config.js +1 -1
- package/lighthouse-core/fraggle-rock/config/default-config.js +4 -5
- package/lighthouse-core/fraggle-rock/gather/base-gatherer.js +6 -1
- package/lighthouse-core/fraggle-rock/gather/session.js +3 -4
- package/lighthouse-core/gather/connections/cri.js +1 -1
- package/lighthouse-core/gather/driver/storage.js +0 -1
- package/lighthouse-core/gather/driver.js +3 -7
- package/lighthouse-core/gather/fetcher.js +4 -7
- package/lighthouse-core/gather/gather-runner.js +29 -6
- package/lighthouse-core/gather/gatherers/accessibility.js +27 -0
- package/lighthouse-core/gather/gatherers/dobetterweb/response-compression.js +1 -0
- package/lighthouse-core/gather/gatherers/image-elements.js +0 -3
- package/lighthouse-core/gather/gatherers/inspector-issues.js +5 -0
- package/lighthouse-core/lib/asset-saver.js +7 -4
- package/lighthouse-core/lib/emulation.js +45 -1
- package/lighthouse-core/lib/i18n/i18n.js +19 -8
- package/lighthouse-core/lib/lh-env.js +2 -0
- package/lighthouse-core/lib/page-functions.js +2 -1
- package/lighthouse-core/lib/proto-preprocessor.js +14 -2
- package/lighthouse-core/lib/statistics.js +26 -39
- package/lighthouse-core/lib/tappable-rects.js +8 -15
- package/lighthouse-core/lib/url-shim.js +1 -1
- package/lighthouse-core/runner.js +1 -1
- package/lighthouse-core/util-commonjs.js +65 -46
- package/package.json +27 -17
- package/readme.md +3 -3
- package/report/.eslintrc.cjs +2 -0
- package/report/README.md +1 -1
- package/report/assets/standalone-template.html +2 -3
- package/report/assets/styles.css +362 -256
- package/report/assets/templates.html +21 -76
- package/report/clients/bundle.js +1 -0
- package/report/clients/standalone.js +6 -15
- package/report/generator/README.md +1 -1
- package/report/renderer/api.js +66 -0
- package/report/renderer/category-renderer.js +76 -22
- package/report/renderer/components.js +59 -110
- package/report/renderer/crc-details-renderer.js +15 -12
- package/report/renderer/dom.js +16 -1
- package/report/renderer/drop-down-menu.js +2 -2
- package/report/renderer/element-screenshot-renderer.js +8 -8
- package/report/renderer/features-util.js +1 -1
- package/report/renderer/open-tab.js +9 -3
- package/report/renderer/performance-category-renderer.js +55 -34
- package/report/renderer/pwa-category-renderer.js +0 -10
- package/report/renderer/report-renderer.js +92 -44
- package/report/renderer/report-ui-features.js +36 -33
- package/report/renderer/swap-locale-feature.js +3 -3
- package/report/renderer/topbar-features.js +53 -48
- package/report/renderer/util.js +66 -46
- package/report/test/clients/bundle-test.js +70 -0
- package/report/test/renderer/category-renderer-test.js +76 -3
- package/report/test/renderer/i18n-test.js +0 -7
- package/report/test/renderer/performance-category-renderer-test.js +51 -14
- package/report/test/renderer/pwa-category-renderer-test.js +6 -6
- package/report/test/renderer/report-renderer-axe-test.js +11 -13
- package/report/test/renderer/report-renderer-test.js +52 -22
- package/report/test/renderer/report-ui-features-test.js +20 -10
- package/report/test/renderer/text-encoding-test.js +1 -1
- package/report/test/renderer/util-test.js +35 -31
- package/report/test-assets/faux-psi-template.html +3 -11
- package/report/test-assets/faux-psi.js +26 -22
- package/report/types/html-renderer.d.ts +2 -0
- package/report/types/report-renderer.d.ts +28 -0
- package/shared/localization/format.js +64 -30
- package/shared/localization/locales/ar-XB.json +72 -75
- package/shared/localization/locales/ar.json +72 -75
- package/shared/localization/locales/bg.json +72 -75
- package/shared/localization/locales/ca.json +72 -75
- package/shared/localization/locales/cs.json +89 -92
- package/shared/localization/locales/da.json +72 -75
- package/shared/localization/locales/de.json +89 -92
- package/shared/localization/locales/el.json +72 -75
- package/shared/localization/locales/en-GB.json +72 -75
- package/shared/localization/locales/en-US.json +78 -75
- package/shared/localization/locales/en-XA.json +72 -75
- package/shared/localization/locales/en-XL.json +78 -75
- package/shared/localization/locales/es-419.json +90 -93
- package/shared/localization/locales/es.json +90 -93
- package/shared/localization/locales/fi.json +72 -75
- package/shared/localization/locales/fil.json +72 -75
- package/shared/localization/locales/fr.json +89 -92
- package/shared/localization/locales/he.json +73 -76
- package/shared/localization/locales/hi.json +72 -75
- package/shared/localization/locales/hr.json +72 -75
- package/shared/localization/locales/hu.json +89 -92
- package/shared/localization/locales/id.json +89 -92
- package/shared/localization/locales/it.json +89 -92
- package/shared/localization/locales/ja.json +89 -92
- package/shared/localization/locales/ko.json +89 -92
- package/shared/localization/locales/lt.json +89 -92
- package/shared/localization/locales/lv.json +95 -98
- package/shared/localization/locales/nl.json +89 -92
- package/shared/localization/locales/no.json +72 -75
- package/shared/localization/locales/pl.json +72 -75
- package/shared/localization/locales/pt-PT.json +72 -75
- package/shared/localization/locales/pt.json +89 -92
- package/shared/localization/locales/ro.json +90 -93
- package/shared/localization/locales/ru.json +89 -92
- package/shared/localization/locales/sk.json +89 -92
- package/shared/localization/locales/sl.json +89 -92
- package/shared/localization/locales/sr-Latn.json +89 -92
- package/shared/localization/locales/sr.json +89 -92
- package/shared/localization/locales/sv.json +72 -75
- package/shared/localization/locales/ta.json +89 -92
- package/shared/localization/locales/te.json +89 -92
- package/shared/localization/locales/th.json +89 -92
- package/shared/localization/locales/tr.json +89 -92
- package/shared/localization/locales/uk.json +92 -95
- package/shared/localization/locales/vi.json +89 -92
- package/shared/localization/locales/zh-HK.json +89 -92
- package/shared/localization/locales/zh-TW.json +89 -92
- package/shared/localization/locales/zh.json +89 -92
- package/shared/localization/locales.js +6 -0
- package/shared/test/localization/format-test.js +45 -9
- package/shared/test/localization/swap-locale-test.js +0 -12
- package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +19 -11
- package/third-party/chromium-synchronization/installability-errors-test.js +3 -2
- package/third-party/download-content-shell/download-content-shell.js +2 -2
- package/third-party/download-content-shell/utils.js +0 -24
- package/third-party/snyk/snapshot.json +4 -1
- package/tsconfig-all.json +2 -2
- package/tsconfig.json +0 -1
- package/types/artifacts.d.ts +7 -7
- package/types/enquirer.d.ts +3 -1
- package/types/lhr/audit-details.d.ts +6 -5
- package/types/lhr/flow.d.ts +3 -2
- package/types/lhr/lhr.d.ts +1 -1
- package/types/node.d.ts +5 -5
- package/types/rollup-plugin-postprocess.d.ts +10 -0
- package/flow-report/src/wrappers/report-renderer.tsx +0 -46
- package/lighthouse-core/audits/dobetterweb/appcache-manifest.js +0 -67
- package/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js +0 -96
- package/lighthouse-core/audits/redirects-http.js +0 -59
- package/lighthouse-core/gather/gatherers/dobetterweb/appcache.js +0 -34
- package/lighthouse-core/gather/gatherers/http-redirect.js +0 -46
- package/lighthouse-core/lib/i18n/locales/en-US.ctc.json +0 -6699
- package/lighthouse-core/lib/i18n/locales/en-XL.ctc.json +0 -6672
- package/report/clients/psi.js +0 -158
- package/report/test/clients/psi-test.js +0 -143
- package/shared/localization/locales/en-US.ctc.json +0 -6877
- package/shared/localization/locales/en-XL.ctc.json +0 -6852
|
@@ -9,14 +9,12 @@ 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 {
|
|
13
|
-
import {HamburgerIcon} from './icons';
|
|
12
|
+
import {useLocalizedStrings} from './i18n/i18n';
|
|
13
|
+
import {HamburgerIcon, InfoIcon} from './icons';
|
|
14
14
|
import {useFlowResult} from './util';
|
|
15
|
-
import {
|
|
15
|
+
import {saveFile} from '../../report/renderer/api';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
|
|
17
|
+
function saveHtml(flowResult: LH.FlowResult) {
|
|
20
18
|
const htmlStr = document.documentElement.outerHTML;
|
|
21
19
|
const blob = new Blob([htmlStr], {type: 'text/html'});
|
|
22
20
|
|
|
@@ -24,7 +22,7 @@ function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
|
|
|
24
22
|
const name = flowResult.name.replace(/\s/g, '-');
|
|
25
23
|
const filename = getFilenamePrefix(name, lhr.fetchTime);
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
saveFile(blob, filename);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
/* eslint-disable max-len */
|
|
@@ -78,8 +76,7 @@ const TopbarButton: FunctionComponent<{
|
|
|
78
76
|
export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLButtonElement>}> =
|
|
79
77
|
({onMenuClick}) => {
|
|
80
78
|
const flowResult = useFlowResult();
|
|
81
|
-
const
|
|
82
|
-
const strings = useUIStrings();
|
|
79
|
+
const strings = useLocalizedStrings();
|
|
83
80
|
const [showHelpDialog, setShowHelpDialog] = useState(false);
|
|
84
81
|
|
|
85
82
|
return (
|
|
@@ -92,14 +89,18 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
|
|
|
92
89
|
</div>
|
|
93
90
|
<div className="Topbar__title">{strings.title}</div>
|
|
94
91
|
<TopbarButton
|
|
95
|
-
onClick={() => saveHtml(flowResult
|
|
92
|
+
onClick={() => saveHtml(flowResult)}
|
|
96
93
|
label="Button that saves the report as HTML"
|
|
97
94
|
>{strings.save}</TopbarButton>
|
|
98
95
|
<div style={{flexGrow: 1}} />
|
|
99
96
|
<TopbarButton
|
|
100
97
|
onClick={() => setShowHelpDialog(previous => !previous)}
|
|
101
|
-
label="Button that toggles the help dialog"
|
|
102
|
-
|
|
98
|
+
label="Button that toggles the help dialog"
|
|
99
|
+
>
|
|
100
|
+
<div className="Topbar__help-label">
|
|
101
|
+
<InfoIcon/>
|
|
102
|
+
{strings.helpLabel}
|
|
103
|
+
</div>
|
|
103
104
|
</TopbarButton>
|
|
104
105
|
{showHelpDialog ?
|
|
105
106
|
<HelpDialog onClose={() => setShowHelpDialog(false)} /> :
|
package/flow-report/src/util.ts
CHANGED
|
@@ -5,18 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {createContext} from 'preact';
|
|
8
|
-
import {useContext, useEffect, useMemo, useState} from 'preact/hooks';
|
|
8
|
+
import {useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
|
|
9
9
|
|
|
10
10
|
import type {UIStringsType} from './i18n/ui-strings';
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
function getScreenDimensions(reportResult: LH.Result) {
|
|
39
39
|
const {width, height} = reportResult.configSettings.screenEmulation;
|
|
40
40
|
return {width, height};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
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
|
|
49
|
+
fullPageScreenshotAudit.details;
|
|
50
50
|
|
|
51
51
|
return fullPageScreenshot || null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
reportResult: LH.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
const
|
|
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
|
|
86
|
+
// Triggers a re-render if any param changes.
|
|
92
87
|
useEffect(() => {
|
|
93
88
|
function hashListener() {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
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
|
-
}, [
|
|
95
|
+
}, [paramValues]);
|
|
101
96
|
|
|
102
|
-
return
|
|
97
|
+
return paramValues;
|
|
103
98
|
}
|
|
104
99
|
|
|
105
|
-
|
|
100
|
+
function useHashState(): LH.FlowResult.HashState|null {
|
|
106
101
|
const flowResult = useFlowResult();
|
|
107
|
-
const indexString =
|
|
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,45 @@ export function useCurrentLhr(): LH.FlowResult.LhrRef|null {
|
|
|
122
117
|
return null;
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
return {
|
|
126
|
-
}, [indexString, flowResult]);
|
|
120
|
+
return {currentLhr: step.lhr, index, anchor};
|
|
121
|
+
}, [indexString, flowResult, anchor]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Creates a DOM subtree from non-preact code (e.g. LH report renderer).
|
|
126
|
+
* @param renderCallback Callback that renders a DOM subtree.
|
|
127
|
+
* @param inputs Changes to these values will trigger a re-render of the DOM subtree.
|
|
128
|
+
* @return Reference to the element that will contain the DOM subtree.
|
|
129
|
+
*/
|
|
130
|
+
function useExternalRenderer<T extends Element>(
|
|
131
|
+
renderCallback: () => Node,
|
|
132
|
+
inputs?: ReadonlyArray<unknown>
|
|
133
|
+
) {
|
|
134
|
+
const ref = useRef<T>(null);
|
|
135
|
+
|
|
136
|
+
useLayoutEffect(() => {
|
|
137
|
+
if (!ref.current) return;
|
|
138
|
+
|
|
139
|
+
const root = renderCallback();
|
|
140
|
+
ref.current.appendChild(root);
|
|
141
|
+
|
|
142
|
+
return () => {
|
|
143
|
+
if (ref.current?.contains(root)) ref.current.removeChild(root);
|
|
144
|
+
};
|
|
145
|
+
}, inputs);
|
|
146
|
+
|
|
147
|
+
return ref;
|
|
127
148
|
}
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
FlowResultContext,
|
|
152
|
+
classNames,
|
|
153
|
+
getScreenDimensions,
|
|
154
|
+
getFullPageScreenshot,
|
|
155
|
+
getFilmstripFrames,
|
|
156
|
+
getModeDescription,
|
|
157
|
+
useFlowResult,
|
|
158
|
+
useHashParams,
|
|
159
|
+
useHashState,
|
|
160
|
+
useExternalRenderer,
|
|
161
|
+
};
|
|
@@ -5,40 +5,22 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useEffect, useLayoutEffect, useRef} from 'preact/hooks';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import {renderCategoryScore} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
11
|
|
|
12
12
|
export const CategoryScore: FunctionComponent<{
|
|
13
13
|
category: LH.ReportResult.Category,
|
|
14
14
|
href: string,
|
|
15
15
|
gatherMode: LH.Result.GatherMode,
|
|
16
16
|
}> = ({category, href, gatherMode}) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const label = el.querySelector('.lh-gauge__label,.lh-fraction__label');
|
|
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
|
-
|
|
30
|
-
if (ref.current) ref.current.append(el);
|
|
31
|
-
return () => {
|
|
32
|
-
if (ref.current && ref.current.contains(el)) {
|
|
33
|
-
ref.current.removeChild(el);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}, [categoryRenderer, category]);
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const anchor = ref.current && ref.current.querySelector('a') as HTMLAnchorElement;
|
|
40
|
-
if (anchor) anchor.href = href;
|
|
41
|
-
}, [href]);
|
|
17
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
18
|
+
return renderCategoryScore(category, {
|
|
19
|
+
gatherMode,
|
|
20
|
+
omitLabel: true,
|
|
21
|
+
onPageAnchorRendered: link => link.href = href,
|
|
22
|
+
});
|
|
23
|
+
}, [category, href]);
|
|
42
24
|
|
|
43
25
|
return (
|
|
44
26
|
<div ref={ref} data-testid="CategoryScore"/>
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
|
|
9
|
+
import {convertMarkdownCodeSnippets} from '../../../report/renderer/api';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
|
+
|
|
12
|
+
export const Markdown: FunctionComponent<{text: string}> = ({text}) => {
|
|
13
|
+
const ref = useExternalRenderer<HTMLSpanElement>(() => {
|
|
14
|
+
return convertMarkdownCodeSnippets(text);
|
|
15
|
+
}, [text]);
|
|
16
|
+
|
|
17
|
+
return <span ref={ref}/>;
|
|
18
|
+
};
|
|
@@ -5,52 +5,41 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {FunctionComponent} from 'preact';
|
|
8
|
-
import {useLayoutEffect, useRef} from 'preact/hooks';
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import {renderReport} from '../../../report/renderer/api.js';
|
|
10
|
+
import {useExternalRenderer} from '../util';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The default behavior of anchor links is not compatible with the flow report's hash navigation.
|
|
14
|
-
* This function converts
|
|
14
|
+
* This function converts a category score anchor link to a flow report link.
|
|
15
15
|
* e.g. <a href="#link"> -> <a href="#index=0&anchor=link">
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
link.hash = `#index=${index}&anchor=${nodeId}`;
|
|
31
|
-
}
|
|
17
|
+
function convertAnchor(link: HTMLAnchorElement, index: number) {
|
|
18
|
+
// Clear existing event listeners by cloning node.
|
|
19
|
+
const newLink = link.cloneNode(true) as HTMLAnchorElement;
|
|
20
|
+
if (!newLink.hash) return newLink;
|
|
21
|
+
|
|
22
|
+
const nodeId = link.hash.substr(1);
|
|
23
|
+
newLink.hash = `#index=${index}&anchor=${nodeId}`;
|
|
24
|
+
newLink.onclick = e => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
const el = document.getElementById(nodeId);
|
|
27
|
+
if (el) el.scrollIntoView();
|
|
28
|
+
};
|
|
29
|
+
link.replaceWith(newLink);
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
export const Report: FunctionComponent<{
|
|
35
|
-
({
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
convertChildAnchors(ref.current, currentLhr.index);
|
|
44
|
-
const topbar = ref.current.querySelector('.lh-topbar');
|
|
45
|
-
if (topbar) topbar.remove();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return () => {
|
|
49
|
-
if (ref.current) ref.current.textContent = '';
|
|
50
|
-
};
|
|
51
|
-
}, [reportRenderer, currentLhr]);
|
|
32
|
+
export const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
|
|
33
|
+
({hashState}) => {
|
|
34
|
+
const ref = useExternalRenderer<HTMLDivElement>(() => {
|
|
35
|
+
return renderReport(hashState.currentLhr, {
|
|
36
|
+
disableAutoDarkModeAndFireworks: true,
|
|
37
|
+
omitTopbar: true,
|
|
38
|
+
onPageAnchorRendered: link => convertAnchor(link, hashState.index),
|
|
39
|
+
});
|
|
40
|
+
}, [hashState]);
|
|
52
41
|
|
|
53
42
|
return (
|
|
54
|
-
<div ref={ref}
|
|
43
|
+
<div ref={ref} data-testid="Report"/>
|
|
55
44
|
);
|
|
56
45
|
};
|
|
@@ -13,11 +13,12 @@ import {render} from 'preact';
|
|
|
13
13
|
|
|
14
14
|
import {App} from './src/app';
|
|
15
15
|
|
|
16
|
-
// Used by standalone-flow.html
|
|
17
16
|
function __initLighthouseFlowReport__() {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
-
|
|
17
|
+
const container = document.body.querySelector('main');
|
|
18
|
+
if (!container) throw Error('Container element not found');
|
|
19
|
+
container.classList.add('flow-vars', 'lh-root', 'lh-vars');
|
|
20
|
+
render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, container);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
|
|
24
|
+
window.__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.
|
|
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: '
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
29
|
-
const root = render(<Header
|
|
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
|
|
39
|
-
const root = render(<Header
|
|
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
|
|
49
|
-
const root = render(<Header
|
|
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();
|
|
@@ -9,18 +9,24 @@ import {JSDOM} from 'jsdom';
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The jest environment "jsdom" does not work when preact is combined with the report renderer.
|
|
12
|
+
* This sets up our own environment with JSDOM globals.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
+
beforeEach(() => {
|
|
14
15
|
const {window} = new JSDOM(undefined, {
|
|
15
16
|
url: 'file:///Users/example/report.html/',
|
|
16
17
|
});
|
|
17
18
|
global.window = window as any;
|
|
18
19
|
global.document = window.document;
|
|
19
20
|
global.location = window.location;
|
|
21
|
+
global.self = global.window;
|
|
22
|
+
|
|
23
|
+
// Use JSDOM types as necessary.
|
|
20
24
|
global.Blob = window.Blob;
|
|
25
|
+
global.HTMLInputElement = window.HTMLInputElement;
|
|
21
26
|
|
|
22
|
-
//
|
|
27
|
+
// Functions not implemented in JSDOM.
|
|
23
28
|
window.Element.prototype.scrollIntoView = jest.fn();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
global.self.matchMedia = jest.fn<any, any>(() => ({
|
|
30
|
+
addListener: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
@@ -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
|
+
});
|