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
|
@@ -21,12 +21,21 @@
|
|
|
21
21
|
* critical request chains network tree.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import {Util} from './util.js';
|
|
25
|
+
|
|
24
26
|
/** @typedef {import('./dom.js').DOM} DOM */
|
|
25
27
|
/** @typedef {import('./details-renderer.js').DetailsRenderer} DetailsRenderer */
|
|
28
|
+
/**
|
|
29
|
+
* @typedef CRCSegment
|
|
30
|
+
* @property {LH.Audit.Details.SimpleCriticalRequestNode[string]} node
|
|
31
|
+
* @property {boolean} isLastChild
|
|
32
|
+
* @property {boolean} hasChildren
|
|
33
|
+
* @property {number} startTime
|
|
34
|
+
* @property {number} transferSize
|
|
35
|
+
* @property {boolean[]} treeMarkers
|
|
36
|
+
*/
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export class CriticalRequestChainRenderer {
|
|
38
|
+
class CriticalRequestChainRenderer {
|
|
30
39
|
/**
|
|
31
40
|
* Create render context for critical-request-chain tree display.
|
|
32
41
|
* @param {LH.Audit.Details.SimpleCriticalRequestNode} tree
|
|
@@ -191,12 +200,6 @@ export class CriticalRequestChainRenderer {
|
|
|
191
200
|
// Alias b/c the name is really long.
|
|
192
201
|
const CRCRenderer = CriticalRequestChainRenderer;
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
hasChildren: boolean,
|
|
198
|
-
startTime: number,
|
|
199
|
-
transferSize: number,
|
|
200
|
-
treeMarkers: Array<boolean>
|
|
201
|
-
}} CRCSegment
|
|
202
|
-
*/
|
|
203
|
+
export {
|
|
204
|
+
CriticalRequestChainRenderer,
|
|
205
|
+
};
|
package/report/renderer/dom.js
CHANGED
|
@@ -27,14 +27,18 @@ import {createComponent} from './components.js';
|
|
|
27
27
|
export class DOM {
|
|
28
28
|
/**
|
|
29
29
|
* @param {Document} document
|
|
30
|
+
* @param {HTMLElement} rootEl
|
|
30
31
|
*/
|
|
31
|
-
constructor(document) {
|
|
32
|
+
constructor(document, rootEl) {
|
|
32
33
|
/** @type {Document} */
|
|
33
34
|
this._document = document;
|
|
34
35
|
/** @type {string} */
|
|
35
36
|
this._lighthouseChannel = 'unknown';
|
|
36
37
|
/** @type {Map<string, DocumentFragment>} */
|
|
37
38
|
this._componentCache = new Map();
|
|
39
|
+
/** @type {HTMLElement} */
|
|
40
|
+
// For legacy Report API users, this'll be undefined, but set in renderReport
|
|
41
|
+
this.rootEl = rootEl;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -76,6 +80,15 @@ export class DOM {
|
|
|
76
80
|
return this._document.createDocumentFragment();
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} data
|
|
85
|
+
* @return {!Node}
|
|
86
|
+
*/
|
|
87
|
+
createTextNode(data) {
|
|
88
|
+
return this._document.createTextNode(data);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
79
92
|
/**
|
|
80
93
|
* @template {string} T
|
|
81
94
|
* @param {Element} parentElem
|
|
@@ -216,6 +229,8 @@ export class DOM {
|
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
/**
|
|
232
|
+
* ONLY use if `dom.rootEl` isn't sufficient for your needs. `dom.rootEl` is preferred
|
|
233
|
+
* for all scoping, because a document can have multiple reports within it.
|
|
219
234
|
* @return {Document}
|
|
220
235
|
*/
|
|
221
236
|
document() {
|
|
@@ -36,11 +36,11 @@ export class DropDownMenu {
|
|
|
36
36
|
* @param {function(MouseEvent): any} menuClickHandler
|
|
37
37
|
*/
|
|
38
38
|
setup(menuClickHandler) {
|
|
39
|
-
this._toggleEl = this._dom.find('button.lh-tools__button', this._dom.
|
|
39
|
+
this._toggleEl = this._dom.find('.lh-topbar button.lh-tools__button', this._dom.rootEl);
|
|
40
40
|
this._toggleEl.addEventListener('click', this.onToggleClick);
|
|
41
41
|
this._toggleEl.addEventListener('keydown', this.onToggleKeydown);
|
|
42
42
|
|
|
43
|
-
this._menuEl = this._dom.find('div.lh-tools__dropdown', this._dom.
|
|
43
|
+
this._menuEl = this._dom.find('.lh-topbar div.lh-tools__dropdown', this._dom.rootEl);
|
|
44
44
|
this._menuEl.addEventListener('keydown', this.onMenuKeydown);
|
|
45
45
|
this._menuEl.addEventListener('click', menuClickHandler);
|
|
46
46
|
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
/**
|
|
19
19
|
* @typedef InstallOverlayFeatureParams
|
|
20
20
|
* @property {DOM} dom
|
|
21
|
-
* @property {Element}
|
|
21
|
+
* @property {Element} rootEl
|
|
22
22
|
* @property {Element} overlayContainerEl
|
|
23
23
|
* @property {LH.Audit.Details.FullPageScreenshot} fullPageScreenshot
|
|
24
24
|
*/
|
|
@@ -51,7 +51,7 @@ function clamp(value, min, max) {
|
|
|
51
51
|
/**
|
|
52
52
|
* @param {Rect} rect
|
|
53
53
|
*/
|
|
54
|
-
function
|
|
54
|
+
function getElementRectCenterPoint(rect) {
|
|
55
55
|
return {
|
|
56
56
|
x: rect.left + rect.width / 2,
|
|
57
57
|
y: rect.top + rect.height / 2,
|
|
@@ -68,7 +68,7 @@ export class ElementScreenshotRenderer {
|
|
|
68
68
|
* @param {Size} screenshotSize
|
|
69
69
|
*/
|
|
70
70
|
static getScreenshotPositions(elementRectSC, elementPreviewSizeSC, screenshotSize) {
|
|
71
|
-
const elementRectCenter =
|
|
71
|
+
const elementRectCenter = getElementRectCenterPoint(elementRectSC);
|
|
72
72
|
|
|
73
73
|
// Try to center clipped region.
|
|
74
74
|
const screenshotLeftVisibleEdge = clamp(
|
|
@@ -135,7 +135,7 @@ export class ElementScreenshotRenderer {
|
|
|
135
135
|
* @param {LH.Audit.Details.FullPageScreenshot['screenshot']} screenshot
|
|
136
136
|
*/
|
|
137
137
|
static installFullPageScreenshot(el, screenshot) {
|
|
138
|
-
el.style.setProperty('--element-screenshot-url', `url(${screenshot.data})`);
|
|
138
|
+
el.style.setProperty('--element-screenshot-url', `url('${screenshot.data}')`);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
@@ -143,14 +143,14 @@ export class ElementScreenshotRenderer {
|
|
|
143
143
|
* @param {InstallOverlayFeatureParams} opts
|
|
144
144
|
*/
|
|
145
145
|
static installOverlayFeature(opts) {
|
|
146
|
-
const {dom,
|
|
146
|
+
const {dom, rootEl, overlayContainerEl, fullPageScreenshot} = opts;
|
|
147
147
|
const screenshotOverlayClass = 'lh-screenshot-overlay--enabled';
|
|
148
148
|
// Don't install the feature more than once.
|
|
149
|
-
if (
|
|
150
|
-
|
|
149
|
+
if (rootEl.classList.contains(screenshotOverlayClass)) return;
|
|
150
|
+
rootEl.classList.add(screenshotOverlayClass);
|
|
151
151
|
|
|
152
152
|
// Add a single listener to the provided element to handle all clicks within (event delegation).
|
|
153
|
-
|
|
153
|
+
rootEl.addEventListener('click', e => {
|
|
154
154
|
const target = /** @type {?HTMLElement} */ (e.target);
|
|
155
155
|
if (!target) return;
|
|
156
156
|
// Only activate the overlay for clicks on the screenshot *preview* of an element, not the full-size too.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @param {boolean} [force]
|
|
15
15
|
*/
|
|
16
16
|
export function toggleDarkTheme(dom, force) {
|
|
17
|
-
const el = dom.
|
|
17
|
+
const el = dom.rootEl;
|
|
18
18
|
// This seems unnecessary, but in DevTools, passing "undefined" as the second
|
|
19
19
|
// parameter acts like passing "false".
|
|
20
20
|
// https://github.com/ChromeDevTools/devtools-frontend/blob/dd6a6d4153647c2a4203c327c595692c5e0a4256/front_end/dom_extension/DOMExtension.js#L809-L819
|
|
@@ -90,7 +90,7 @@ async function openTabWithUrlData(data, url_, windowName) {
|
|
|
90
90
|
* @param {LH.Result} lhr
|
|
91
91
|
* @protected
|
|
92
92
|
*/
|
|
93
|
-
|
|
93
|
+
async function openViewer(lhr) {
|
|
94
94
|
const windowName = 'viewer-' + computeWindowNameSuffix(lhr);
|
|
95
95
|
const url = getAppsOrigin() + '/viewer/';
|
|
96
96
|
await openTabWithUrlData({lhr}, url, windowName);
|
|
@@ -101,7 +101,7 @@ export async function openViewer(lhr) {
|
|
|
101
101
|
* @param {LH.Result} lhr
|
|
102
102
|
* @protected
|
|
103
103
|
*/
|
|
104
|
-
|
|
104
|
+
async function openViewerAndSendData(lhr) {
|
|
105
105
|
const windowName = 'viewer-' + computeWindowNameSuffix(lhr);
|
|
106
106
|
const url = getAppsOrigin() + '/viewer/';
|
|
107
107
|
openTabAndSendData({lhr}, url, windowName);
|
|
@@ -111,7 +111,7 @@ export async function openViewerAndSendData(lhr) {
|
|
|
111
111
|
* Opens a new tab to the treemap app and sends the JSON results using URL.fragment
|
|
112
112
|
* @param {LH.Result} json
|
|
113
113
|
*/
|
|
114
|
-
|
|
114
|
+
function openTreemap(json) {
|
|
115
115
|
const treemapData = json.audits['script-treemap-data'].details;
|
|
116
116
|
if (!treemapData) {
|
|
117
117
|
throw new Error('no script treemap data found');
|
|
@@ -135,3 +135,9 @@ export function openTreemap(json) {
|
|
|
135
135
|
|
|
136
136
|
openTabWithUrlData(treemapOptions, url, windowName);
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
openViewer,
|
|
141
|
+
openViewerAndSendData,
|
|
142
|
+
openTreemap,
|
|
143
|
+
};
|
|
@@ -152,42 +152,62 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
152
152
|
return url.href;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* For performance, audits with no group should be a diagnostic or opportunity.
|
|
157
|
+
* The audit details type will determine which of the two groups an audit is in.
|
|
158
|
+
*
|
|
159
|
+
* @param {LH.ReportResult.AuditRef} audit
|
|
160
|
+
* @return {'load-opportunity'|'diagnostic'|null}
|
|
161
|
+
*/
|
|
162
|
+
_classifyPerformanceAudit(audit) {
|
|
163
|
+
if (audit.group) return null;
|
|
164
|
+
if (audit.result.details && audit.result.details.type === 'opportunity') {
|
|
165
|
+
return 'load-opportunity';
|
|
166
|
+
}
|
|
167
|
+
return 'diagnostic';
|
|
168
|
+
}
|
|
169
|
+
|
|
155
170
|
/**
|
|
156
171
|
* @param {LH.ReportResult.Category} category
|
|
157
172
|
* @param {Object<string, LH.Result.ReportGroup>} groups
|
|
158
|
-
* @param {{gatherMode: LH.Result.GatherMode
|
|
173
|
+
* @param {{gatherMode: LH.Result.GatherMode}=} options
|
|
159
174
|
* @return {Element}
|
|
160
175
|
* @override
|
|
161
176
|
*/
|
|
162
177
|
render(category, groups, options) {
|
|
163
178
|
const strings = Util.i18n.strings;
|
|
164
179
|
const element = this.dom.createElement('div', 'lh-category');
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
gaugeEl.appendChild(this.renderCategoryScore(category, groups, options));
|
|
168
|
-
element.appendChild(gaugeEl);
|
|
169
|
-
} else {
|
|
170
|
-
element.id = category.id;
|
|
171
|
-
element.appendChild(this.renderCategoryHeader(category, groups, options));
|
|
172
|
-
}
|
|
180
|
+
element.id = category.id;
|
|
181
|
+
element.appendChild(this.renderCategoryHeader(category, groups, options));
|
|
173
182
|
|
|
174
183
|
// Metrics.
|
|
175
184
|
const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics');
|
|
176
185
|
if (metricAudits.length) {
|
|
177
|
-
const
|
|
186
|
+
const [metricsGroupEl, metricsFooterEl] = this.renderAuditGroup(groups.metrics);
|
|
178
187
|
|
|
179
188
|
// Metric descriptions toggle.
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
const checkboxEl = this.dom.createElement('input', 'lh-metrics-toggle__input');
|
|
190
|
+
const checkboxId = `lh-metrics-toggle${Util.getUniqueSuffix()}`;
|
|
191
|
+
checkboxEl.setAttribute('aria-label', 'Toggle the display of metric descriptions');
|
|
192
|
+
checkboxEl.type = 'checkbox';
|
|
193
|
+
checkboxEl.id = checkboxId;
|
|
194
|
+
metricsGroupEl.prepend(checkboxEl);
|
|
195
|
+
const metricHeaderEl = this.dom.find('.lh-audit-group__header', metricsGroupEl);
|
|
196
|
+
const labelEl = this.dom.createChildOf(metricHeaderEl, 'label', 'lh-metrics-toggle__label');
|
|
197
|
+
labelEl.htmlFor = checkboxId;
|
|
198
|
+
const showEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--show');
|
|
199
|
+
const hideEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--hide');
|
|
200
|
+
showEl.textContent = Util.i18n.strings.expandView;
|
|
201
|
+
hideEl.textContent = Util.i18n.strings.collapseView;
|
|
202
|
+
|
|
203
|
+
const metricsBoxesEl = this.dom.createElement('div', 'lh-metrics-container');
|
|
204
|
+
metricsGroupEl.insertBefore(metricsBoxesEl, metricsFooterEl);
|
|
186
205
|
metricAudits.forEach(item => {
|
|
187
206
|
metricsBoxesEl.appendChild(this._renderMetric(item));
|
|
188
207
|
});
|
|
189
208
|
|
|
190
|
-
const
|
|
209
|
+
const descriptionEl = this.dom.find('.lh-category-header__description', element);
|
|
210
|
+
const estValuesEl = this.dom.createChildOf(descriptionEl, 'div', 'lh-metrics__disclaimer');
|
|
191
211
|
const disclaimerEl = this.dom.convertMarkdownLinkSnippets(strings.varianceDisclaimer);
|
|
192
212
|
estValuesEl.appendChild(disclaimerEl);
|
|
193
213
|
|
|
@@ -198,8 +218,8 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
198
218
|
this.dom.safelySetHref(calculatorLink, this._getScoringCalculatorHref(category.auditRefs));
|
|
199
219
|
|
|
200
220
|
|
|
201
|
-
|
|
202
|
-
element.appendChild(
|
|
221
|
+
metricsGroupEl.classList.add('lh-audit-group--metrics');
|
|
222
|
+
element.appendChild(metricsGroupEl);
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
// Filmstrip
|
|
@@ -214,10 +234,10 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
214
234
|
|
|
215
235
|
// Opportunities
|
|
216
236
|
const opportunityAudits = category.auditRefs
|
|
217
|
-
.filter(audit => audit
|
|
237
|
+
.filter(audit => this._classifyPerformanceAudit(audit) === 'load-opportunity')
|
|
238
|
+
.filter(audit => !Util.showAsPassed(audit.result))
|
|
218
239
|
.sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA));
|
|
219
240
|
|
|
220
|
-
|
|
221
241
|
const filterableMetrics = metricAudits.filter(a => !!a.relevantAudits);
|
|
222
242
|
// TODO: only add if there are opportunities & diagnostics rendered.
|
|
223
243
|
if (filterableMetrics.length) {
|
|
@@ -230,7 +250,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
230
250
|
const wastedMsValues = opportunityAudits.map(audit => this._getWastedMs(audit));
|
|
231
251
|
const maxWaste = Math.max(...wastedMsValues);
|
|
232
252
|
const scale = Math.max(Math.ceil(maxWaste / 1000) * 1000, minimumScale);
|
|
233
|
-
const groupEl = this.renderAuditGroup(groups['load-opportunities']);
|
|
253
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups['load-opportunities']);
|
|
234
254
|
const tmpl = this.dom.createComponent('opportunityHeader');
|
|
235
255
|
|
|
236
256
|
this.dom.find('.lh-load-opportunity__col--one', tmpl).textContent =
|
|
@@ -239,15 +259,17 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
239
259
|
strings.opportunitySavingsColumnLabel;
|
|
240
260
|
|
|
241
261
|
const headerEl = this.dom.find('.lh-load-opportunity__header', tmpl);
|
|
242
|
-
groupEl.
|
|
243
|
-
opportunityAudits.forEach(item =>
|
|
262
|
+
groupEl.insertBefore(headerEl, footerEl);
|
|
263
|
+
opportunityAudits.forEach(item =>
|
|
264
|
+
groupEl.insertBefore(this._renderOpportunity(item, scale), footerEl));
|
|
244
265
|
groupEl.classList.add('lh-audit-group--load-opportunities');
|
|
245
266
|
element.appendChild(groupEl);
|
|
246
267
|
}
|
|
247
268
|
|
|
248
269
|
// Diagnostics
|
|
249
270
|
const diagnosticAudits = category.auditRefs
|
|
250
|
-
.filter(audit => audit
|
|
271
|
+
.filter(audit => this._classifyPerformanceAudit(audit) === 'diagnostic')
|
|
272
|
+
.filter(audit => !Util.showAsPassed(audit.result))
|
|
251
273
|
.sort((a, b) => {
|
|
252
274
|
const scoreA = a.result.scoreDisplayMode === 'informative' ? 100 : Number(a.result.score);
|
|
253
275
|
const scoreB = b.result.scoreDisplayMode === 'informative' ? 100 : Number(b.result.score);
|
|
@@ -255,16 +277,15 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
255
277
|
});
|
|
256
278
|
|
|
257
279
|
if (diagnosticAudits.length) {
|
|
258
|
-
const groupEl = this.renderAuditGroup(groups['diagnostics']);
|
|
259
|
-
diagnosticAudits.forEach(item => groupEl.
|
|
280
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']);
|
|
281
|
+
diagnosticAudits.forEach(item => groupEl.insertBefore(this.renderAudit(item), footerEl));
|
|
260
282
|
groupEl.classList.add('lh-audit-group--diagnostics');
|
|
261
283
|
element.appendChild(groupEl);
|
|
262
284
|
}
|
|
263
285
|
|
|
264
286
|
// Passed audits
|
|
265
287
|
const passedAudits = category.auditRefs
|
|
266
|
-
.filter(audit => (audit
|
|
267
|
-
Util.showAsPassed(audit.result));
|
|
288
|
+
.filter(audit => this._classifyPerformanceAudit(audit) && Util.showAsPassed(audit.result));
|
|
268
289
|
|
|
269
290
|
if (!passedAudits.length) return element;
|
|
270
291
|
|
|
@@ -284,16 +305,16 @@ export class PerformanceCategoryRenderer extends CategoryRenderer {
|
|
|
284
305
|
const table = this.detailsRenderer.render(audit.result.details);
|
|
285
306
|
if (table) {
|
|
286
307
|
table.id = id;
|
|
287
|
-
table.classList.add('lh-audit');
|
|
308
|
+
table.classList.add('lh-details', 'lh-details--budget', 'lh-audit');
|
|
288
309
|
budgetTableEls.push(table);
|
|
289
310
|
}
|
|
290
311
|
}
|
|
291
312
|
});
|
|
292
313
|
if (budgetTableEls.length > 0) {
|
|
293
|
-
const
|
|
294
|
-
budgetTableEls.forEach(table =>
|
|
295
|
-
|
|
296
|
-
element.appendChild(
|
|
314
|
+
const [groupEl, footerEl] = this.renderAuditGroup(groups.budgets);
|
|
315
|
+
budgetTableEls.forEach(table => groupEl.insertBefore(table, footerEl));
|
|
316
|
+
groupEl.classList.add('lh-audit-group--budgets');
|
|
317
|
+
element.appendChild(groupEl);
|
|
297
318
|
}
|
|
298
319
|
|
|
299
320
|
return element;
|
|
@@ -47,16 +47,6 @@ export class PwaCategoryRenderer extends CategoryRenderer {
|
|
|
47
47
|
return categoryElem;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
* Alias for backcompat.
|
|
52
|
-
* @param {LH.ReportResult.Category} category
|
|
53
|
-
* @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
|
|
54
|
-
* @return {DocumentFragment}
|
|
55
|
-
*/
|
|
56
|
-
renderScoreGauge(category, groupDefinitions) {
|
|
57
|
-
return this.renderCategoryScore(category, groupDefinitions);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
50
|
/**
|
|
61
51
|
* @param {LH.ReportResult.Category} category
|
|
62
52
|
* @param {Record<string, LH.Result.ReportGroup>} groupDefinitions
|
|
@@ -36,22 +36,43 @@ export class ReportRenderer {
|
|
|
36
36
|
constructor(dom) {
|
|
37
37
|
/** @type {DOM} */
|
|
38
38
|
this._dom = dom;
|
|
39
|
+
/** @type {LH.Renderer.Options} */
|
|
40
|
+
this._opts = {};
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* @param {LH.Result} lhr
|
|
43
|
-
* @param {
|
|
45
|
+
* @param {HTMLElement?} rootEl Report root element containing the report
|
|
46
|
+
* @param {LH.Renderer.Options=} opts
|
|
44
47
|
* @return {!Element}
|
|
45
48
|
*/
|
|
46
|
-
renderReport(lhr,
|
|
49
|
+
renderReport(lhr, rootEl, opts) {
|
|
50
|
+
// Allow legacy report rendering API
|
|
51
|
+
if (!this._dom.rootEl && rootEl) {
|
|
52
|
+
console.warn('Please adopt the new report API in renderer/api.js.');
|
|
53
|
+
const closestRoot = rootEl.closest('.lh-root');
|
|
54
|
+
if (closestRoot) {
|
|
55
|
+
this._dom.rootEl = /** @type {HTMLElement} */ (closestRoot);
|
|
56
|
+
} else {
|
|
57
|
+
rootEl.classList.add('lh-root', 'lh-vars');
|
|
58
|
+
this._dom.rootEl = rootEl;
|
|
59
|
+
}
|
|
60
|
+
} else if (this._dom.rootEl && rootEl) {
|
|
61
|
+
// Handle legacy flow-report case
|
|
62
|
+
this._dom.rootEl = rootEl;
|
|
63
|
+
}
|
|
64
|
+
if (opts) {
|
|
65
|
+
this._opts = opts;
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
this._dom.setLighthouseChannel(lhr.configSettings.channel || 'unknown');
|
|
48
69
|
|
|
49
70
|
const report = Util.prepareReportResult(lhr);
|
|
50
71
|
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
this._dom.rootEl.textContent = ''; // Remove previous report.
|
|
73
|
+
this._dom.rootEl.appendChild(this._renderReport(report));
|
|
53
74
|
|
|
54
|
-
return
|
|
75
|
+
return this._dom.rootEl;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
/**
|
|
@@ -85,44 +106,64 @@ export class ReportRenderer {
|
|
|
85
106
|
_renderReportFooter(report) {
|
|
86
107
|
const footer = this._dom.createComponent('footer');
|
|
87
108
|
|
|
88
|
-
|
|
89
|
-
env.id = 'runtime-settings';
|
|
90
|
-
this._dom.find('.lh-env__title', footer).textContent = Util.i18n.strings.runtimeSettingsTitle;
|
|
91
|
-
|
|
92
|
-
const envValues = Util.getEnvironmentDisplayValues(report.configSettings || {});
|
|
93
|
-
const runtimeValues = [
|
|
94
|
-
{name: Util.i18n.strings.runtimeSettingsUrl, description: report.finalUrl},
|
|
95
|
-
{name: Util.i18n.strings.runtimeSettingsFetchTime,
|
|
96
|
-
description: Util.i18n.formatDateTime(report.fetchTime)},
|
|
97
|
-
...envValues,
|
|
98
|
-
{name: Util.i18n.strings.runtimeSettingsChannel, description: report.configSettings.channel},
|
|
99
|
-
{name: Util.i18n.strings.runtimeSettingsUA, description: report.userAgent},
|
|
100
|
-
{name: Util.i18n.strings.runtimeSettingsUANetwork, description: report.environment &&
|
|
101
|
-
report.environment.networkUserAgent},
|
|
102
|
-
{name: Util.i18n.strings.runtimeSettingsBenchmark, description: report.environment &&
|
|
103
|
-
report.environment.benchmarkIndex.toFixed(0)},
|
|
104
|
-
];
|
|
105
|
-
if (report.environment.credits && report.environment.credits['axe-core']) {
|
|
106
|
-
runtimeValues.push({
|
|
107
|
-
name: Util.i18n.strings.runtimeSettingsAxeVersion,
|
|
108
|
-
description: report.environment.credits['axe-core'],
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
for (const runtime of runtimeValues) {
|
|
113
|
-
if (!runtime.description) continue;
|
|
114
|
-
|
|
115
|
-
const item = this._dom.createComponent('envItem');
|
|
116
|
-
this._dom.find('.lh-env__name', item).textContent = runtime.name;
|
|
117
|
-
this._dom.find('.lh-env__description', item).textContent = runtime.description;
|
|
118
|
-
env.appendChild(item);
|
|
119
|
-
}
|
|
109
|
+
this._renderMetaBlock(report, footer);
|
|
120
110
|
|
|
121
111
|
this._dom.find('.lh-footer__version_issue', footer).textContent = Util.i18n.strings.footerIssue;
|
|
122
112
|
this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion;
|
|
123
113
|
return footer;
|
|
124
114
|
}
|
|
125
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @param {LH.ReportResult} report
|
|
118
|
+
* @param {DocumentFragment} footer
|
|
119
|
+
*/
|
|
120
|
+
_renderMetaBlock(report, footer) {
|
|
121
|
+
const envValues = Util.getEmulationDescriptions(report.configSettings || {});
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
const match = report.userAgent.match(/(\w*Chrome\/[\d.]+)/); // \w* to include 'HeadlessChrome'
|
|
125
|
+
const chromeVer = Array.isArray(match)
|
|
126
|
+
? match[1].replace('/', ' ').replace('Chrome', 'Chromium')
|
|
127
|
+
: 'Chromium';
|
|
128
|
+
const channel = report.configSettings.channel;
|
|
129
|
+
const benchmarkIndex = report.environment.benchmarkIndex.toFixed(0);
|
|
130
|
+
const axeVersion = report.environment.credits?.['axe-core'];
|
|
131
|
+
|
|
132
|
+
// [CSS icon class, textContent, tooltipText]
|
|
133
|
+
const metaItems = [
|
|
134
|
+
['date',
|
|
135
|
+
`Captured at ${Util.i18n.formatDateTime(report.fetchTime)}`],
|
|
136
|
+
['devices',
|
|
137
|
+
`${envValues.deviceEmulation} with Lighthouse ${report.lighthouseVersion}`,
|
|
138
|
+
`${Util.i18n.strings.runtimeSettingsBenchmark}: ${benchmarkIndex}` +
|
|
139
|
+
`\n${Util.i18n.strings.runtimeSettingsCPUThrottling}: ${envValues.cpuThrottling}` +
|
|
140
|
+
(axeVersion ? `\n${Util.i18n.strings.runtimeSettingsAxeVersion}: ${axeVersion}` : '')],
|
|
141
|
+
['samples-one',
|
|
142
|
+
Util.i18n.strings.runtimeSingleLoad,
|
|
143
|
+
Util.i18n.strings.runtimeSingleLoadTooltip],
|
|
144
|
+
['stopwatch',
|
|
145
|
+
Util.i18n.strings.runtimeAnalysisWindow],
|
|
146
|
+
['networkspeed',
|
|
147
|
+
`${envValues.summary}`,
|
|
148
|
+
`${Util.i18n.strings.runtimeSettingsNetworkThrottling}: ${envValues.networkThrottling}`],
|
|
149
|
+
['chrome',
|
|
150
|
+
`Using ${chromeVer}` + (channel ? ` with ${channel}` : ''),
|
|
151
|
+
`${Util.i18n.strings.runtimeSettingsUANetwork}: "${report.environment.networkUserAgent}"`],
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
const metaItemsEl = this._dom.find('.lh-meta__items', footer);
|
|
155
|
+
for (const [iconname, text, tooltip] of metaItems) {
|
|
156
|
+
const itemEl = this._dom.createChildOf(metaItemsEl, 'li', 'lh-meta__item');
|
|
157
|
+
itemEl.textContent = text;
|
|
158
|
+
if (tooltip) {
|
|
159
|
+
itemEl.classList.add('lh-tooltip-boundary');
|
|
160
|
+
const tooltipEl = this._dom.createChildOf(itemEl, 'div', 'lh-tooltip');
|
|
161
|
+
tooltipEl.textContent = tooltip;
|
|
162
|
+
}
|
|
163
|
+
itemEl.classList.add('lh-report-icon', `lh-report-icon--${iconname}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
126
167
|
/**
|
|
127
168
|
* Returns a div with a list of top-level warnings, or an empty div if no warnings.
|
|
128
169
|
* @param {LH.ReportResult} report
|
|
@@ -177,12 +218,13 @@ export class ReportRenderer {
|
|
|
177
218
|
gaugeWrapperEl.addEventListener('click', e => {
|
|
178
219
|
if (!gaugeWrapperEl.matches('[href^="#"]')) return;
|
|
179
220
|
const selector = gaugeWrapperEl.getAttribute('href');
|
|
180
|
-
const reportRoot =
|
|
221
|
+
const reportRoot = this._dom.rootEl;
|
|
181
222
|
if (!selector || !reportRoot) return;
|
|
182
223
|
const destEl = this._dom.find(selector, reportRoot);
|
|
183
224
|
e.preventDefault();
|
|
184
225
|
destEl.scrollIntoView();
|
|
185
226
|
});
|
|
227
|
+
this._opts.onPageAnchorRendered?.(gaugeWrapperEl);
|
|
186
228
|
}
|
|
187
229
|
|
|
188
230
|
|
|
@@ -192,7 +234,7 @@ export class ReportRenderer {
|
|
|
192
234
|
// The renderer for default categories is just the default CategoryRenderer.
|
|
193
235
|
// If the functions are equal, then renderer is an instance of CategoryRenderer.
|
|
194
236
|
// For example, the PWA category uses PwaCategoryRenderer, which overrides
|
|
195
|
-
// CategoryRenderer.
|
|
237
|
+
// CategoryRenderer.renderCategoryScore, so it would fail this check and be placed
|
|
196
238
|
// in the customGauges bucket.
|
|
197
239
|
defaultGauges.push(categoryGauge);
|
|
198
240
|
} else {
|
|
@@ -247,8 +289,10 @@ export class ReportRenderer {
|
|
|
247
289
|
headerContainer.classList.add('lh-header--solo-category');
|
|
248
290
|
}
|
|
249
291
|
|
|
292
|
+
const scoreScale = this._dom.createElement('div');
|
|
293
|
+
scoreScale.classList.add('lh-scorescale-wrap');
|
|
294
|
+
scoreScale.append(this._dom.createComponent('scorescale'));
|
|
250
295
|
if (scoreHeader) {
|
|
251
|
-
const scoreScale = this._dom.createComponent('scorescale');
|
|
252
296
|
const scoresContainer = this._dom.find('.lh-scores-container', headerContainer);
|
|
253
297
|
scoreHeader.append(
|
|
254
298
|
...this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers));
|
|
@@ -266,7 +310,7 @@ export class ReportRenderer {
|
|
|
266
310
|
for (const category of Object.values(report.categories)) {
|
|
267
311
|
const renderer = specificCategoryRenderers[category.id] || categoryRenderer;
|
|
268
312
|
// .lh-category-wrapper is full-width and provides horizontal rules between categories.
|
|
269
|
-
// .lh-category within has the max-width: var(--report-width);
|
|
313
|
+
// .lh-category within has the max-width: var(--report-content-width);
|
|
270
314
|
const wrapper = renderer.dom.createChildOf(categories, 'div', 'lh-category-wrapper');
|
|
271
315
|
wrapper.appendChild(renderer.render(
|
|
272
316
|
category,
|
|
@@ -275,11 +319,15 @@ export class ReportRenderer {
|
|
|
275
319
|
));
|
|
276
320
|
}
|
|
277
321
|
|
|
322
|
+
categoryRenderer.injectFinalScreenshot(categories, report.audits, scoreScale);
|
|
323
|
+
|
|
278
324
|
const reportFragment = this._dom.createFragment();
|
|
279
325
|
reportFragment.append(this._dom.createComponent('styles'));
|
|
280
|
-
const topbarDocumentFragment = this._renderReportTopbar(report);
|
|
281
326
|
|
|
282
|
-
|
|
327
|
+
if (!this._opts.omitTopbar) {
|
|
328
|
+
reportFragment.appendChild(this._renderReportTopbar(report));
|
|
329
|
+
}
|
|
330
|
+
|
|
283
331
|
reportFragment.appendChild(reportContainer);
|
|
284
332
|
reportContainer.appendChild(headerContainer);
|
|
285
333
|
reportContainer.appendChild(reportSection);
|
|
@@ -287,7 +335,7 @@ export class ReportRenderer {
|
|
|
287
335
|
|
|
288
336
|
if (fullPageScreenshot) {
|
|
289
337
|
ElementScreenshotRenderer.installFullPageScreenshot(
|
|
290
|
-
|
|
338
|
+
this._dom.rootEl, fullPageScreenshot.screenshot);
|
|
291
339
|
}
|
|
292
340
|
|
|
293
341
|
return reportFragment;
|