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.
- package/dist/report/bundle.esm.js +5994 -0
- package/dist/report/flow.js +30 -13
- package/dist/report/standalone.js +41 -35
- package/flow-report/.eslintrc.cjs +2 -0
- package/flow-report/assets/standalone-flow-template.html +3 -2
- package/flow-report/assets/styles.css +108 -31
- package/flow-report/src/app.tsx +18 -18
- 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 +9 -5
- package/flow-report/src/util.ts +33 -26
- package/flow-report/src/wrappers/category-score.tsx +0 -3
- package/flow-report/src/wrappers/markdown.tsx +27 -0
- package/flow-report/src/wrappers/report-renderer.tsx +10 -3
- package/flow-report/src/wrappers/report.tsx +27 -6
- package/flow-report/standalone-flow.tsx +4 -3
- 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 +1 -1
- package/flow-report/test/sidebar/sidebar-test.tsx +43 -1
- package/flow-report/test/summary/category-test.tsx +120 -24
- package/flow-report/test/summary/summary-test.tsx +1 -1
- package/flow-report/test/topbar-test.tsx +1 -0
- package/flow-report/test/util-test.tsx +32 -11
- package/flow-report/test/wrappers/category-score-test.tsx +87 -0
- package/flow-report/test/wrappers/markdown-test.tsx +29 -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/frontends/smokehouse-bin.js +0 -0
- 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 +5 -3
- 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/proto-preprocessor.js +14 -2
- 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 +360 -254
- 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 +32 -0
- package/report/renderer/category-renderer.js +59 -19
- 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 +91 -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 +48 -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 +10 -12
- package/report/test/renderer/report-renderer-test.js +23 -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 +23 -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 +75 -75
- package/shared/localization/locales/en-XA.json +72 -75
- package/shared/localization/locales/en-XL.json +75 -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 +2 -0
- package/third-party/download-content-shell/download-content-shell.js +2 -2
- package/third-party/download-content-shell/utils.js +0 -24
- 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/changelog.md +0 -5490
- 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/lighthouse-core/scripts/package.json +0 -4
- 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,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 {
|
|
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 =
|
|
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
|
-
|
|
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)} /> :
|
package/flow-report/src/util.ts
CHANGED
|
@@ -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
|
-
|
|
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,18 @@ 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]);
|
|
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
|
-
|
|
22
|
+
const ReportRendererContext = createContext<ReportRendererGlobals|undefined>(undefined);
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
({
|
|
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
|
|
43
|
-
convertChildAnchors(ref.current,
|
|
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,
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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.
|
|
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();
|
|
@@ -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
|
-
|
|
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
|
+
});
|