diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b6b170bc23c..cd63ebda490e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,7 @@ jobs: - run: yarn test-legacy-javascript - run: yarn i18n:checks - run: yarn dogfood-lhci + - run: yarn generate-insight-audits # Fail if any changes were written to any source files or generated untracked files (ex, from: build/build-cdt-lib.js). - run: git add -A && git diff --cached --exit-code diff --git a/build/build-report.js b/build/build-report.js index 8d6ab13e7e6e..5cdf1f6bf1ca 100644 --- a/build/build-report.js +++ b/build/build-report.js @@ -44,7 +44,7 @@ function buildStandaloneReport() { outfile: 'dist/report/standalone.js', format: 'iife', bundle: true, - minify: true, + minify: !process.env.DEBUG, }); } diff --git a/cli/test/smokehouse/test-definitions/perf-trace-elements.js b/cli/test/smokehouse/test-definitions/perf-trace-elements.js index c03b57dda94e..cfc357d92e37 100644 --- a/cli/test/smokehouse/test-definitions/perf-trace-elements.js +++ b/cli/test/smokehouse/test-definitions/perf-trace-elements.js @@ -31,6 +31,12 @@ const expectations = { }, artifacts: { TraceElements: [ + {traceEventType: 'trace-engine'}, + {traceEventType: 'trace-engine'}, + {traceEventType: 'trace-engine'}, + {traceEventType: 'trace-engine'}, + {traceEventType: 'trace-engine'}, + {traceEventType: 'trace-engine', _minChromiumVersion: '135'}, { traceEventType: 'largest-contentful-paint', node: { diff --git a/core/audits/insights/README.md b/core/audits/insights/README.md new file mode 100644 index 000000000000..305aa4c1b078 --- /dev/null +++ b/core/audits/insights/README.md @@ -0,0 +1,3 @@ +# Insight audits + +When adding new insight audits, you can start off by just running `yarn generate-insight-audits` diff --git a/core/audits/insights/cls-culprits-insight.js b/core/audits/insights/cls-culprits-insight.js new file mode 100644 index 000000000000..71f7835c87ab --- /dev/null +++ b/core/audits/insights/cls-culprits-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/CLSCulprits.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js', UIStrings); + +class CLSCulpritsInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'cls-culprits-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'CLSCulprits', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default CLSCulpritsInsight; diff --git a/core/audits/insights/document-latency-insight.js b/core/audits/insights/document-latency-insight.js new file mode 100644 index 000000000000..8bd54832b87a --- /dev/null +++ b/core/audits/insights/document-latency-insight.js @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/DocumentLatency.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js', UIStrings); + +class DocumentLatencyInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'document-latency-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'DocumentLatency', (insight) => { + if (!insight.data) { + return; + } + + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default DocumentLatencyInsight; diff --git a/core/audits/insights/dom-size-insight.js b/core/audits/insights/dom-size-insight.js new file mode 100644 index 000000000000..adc1cf669dfc --- /dev/null +++ b/core/audits/insights/dom-size-insight.js @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/DOMSize.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js', UIStrings); + +class DOMSizeInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'dom-size-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + return adaptInsightToAuditProduct(artifacts, context, 'DOMSize', (insight) => { + if (!insight.maxDOMStats?.args.data.maxChildren || !insight.maxDOMStats?.args.data.maxDepth) { + return; + } + + const {totalElements, maxChildren, maxDepth} = insight.maxDOMStats.args.data; + + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + {key: 'statistic', valueType: 'text', label: str_(UIStrings.statistic)}, + {key: 'node', valueType: 'node', label: str_(UIStrings.element)}, + {key: 'value', valueType: 'numeric', label: str_(UIStrings.value)}, + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + { + statistic: str_(UIStrings.totalElements), + value: { + type: 'numeric', + granularity: 1, + value: totalElements, + }, + }, + { + statistic: str_(UIStrings.maxChildren), + node: makeNodeItemForNodeId(artifacts.TraceElements, maxChildren.nodeId), + value: { + type: 'numeric', + granularity: 1, + value: maxChildren.numChildren, + }, + }, + { + statistic: str_(UIStrings.maxDOMDepth), + node: makeNodeItemForNodeId(artifacts.TraceElements, maxDepth.nodeId), + value: { + type: 'numeric', + granularity: 1, + value: maxDepth.depth, + }, + }, + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default DOMSizeInsight; diff --git a/core/audits/insights/font-display-insight.js b/core/audits/insights/font-display-insight.js new file mode 100644 index 000000000000..c796ef774305 --- /dev/null +++ b/core/audits/insights/font-display-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/FontDisplay.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js', UIStrings); + +class FontDisplayInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'font-display-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'FontDisplay', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default FontDisplayInsight; diff --git a/core/audits/insights/forced-reflow-insight.js b/core/audits/insights/forced-reflow-insight.js new file mode 100644 index 000000000000..d3e4a1a217c8 --- /dev/null +++ b/core/audits/insights/forced-reflow-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ForcedReflow.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js', UIStrings); + +class ForcedReflowInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'forced-reflow-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'ForcedReflow', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default ForcedReflowInsight; diff --git a/core/audits/insights/image-delivery-insight.js b/core/audits/insights/image-delivery-insight.js new file mode 100644 index 000000000000..2b87d9f3331d --- /dev/null +++ b/core/audits/insights/image-delivery-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ImageDelivery.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js', UIStrings); + +class ImageDeliveryInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'image-delivery-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'ImageDelivery', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default ImageDeliveryInsight; diff --git a/core/audits/insights/insight-audit.js b/core/audits/insights/insight-audit.js new file mode 100644 index 000000000000..c5fa6119f413 --- /dev/null +++ b/core/audits/insights/insight-audit.js @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {NO_NAVIGATION} from '@paulirish/trace_engine/models/trace/types/TraceEvents.js'; + +import {ProcessedTrace} from '../../computed/processed-trace.js'; +import {TraceEngineResult} from '../../computed/trace-engine-result.js'; +import {Audit} from '../audit.js'; + +/** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ +async function getInsightSet(artifacts, context) { + const trace = artifacts.traces[Audit.DEFAULT_PASS]; + const processedTrace = await ProcessedTrace.request(trace, context); + const traceEngineResult = await TraceEngineResult.request({trace}, context); + + const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId; + const key = navigationId ?? NO_NAVIGATION; + + return traceEngineResult.insights.get(key); +} + +/** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @param {T} insightName + * @param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T]) => LH.Audit.Details|undefined} createDetails + * @template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T + * @return {Promise} + */ +async function adaptInsightToAuditProduct(artifacts, context, insightName, createDetails) { + const insights = await getInsightSet(artifacts, context); + if (!insights) { + return { + scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE, + score: null, + }; + } + + const insight = insights.model[insightName]; + const details = createDetails(insight); + if (!details || (details.type === 'table' && details.headings.length === 0)) { + return { + scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE, + score: null, + }; + } + + return { + scoreDisplayMode: + insight.metricSavings ? Audit.SCORING_MODES.METRIC_SAVINGS : Audit.SCORING_MODES.NUMERIC, + score: insight.shouldShow ? 0 : 1, + metricSavings: insight.metricSavings, + warnings: insight.warnings, + details, + }; +} + +/** + * @param {LH.Artifacts.TraceElement[]} traceElements + * @param {number|null|undefined} nodeId + * @return {LH.Audit.Details.NodeValue|undefined} + */ +function makeNodeItemForNodeId(traceElements, nodeId) { + if (typeof nodeId !== 'number') { + return; + } + + const traceElement = + traceElements.find(te => te.traceEventType === 'trace-engine' && te.nodeId === nodeId); + const node = traceElement?.node; + if (!node) { + return; + } + + return Audit.makeNodeItem(node); +} + +export { + adaptInsightToAuditProduct, + makeNodeItemForNodeId, +}; diff --git a/core/audits/insights/interaction-to-next-paint-insight.js b/core/audits/insights/interaction-to-next-paint-insight.js new file mode 100644 index 000000000000..344e81c874d2 --- /dev/null +++ b/core/audits/insights/interaction-to-next-paint-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js', UIStrings); + +class InteractionToNextPaintInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'interaction-to-next-paint-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'InteractionToNextPaint', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default InteractionToNextPaintInsight; diff --git a/core/audits/insights/lcp-discovery-insight.js b/core/audits/insights/lcp-discovery-insight.js new file mode 100644 index 000000000000..f640d5a64d0c --- /dev/null +++ b/core/audits/insights/lcp-discovery-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js', UIStrings); + +class LCPDiscoveryInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'lcp-discovery-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'LCPDiscovery', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default LCPDiscoveryInsight; diff --git a/core/audits/insights/lcp-phases-insight.js b/core/audits/insights/lcp-phases-insight.js new file mode 100644 index 000000000000..b1a0a8304ea0 --- /dev/null +++ b/core/audits/insights/lcp-phases-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/LCPPhases.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js', UIStrings); + +class LCPPhasesInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'lcp-phases-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'LCPPhases', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default LCPPhasesInsight; diff --git a/core/audits/insights/long-critical-network-tree-insight.js b/core/audits/insights/long-critical-network-tree-insight.js new file mode 100644 index 000000000000..f21de1398845 --- /dev/null +++ b/core/audits/insights/long-critical-network-tree-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js', UIStrings); + +class LongCriticalNetworkTreeInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'long-critical-network-tree-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'LongCriticalNetworkTree', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default LongCriticalNetworkTreeInsight; diff --git a/core/audits/insights/render-blocking-insight.js b/core/audits/insights/render-blocking-insight.js new file mode 100644 index 000000000000..692b071fcc07 --- /dev/null +++ b/core/audits/insights/render-blocking-insight.js @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/RenderBlocking.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js', UIStrings); + +class RenderBlockingInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'render-blocking-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: show UIStrings.noRenderBlocking if nothing was blocking? + return adaptInsightToAuditProduct(artifacts, context, 'RenderBlocking', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)}, + {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnTransferSize)}, + {key: 'wastedMs', valueType: 'timespanMs', label: str_(i18n.UIStrings.columnWastedMs)}, + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = insight.renderBlockingRequests.map(request => ({ + url: request.args.data.url, + totalBytes: request.args.data.encodedDataLength, + wastedMs: insight.requestIdToWastedMs?.get(request.args.data.requestId), + })); + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default RenderBlockingInsight; diff --git a/core/audits/insights/slow-css-selector-insight.js b/core/audits/insights/slow-css-selector-insight.js new file mode 100644 index 000000000000..b819e1e5f082 --- /dev/null +++ b/core/audits/insights/slow-css-selector-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js', UIStrings); + +class SlowCSSSelectorInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'slow-css-selector-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'SlowCSSSelector', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default SlowCSSSelectorInsight; diff --git a/core/audits/insights/third-parties-insight.js b/core/audits/insights/third-parties-insight.js new file mode 100644 index 000000000000..13873ee63056 --- /dev/null +++ b/core/audits/insights/third-parties-insight.js @@ -0,0 +1,52 @@ +/* eslint-disable no-unused-vars */ // TODO: remove once implemented. + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ThirdParties.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js', UIStrings); + +class ThirdPartiesInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'third-parties-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, 'ThirdParties', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default ThirdPartiesInsight; diff --git a/core/audits/insights/viewport-insight.js b/core/audits/insights/viewport-insight.js new file mode 100644 index 000000000000..2acdc05f40c2 --- /dev/null +++ b/core/audits/insights/viewport-insight.js @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/Viewport.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js', UIStrings); + +class ViewportInsight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'viewport-insight', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + return adaptInsightToAuditProduct(artifacts, context, 'Viewport', (insight) => { + const nodeId = insight.viewportEvent?.args.data.node_id; + + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + {key: 'node', valueType: 'node', label: ''}, + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + {node: makeNodeItemForNodeId(artifacts.TraceElements, nodeId)}, + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default ViewportInsight; diff --git a/core/computed/metrics/lantern-metric.js b/core/computed/metrics/lantern-metric.js index 307bef465311..83e6d62ce428 100644 --- a/core/computed/metrics/lantern-metric.js +++ b/core/computed/metrics/lantern-metric.js @@ -39,7 +39,11 @@ async function getComputationDataParamsFromTrace(data, context) { const graph = await PageDependencyGraph.request({...data, fromTrace: true}, context); const traceEngineResult = await TraceEngineResult.request(data, context); const frameId = traceEngineResult.data.Meta.mainFrameId; - const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data.navigationId; + const navigationId = traceEngineResult.data.Meta.mainFrameNavigations[0].args.data?.navigationId; + if (!navigationId) { + throw new Error(`Lantern metrics could not be calculated due to missing navigation id`); + } + const processedNavigation = Lantern.TraceEngineComputationData.createProcessedNavigation( traceEngineResult.data, frameId, navigationId); const simulator = data.simulator || (await LoadSimulator.request(data, context)); diff --git a/core/computed/trace-engine-result.js b/core/computed/trace-engine-result.js index 3acc45613bd7..e158e2cd778a 100644 --- a/core/computed/trace-engine-result.js +++ b/core/computed/trace-engine-result.js @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as i18n from '../lib/i18n/i18n.js'; import * as TraceEngine from '../lib/trace-engine.js'; import {makeComputedArtifact} from './computed-artifact.js'; import {CumulativeLayoutShift} from './metrics/cumulative-layout-shift.js'; @@ -28,34 +27,9 @@ class TraceEngineResult { ), {}); if (!processor.parsedTrace) throw new Error('No data'); if (!processor.insights) throw new Error('No insights'); - this.localizeInsights(processor.insights); return {data: processor.parsedTrace, insights: processor.insights}; } - /** - * @param {import('@paulirish/trace_engine/models/trace/insights/types.js').TraceInsightSets} insightSets - */ - static localizeInsights(insightSets) { - for (const insightSet of insightSets.values()) { - for (const [name, model] of Object.entries(insightSet.model)) { - if (model instanceof Error) { - continue; - } - - const key = `node_modules/@paulirish/trace_engine/models/trace/insights/${name}.js`; - const str_ = i18n.createIcuMessageFn(key, { - title: model.title, - description: model.description, - }); - - // @ts-expect-error coerce to string, should be fine - model.title = str_(model.title); - // @ts-expect-error coerce to string, should be fine - model.description = str_(model.description); - } - } - } - /** * @param {{trace: LH.Trace}} data * @param {LH.Artifacts.ComputedContext} context diff --git a/core/config/default-config.js b/core/config/default-config.js index 03ba163c820c..2bdab29d9415 100644 --- a/core/config/default-config.js +++ b/core/config/default-config.js @@ -15,6 +15,8 @@ const UIStrings = { performanceCategoryTitle: 'Performance', /** Title of the speed metrics section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. */ metricGroupTitle: 'Metrics', + /** Title of the insights section of the Performance category. Within this section are various insights to give developers tips on how to improve the performance of their page. */ + insightGroupTitle: 'Insights', /** Title of an opportunity sub-section of the Performance category. Within this section are audits with imperative titles that suggest actions the user can take to improve the time of the first initial render of the webpage. */ firstPaintImprovementsGroupTitle: 'First Paint Improvements', /** Description of an opportunity sub-section of the Performance category. Within this section are audits with imperative titles that suggest actions the user can take to improve the time of the first initial render of the webpage. */ @@ -308,11 +310,28 @@ const defaultConfig = { 'seo/manual/structured-data', 'work-during-interaction', 'bf-cache', + 'insights/cls-culprits-insight', + 'insights/document-latency-insight', + 'insights/dom-size-insight', + 'insights/font-display-insight', + 'insights/forced-reflow-insight', + 'insights/image-delivery-insight', + 'insights/interaction-to-next-paint-insight', + 'insights/lcp-discovery-insight', + 'insights/lcp-phases-insight', + 'insights/long-critical-network-tree-insight', + 'insights/render-blocking-insight', + 'insights/slow-css-selector-insight', + 'insights/third-parties-insight', + 'insights/viewport-insight', ], groups: { 'metrics': { title: str_(UIStrings.metricGroupTitle), }, + 'insights': { + title: str_(UIStrings.insightGroupTitle), + }, 'diagnostics': { title: str_(UIStrings.diagnosticsGroupTitle), description: str_(UIStrings.diagnosticsGroupDescription), @@ -388,6 +407,22 @@ const defaultConfig = { {id: 'speed-index', weight: 10, group: 'metrics', acronym: 'SI'}, {id: 'interaction-to-next-paint', weight: 0, group: 'metrics', acronym: 'INP'}, + // Insight audits. + {id: 'cls-culprits-insight', weight: 0, group: 'hidden'}, + {id: 'document-latency-insight', weight: 0, group: 'hidden'}, + {id: 'dom-size-insight', weight: 0, group: 'hidden'}, + {id: 'font-display-insight', weight: 0, group: 'hidden'}, + {id: 'forced-reflow-insight', weight: 0, group: 'hidden'}, + {id: 'image-delivery-insight', weight: 0, group: 'hidden'}, + {id: 'interaction-to-next-paint-insight', weight: 0, group: 'hidden'}, + {id: 'lcp-discovery-insight', weight: 0, group: 'hidden'}, + {id: 'lcp-phases-insight', weight: 0, group: 'hidden'}, + {id: 'long-critical-network-tree-insight', weight: 0, group: 'hidden'}, + {id: 'render-blocking-insight', weight: 0, group: 'hidden'}, + {id: 'slow-css-selector-insight', weight: 0, group: 'hidden'}, + {id: 'third-parties-insight', weight: 0, group: 'hidden'}, + {id: 'viewport-insight', weight: 0, group: 'hidden'}, + // These are our "invisible" metrics. Not displayed, but still in the LHR. {id: 'interactive', weight: 0, group: 'hidden', acronym: 'TTI'}, {id: 'max-potential-fid', weight: 0, group: 'hidden'}, diff --git a/core/gather/gatherers/trace-elements.js b/core/gather/gatherers/trace-elements.js index 6b083051b6e2..ed8874e76666 100644 --- a/core/gather/gatherers/trace-elements.js +++ b/core/gather/gatherers/trace-elements.js @@ -65,6 +65,71 @@ class TraceElements extends BaseGatherer { if (name) this.animationIdToName.set(id, name); } + /** + * @param {LH.Artifacts.TraceEngineResult} traceEngineResult + * @param {string|undefined} navigationId + * @return {Promise>} + */ + static async getTraceEngineElements(traceEngineResult, navigationId) { + // Can only resolve elements for the latest insight set, which should correspond + // to the current navigation id (if present). Can't resolve elements for pages + // that are gone. + const insightSet = [...traceEngineResult.insights.values()].at(-1); + if (!insightSet) { + return []; + } + + if (navigationId) { + if (insightSet.navigation?.args.data?.navigationId !== navigationId) { + return []; + } + } else { + if (insightSet.navigation) { + return []; + } + } + + /** + * Execute `cb(obj, key)` on every object property (non-objects only), recursively. + * @param {any} obj + * @param {(obj: Record, key: string) => void} cb + * @param {Set} seen + */ + function recursiveObjectEnumerate(obj, cb, seen) { + if (seen.has(seen)) { + return; + } + + seen.add(obj); + + if (obj && typeof obj === 'object' && !Array.isArray(obj)) { + Object.keys(obj).forEach(key => { + if (typeof obj[key] === 'object') { + recursiveObjectEnumerate(obj[key], cb, seen); + } else { + cb(obj, key); + } + }); + } else if (Array.isArray(obj)) { + obj.forEach(item => { + if (typeof item === 'object' || Array.isArray(item)) { + recursiveObjectEnumerate(item, cb, seen); + } + }); + } + } + + /** @type {number[]} */ + const nodeIds = []; + recursiveObjectEnumerate(insightSet.model, (obj, key) => { + if (typeof obj[key] === 'number' && (key === 'nodeId' || key === 'node_id')) { + nodeIds.push(obj[key]); + } + }, new Set()); + + return [...new Set(nodeIds)].map(id => ({nodeId: id})); + } + /** * We want to a single representative node to represent the shift, so let's pick * the one with the largest impact (size x distance moved). @@ -319,7 +384,10 @@ class TraceElements extends BaseGatherer { const processedTrace = await ProcessedTrace.request(trace, context); const {mainThreadEvents} = processedTrace; + const navigationId = processedTrace.timeOriginEvt.args.data?.navigationId; + const traceEngineData = await TraceElements.getTraceEngineElements( + traceEngineResult, navigationId); const lcpNodeData = await TraceElements.getLcpElement(trace, context); const shiftsData = await TraceElements.getTopLayoutShifts( trace, traceEngineResult.data, rootCauses, context); @@ -328,6 +396,7 @@ class TraceElements extends BaseGatherer { /** @type {Map} */ const backendNodeDataMap = new Map([ + ['trace-engine', traceEngineData], ['largest-contentful-paint', lcpNodeData ? [lcpNodeData] : []], ['layout-shift', shiftsData], ['animation', animatedElementData], @@ -336,6 +405,7 @@ class TraceElements extends BaseGatherer { /** @type {Map} */ const callFunctionOnCache = new Map(); + /** @type {LH.Artifacts.TraceElement[]} */ const traceElements = []; for (const [traceEventType, backendNodeData] of backendNodeDataMap) { for (let i = 0; i < backendNodeData.length; i++) { @@ -348,8 +418,8 @@ class TraceElements extends BaseGatherer { if (response?.result?.value) { traceElements.push({ - traceEventType, ...response.result.value, + traceEventType, animations: backendNodeData[i].animations, nodeId: backendNodeId, type: backendNodeData[i].type, diff --git a/core/runner.js b/core/runner.js index 96e762bd40cf..c940ef35446e 100644 --- a/core/runner.js +++ b/core/runner.js @@ -484,6 +484,7 @@ vs 'multi-check-audit.js', 'byte-efficiency/byte-efficiency-audit.js', 'manual/manual-audit.js', + 'insights/insight-audit.js', ]; const fileList = [ @@ -499,6 +500,7 @@ vs ...fs.readdirSync(path.join(moduleDir, './audits/byte-efficiency')) .map(f => `byte-efficiency/${f}`), ...fs.readdirSync(path.join(moduleDir, './audits/manual')).map(f => `manual/${f}`), + ...fs.readdirSync(path.join(moduleDir, './audits/insights')).map(f => `insights/${f}`), ]; return fileList.filter(f => { return /\.js$/.test(f) && !ignoredFiles.includes(f); diff --git a/core/scripts/generate-insight-audits.js b/core/scripts/generate-insight-audits.js new file mode 100644 index 000000000000..3da6e0561251 --- /dev/null +++ b/core/scripts/generate-insight-audits.js @@ -0,0 +1,133 @@ +/* eslint-disable max-len */ + +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Generates a new insight audit for every insight that does not + * already have one. + */ + +import fs from 'fs'; + +import {LH_ROOT} from '../../shared/root.js'; + +function getAllInsightNames() { + const matches = fs.readFileSync('node_modules/@paulirish/trace_engine/models/trace/insights/Models.js', 'utf-8') + .matchAll(/as ([a-zA-Z]+)/g); + return [...matches].map(m => m[1]).sort(); +} + +/** + * @param {string} str + * @return {string} + */ +function kebabize(str) { + return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, + ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); +} + +/** + * @param {string} insightName + * @param {string} auditId + * @return {string} + */ +function createAuditCode(insightName, auditId) { + return ` +/** + * @license + * Copyright ${new Date().getFullYear()} Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/${insightName}.js'; + +import {Audit} from '../audit.js'; +import * as i18n from '../../lib/i18n/i18n.js'; +import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; + +// eslint-disable-next-line max-len +const str_ = i18n.createIcuMessageFn('node_modules/@paulirish/trace_engine/models/trace/insights/${insightName}.js', UIStrings); + +class ${insightName}Insight extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: '${auditId}', + title: str_(UIStrings.title), + failureTitle: str_(UIStrings.title), + description: str_(UIStrings.description), + guidanceLevel: 3, // TODO: confirm/change. + requiredArtifacts: ['traces', 'TraceElements'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + // TODO: implement. + return adaptInsightToAuditProduct(artifacts, context, '${insightName}', (insight) => { + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + ]; + /** @type {LH.Audit.Details.Table['items']} */ + const items = [ + ]; + return Audit.makeTableDetails(headings, items); + }); + } +} + +export default ${insightName}Insight; +`.trim() + '\n'; +} + +const insightNames = getAllInsightNames(); + +const allAuditIds = []; +for (const insightName of insightNames) { + const auditId = `${kebabize(insightName)}-insight`; + allAuditIds.push(auditId); + + const outputFile = `${LH_ROOT}/core/audits/insights/${auditId}.js`; + if (fs.existsSync(outputFile)) { + continue; + } + + const code = createAuditCode(insightName, auditId); + fs.writeFileSync(outputFile, code); +} + +/** + * @param {string} text + * @param {string} needleStart + * @param {string} needleEnd + * @param {string} replacementText + * @return {string} + */ +function insert(text, needleStart, needleEnd, replacementText) { + const startIndex = text.indexOf(needleStart) + needleStart.length; + const endIndex = text.indexOf(needleEnd, startIndex); + return text.slice(0, startIndex) + replacementText + text.slice(endIndex); +} + +allAuditIds.sort(); + +const defaultConfigPath = `${LH_ROOT}/core/config/default-config.js`; +let defaultConfigText = fs.readFileSync(defaultConfigPath, 'utf-8'); + +const auditListCode = allAuditIds.map(id => ` 'insights/${id}',\n`).join('') + ' '; +defaultConfigText = insert(defaultConfigText, `'bf-cache',\n`, ']', auditListCode); + +const auditRefListCode = allAuditIds.map(id => ` {id: '${id}', weight: 0, group: 'hidden'},`).join('\n'); +defaultConfigText = insert(defaultConfigText, 'Insight audits.\n', '\n\n', auditRefListCode); + +fs.writeFileSync(defaultConfigPath, defaultConfigText); diff --git a/core/test/fixtures/user-flows/reports/sample-flow-result.json b/core/test/fixtures/user-flows/reports/sample-flow-result.json index 68ae6bb2d971..7b0fbc5b3808 100644 --- a/core/test/fixtures/user-flows/reports/sample-flow-result.json +++ b/core/test/fixtures/user-flows/reports/sample-flow-result.json @@ -3835,6 +3835,177 @@ "score": 1, "scoreDisplayMode": "binary", "guidanceLevel": 4 + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 72 + } + }, + { + "statistic": "Most children", + "value": { + "type": "numeric", + "granularity": 1, + "value": 14 + } + }, + { + "statistic": "DOM depth", + "value": { + "type": "numeric", + "granularity": 1, + "value": 9 + } + } + ] + }, + "guidanceLevel": 3 + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "Many APIs, typically reading layout geometry, force the rendering engine to pause script execution in order to calculate the style and layout. Learn more about [forced reflow](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and its mitigations.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "interaction-to-next-paint-insight": { + "id": "interaction-to-next-paint-insight", + "title": "INP by phase", + "description": "Start investigating with the longest phase. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "lcp-phases-insight": { + "id": "lcp-phases-insight", + "title": "LCP by phase", + "description": "Each [phase has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "long-critical-network-tree-insight": { + "id": "long-critical-network-tree-insight", + "title": "Long critical network tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources/) can move these network requests out of the critical path.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "slow-css-selector-insight": { + "id": "slow-css-selector-insight", + "title": "CSS Selector costs", + "description": "If Recalculate Style costs remain high, selector optimization can reduce them. [Optimize the selectors](https://developer.chrome.com/docs/devtools/performance/selector-stats) with both high elapsed time and high slow-path %. Simpler selectors, fewer selectors, a smaller DOM, and a shallower DOM will all reduce matching costs.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "Third parties", + "description": "Third party code can significantly impact load performance. [Reduce and defer loading of third party code](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/) to prioritize your page's content.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + {} + ] + }, + "guidanceLevel": 3 } }, "configSettings": { @@ -3929,6 +4100,76 @@ "group": "metrics", "acronym": "SI" }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interaction-to-next-paint-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-phases-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "long-critical-network-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "slow-css-selector-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, { "id": "interactive", "weight": 0, @@ -4742,6 +4983,9 @@ "metrics": { "title": "Metrics" }, + "insights": { + "title": "Insights" + }, "diagnostics": { "title": "Diagnostics", "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." @@ -6959,12 +7203,96 @@ }, { "startTime": 267, + "name": "lh:audit:cls-culprits-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 268, + "name": "lh:audit:document-latency-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 269, + "name": "lh:audit:dom-size-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 270, + "name": "lh:audit:font-display-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 271, + "name": "lh:audit:forced-reflow-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 272, + "name": "lh:audit:image-delivery-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 273, + "name": "lh:audit:interaction-to-next-paint-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 274, + "name": "lh:audit:lcp-discovery-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 275, + "name": "lh:audit:lcp-phases-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 276, + "name": "lh:audit:long-critical-network-tree-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 277, + "name": "lh:audit:render-blocking-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 278, + "name": "lh:audit:slow-css-selector-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 279, + "name": "lh:audit:third-parties-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 280, + "name": "lh:audit:viewport-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 281, "name": "lh:runner:generate", "duration": 1, "entryType": "measure" } ], - "total": 268 + "total": 282 }, "i18n": { "rendererFormattedStrings": { @@ -7402,7 +7730,8 @@ "core/lib/i18n/i18n.js | columnElement": [ "audits[largest-contentful-paint-element].details.items[0].headings[0].label", "audits[lcp-lazy-loaded].details.headings[0].label", - "audits[dom-size].details.headings[1].label" + "audits[dom-size].details.headings[1].label", + "audits[dom-size-insight].details.headings[1].label" ], "core/audits/largest-contentful-paint-element.js | columnPhase": [ "audits[largest-contentful-paint-element].details.items[1].headings[0].label" @@ -8226,6 +8555,105 @@ "core/audits/bf-cache.js | description": [ "audits[bf-cache].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": [ + "audits[interaction-to-next-paint-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | description": [ + "audits[interaction-to-next-paint-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | title": [ + "audits[lcp-phases-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | description": [ + "audits[lcp-phases-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ + "audits[long-critical-network-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | description": [ + "audits[long-critical-network-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | title": [ + "audits[slow-css-selector-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | description": [ + "audits[slow-css-selector-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], "core/config/default-config.js | performanceCategoryTitle": [ "categories.performance.title" ], @@ -8253,6 +8681,9 @@ "core/config/default-config.js | metricGroupTitle": [ "categoryGroups.metrics.title" ], + "core/config/default-config.js | insightGroupTitle": [ + "categoryGroups.insights.title" + ], "core/config/default-config.js | diagnosticsGroupTitle": [ "categoryGroups.diagnostics.title" ], @@ -10564,6 +10995,177 @@ "score": 1, "scoreDisplayMode": "binary", "guidanceLevel": 4 + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 363 + } + }, + { + "statistic": "Most children", + "value": { + "type": "numeric", + "granularity": 1, + "value": 17 + } + }, + { + "statistic": "DOM depth", + "value": { + "type": "numeric", + "granularity": 1, + "value": 11 + } + } + ] + }, + "guidanceLevel": 3 + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "Many APIs, typically reading layout geometry, force the rendering engine to pause script execution in order to calculate the style and layout. Learn more about [forced reflow](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and its mitigations.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "interaction-to-next-paint-insight": { + "id": "interaction-to-next-paint-insight", + "title": "INP by phase", + "description": "Start investigating with the longest phase. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "lcp-phases-insight": { + "id": "lcp-phases-insight", + "title": "LCP by phase", + "description": "Each [phase has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "long-critical-network-tree-insight": { + "id": "long-critical-network-tree-insight", + "title": "Long critical network tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources/) can move these network requests out of the critical path.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "slow-css-selector-insight": { + "id": "slow-css-selector-insight", + "title": "CSS Selector costs", + "description": "If Recalculate Style costs remain high, selector optimization can reduce them. [Optimize the selectors](https://developer.chrome.com/docs/devtools/performance/selector-stats) with both high elapsed time and high slow-path %. Simpler selectors, fewer selectors, a smaller DOM, and a shallower DOM will all reduce matching costs.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "Third parties", + "description": "Third party code can significantly impact load performance. [Reduce and defer loading of third party code](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/) to prioritize your page's content.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + {} + ] + }, + "guidanceLevel": 3 } }, "configSettings": { @@ -10635,16 +11237,86 @@ "acronym": "TBT" }, { - "id": "cumulative-layout-shift", - "weight": 25, - "group": "metrics", - "acronym": "CLS" + "id": "cumulative-layout-shift", + "weight": 25, + "group": "metrics", + "acronym": "CLS" + }, + { + "id": "interaction-to-next-paint", + "weight": 0, + "group": "metrics", + "acronym": "INP" + }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interaction-to-next-paint-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-phases-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "long-critical-network-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "slow-css-selector-insight", + "weight": 0, + "group": "hidden" }, { - "id": "interaction-to-next-paint", + "id": "third-parties-insight", "weight": 0, - "group": "metrics", - "acronym": "INP" + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" }, { "id": "uses-responsive-images", @@ -10867,6 +11539,9 @@ "metrics": { "title": "Metrics" }, + "insights": { + "title": "Insights" + }, "diagnostics": { "title": "Diagnostics", "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." @@ -11704,12 +12379,96 @@ }, { "startTime": 101, + "name": "lh:audit:cls-culprits-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 102, + "name": "lh:audit:document-latency-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 103, + "name": "lh:audit:dom-size-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 104, + "name": "lh:audit:font-display-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 105, + "name": "lh:audit:forced-reflow-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 106, + "name": "lh:audit:image-delivery-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 107, + "name": "lh:audit:interaction-to-next-paint-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 108, + "name": "lh:audit:lcp-discovery-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 109, + "name": "lh:audit:lcp-phases-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 110, + "name": "lh:audit:long-critical-network-tree-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 111, + "name": "lh:audit:render-blocking-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 112, + "name": "lh:audit:slow-css-selector-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 113, + "name": "lh:audit:third-parties-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 114, + "name": "lh:audit:viewport-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 115, "name": "lh:runner:generate", "duration": 1, "entryType": "measure" } ], - "total": 102 + "total": 116 }, "i18n": { "rendererFormattedStrings": { @@ -12019,7 +12778,8 @@ } ], "core/lib/i18n/i18n.js | columnElement": [ - "audits[layout-shifts].details.headings[0].label" + "audits[layout-shifts].details.headings[0].label", + "audits[dom-size-insight].details.headings[1].label" ], "core/audits/layout-shifts.js | columnScore": [ "audits[layout-shifts].details.headings[1].label" @@ -12198,6 +12958,105 @@ "core/audits/bf-cache.js | description": [ "audits[bf-cache].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": [ + "audits[interaction-to-next-paint-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | description": [ + "audits[interaction-to-next-paint-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | title": [ + "audits[lcp-phases-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | description": [ + "audits[lcp-phases-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ + "audits[long-critical-network-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | description": [ + "audits[long-critical-network-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | title": [ + "audits[slow-css-selector-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | description": [ + "audits[slow-css-selector-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], "core/config/default-config.js | performanceCategoryTitle": [ "categories.performance.title" ], @@ -12207,6 +13066,9 @@ "core/config/default-config.js | metricGroupTitle": [ "categoryGroups.metrics.title" ], + "core/config/default-config.js | insightGroupTitle": [ + "categoryGroups.insights.title" + ], "core/config/default-config.js | diagnosticsGroupTitle": [ "categoryGroups.diagnostics.title" ], @@ -15389,6 +16251,9 @@ "metrics": { "title": "Metrics" }, + "insights": { + "title": "Insights" + }, "diagnostics": { "title": "Diagnostics", "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." @@ -17537,6 +18402,9 @@ "core/config/default-config.js | metricGroupTitle": [ "categoryGroups.metrics.title" ], + "core/config/default-config.js | insightGroupTitle": [ + "categoryGroups.insights.title" + ], "core/config/default-config.js | diagnosticsGroupTitle": [ "categoryGroups.diagnostics.title" ], @@ -21583,83 +22451,254 @@ ] } }, - "link-text": { - "id": "link-text", - "title": "Links have descriptive text", - "description": "Descriptive link text helps search engines understand your content. [Learn how to make links more accessible](https://developer.chrome.com/docs/lighthouse/seo/link-text/).", - "score": 1, - "scoreDisplayMode": "binary", - "details": { - "type": "table", - "headings": [], - "items": [] - } + "link-text": { + "id": "link-text", + "title": "Links have descriptive text", + "description": "Descriptive link text helps search engines understand your content. [Learn how to make links more accessible](https://developer.chrome.com/docs/lighthouse/seo/link-text/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, + "crawlable-anchors": { + "id": "crawlable-anchors", + "title": "Links are crawlable", + "description": "Search engines may use `href` attributes on links to crawl websites. Ensure that the `href` attribute of anchor elements links to an appropriate destination, so more pages of the site can be discovered. [Learn how to make links crawlable](https://support.google.com/webmasters/answer/9112205)", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, + "is-crawlable": { + "id": "is-crawlable", + "title": "Page isn’t blocked from indexing", + "description": "Search engines are unable to include your pages in search results if they don't have permission to crawl them. [Learn more about crawler directives](https://developer.chrome.com/docs/lighthouse/seo/is-crawlable/).", + "score": 1, + "scoreDisplayMode": "binary", + "warnings": [], + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, + "robots-txt": { + "id": "robots-txt", + "title": "robots.txt is valid", + "description": "If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed. [Learn more about robots.txt](https://developer.chrome.com/docs/lighthouse/seo/invalid-robots-txt/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "hreflang": { + "id": "hreflang", + "title": "Document has a valid `hreflang`", + "description": "hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more about `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, + "canonical": { + "id": "canonical", + "title": "Document has a valid `rel=canonical`", + "description": "Canonical links suggest which URL to show in search results. [Learn more about canonical links](https://developer.chrome.com/docs/lighthouse/seo/canonical/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "structured-data": { + "id": "structured-data", + "title": "Structured data is valid", + "description": "Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more about Structured Data](https://developer.chrome.com/docs/lighthouse/seo/structured-data/).", + "score": null, + "scoreDisplayMode": "manual" + }, + "bf-cache": { + "id": "bf-cache", + "title": "Page didn't prevent back/forward cache restoration", + "description": "Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://developer.chrome.com/docs/lighthouse/performance/bf-cache/)", + "score": 1, + "scoreDisplayMode": "binary", + "guidanceLevel": 4 + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 85 + } + }, + { + "statistic": "Most children", + "value": { + "type": "numeric", + "granularity": 1, + "value": 15 + } + }, + { + "statistic": "DOM depth", + "value": { + "type": "numeric", + "granularity": 1, + "value": 9 + } + } + ] + }, + "guidanceLevel": 3 + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "Many APIs, typically reading layout geometry, force the rendering engine to pause script execution in order to calculate the style and layout. Learn more about [forced reflow](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and its mitigations.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "crawlable-anchors": { - "id": "crawlable-anchors", - "title": "Links are crawlable", - "description": "Search engines may use `href` attributes on links to crawl websites. Ensure that the `href` attribute of anchor elements links to an appropriate destination, so more pages of the site can be discovered. [Learn how to make links crawlable](https://support.google.com/webmasters/answer/9112205)", - "score": 1, - "scoreDisplayMode": "binary", - "details": { - "type": "table", - "headings": [], - "items": [] - } + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "is-crawlable": { - "id": "is-crawlable", - "title": "Page isn’t blocked from indexing", - "description": "Search engines are unable to include your pages in search results if they don't have permission to crawl them. [Learn more about crawler directives](https://developer.chrome.com/docs/lighthouse/seo/is-crawlable/).", - "score": 1, - "scoreDisplayMode": "binary", - "warnings": [], - "details": { - "type": "table", - "headings": [], - "items": [] - } + "interaction-to-next-paint-insight": { + "id": "interaction-to-next-paint-insight", + "title": "INP by phase", + "description": "Start investigating with the longest phase. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "robots-txt": { - "id": "robots-txt", - "title": "robots.txt is valid", - "description": "If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed. [Learn more about robots.txt](https://developer.chrome.com/docs/lighthouse/seo/invalid-robots-txt/).", + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", "score": null, - "scoreDisplayMode": "notApplicable" + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "hreflang": { - "id": "hreflang", - "title": "Document has a valid `hreflang`", - "description": "hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more about `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/).", - "score": 1, - "scoreDisplayMode": "binary", - "details": { - "type": "table", - "headings": [], - "items": [] - } + "lcp-phases-insight": { + "id": "lcp-phases-insight", + "title": "LCP by phase", + "description": "Each [phase has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "canonical": { - "id": "canonical", - "title": "Document has a valid `rel=canonical`", - "description": "Canonical links suggest which URL to show in search results. [Learn more about canonical links](https://developer.chrome.com/docs/lighthouse/seo/canonical/).", + "long-critical-network-tree-insight": { + "id": "long-critical-network-tree-insight", + "title": "Long critical network tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", "score": null, - "scoreDisplayMode": "notApplicable" + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "structured-data": { - "id": "structured-data", - "title": "Structured data is valid", - "description": "Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more about Structured Data](https://developer.chrome.com/docs/lighthouse/seo/structured-data/).", + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources/) can move these network requests out of the critical path.", "score": null, - "scoreDisplayMode": "manual" + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 }, - "bf-cache": { - "id": "bf-cache", - "title": "Page didn't prevent back/forward cache restoration", - "description": "Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://developer.chrome.com/docs/lighthouse/performance/bf-cache/)", + "slow-css-selector-insight": { + "id": "slow-css-selector-insight", + "title": "CSS Selector costs", + "description": "If Recalculate Style costs remain high, selector optimization can reduce them. [Optimize the selectors](https://developer.chrome.com/docs/devtools/performance/selector-stats) with both high elapsed time and high slow-path %. Simpler selectors, fewer selectors, a smaller DOM, and a shallower DOM will all reduce matching costs.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "Third parties", + "description": "Third party code can significantly impact load performance. [Reduce and defer loading of third party code](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/) to prioritize your page's content.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", "score": 1, - "scoreDisplayMode": "binary", - "guidanceLevel": 4 + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + {} + ] + }, + "guidanceLevel": 3 } }, "configSettings": { @@ -21754,6 +22793,76 @@ "group": "metrics", "acronym": "SI" }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interaction-to-next-paint-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-phases-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "long-critical-network-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "slow-css-selector-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, { "id": "interactive", "weight": 0, @@ -22567,6 +23676,9 @@ "metrics": { "title": "Metrics" }, + "insights": { + "title": "Insights" + }, "diagnostics": { "title": "Diagnostics", "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." @@ -24841,12 +25953,96 @@ }, { "startTime": 266, + "name": "lh:audit:cls-culprits-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 267, + "name": "lh:audit:document-latency-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 268, + "name": "lh:audit:dom-size-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 269, + "name": "lh:audit:font-display-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 270, + "name": "lh:audit:forced-reflow-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 271, + "name": "lh:audit:image-delivery-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 272, + "name": "lh:audit:interaction-to-next-paint-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 273, + "name": "lh:audit:lcp-discovery-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 274, + "name": "lh:audit:lcp-phases-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 275, + "name": "lh:audit:long-critical-network-tree-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 276, + "name": "lh:audit:render-blocking-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 277, + "name": "lh:audit:slow-css-selector-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 278, + "name": "lh:audit:third-parties-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 279, + "name": "lh:audit:viewport-insight", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 280, "name": "lh:runner:generate", "duration": 1, "entryType": "measure" } ], - "total": 267 + "total": 281 }, "i18n": { "rendererFormattedStrings": { @@ -25270,7 +26466,8 @@ "core/lib/i18n/i18n.js | columnElement": [ "audits[largest-contentful-paint-element].details.items[0].headings[0].label", "audits[lcp-lazy-loaded].details.headings[0].label", - "audits[dom-size].details.headings[1].label" + "audits[dom-size].details.headings[1].label", + "audits[dom-size-insight].details.headings[1].label" ], "core/audits/largest-contentful-paint-element.js | columnPhase": [ "audits[largest-contentful-paint-element].details.items[1].headings[0].label" @@ -26104,6 +27301,105 @@ "core/audits/bf-cache.js | description": [ "audits[bf-cache].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": [ + "audits[interaction-to-next-paint-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | description": [ + "audits[interaction-to-next-paint-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | title": [ + "audits[lcp-phases-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | description": [ + "audits[lcp-phases-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ + "audits[long-critical-network-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | description": [ + "audits[long-critical-network-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | title": [ + "audits[slow-css-selector-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | description": [ + "audits[slow-css-selector-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], "core/config/default-config.js | performanceCategoryTitle": [ "categories.performance.title" ], @@ -26131,6 +27427,9 @@ "core/config/default-config.js | metricGroupTitle": [ "categoryGroups.metrics.title" ], + "core/config/default-config.js | insightGroupTitle": [ + "categoryGroups.insights.title" + ], "core/config/default-config.js | diagnosticsGroupTitle": [ "categoryGroups.diagnostics.title" ], diff --git a/core/test/gather/gatherers/trace-elements-test.js b/core/test/gather/gatherers/trace-elements-test.js index 2c95398abba2..cb914b1866b2 100644 --- a/core/test/gather/gatherers/trace-elements-test.js +++ b/core/test/gather/gatherers/trace-elements-test.js @@ -218,15 +218,18 @@ describe('Trace Elements gatherer - Animated Elements', () => { const driver = createMockDriver(); driver._session.sendCommand - // nodeId: 6 - .mockResponse('DOM.resolveNode', {object: {objectId: 1}}) - .mockResponse('Runtime.callFunctionOn', {result: {value: LCPNodeData}}) - // nodeId: 4 - .mockResponse('DOM.resolveNode', {object: {objectId: 2}}) + // Trace engine 1 / Shifts 1 + .mockResponse('DOM.resolveNode', {object: {objectId: 4}}) .mockResponse('Runtime.callFunctionOn', {result: {value: layoutShiftNodeData}}) - // nodeId: 5 - .mockResponse('DOM.resolveNode', {object: {objectId: 3}}) - .mockResponse('Runtime.callFunctionOn', {result: {value: animationNodeData}}); + // Trace engine 2 + .mockResponse('DOM.resolveNode', {object: {objectId: 7}}) + .mockResponse('Runtime.callFunctionOn', {result: {value: null}}) + // Trace engine 3 / Animations 1 + .mockResponse('DOM.resolveNode', {object: {objectId: 5}}) + .mockResponse('Runtime.callFunctionOn', {result: {value: animationNodeData}}) + // LCP 1 + .mockResponse('DOM.resolveNode', {object: {objectId: 5}}) + .mockResponse('Runtime.callFunctionOn', {result: {value: LCPNodeData}}); const trace = createTestTrace({timeOrigin: 0, traceEnd: 2000}); trace.traceEvents.push( @@ -262,18 +265,28 @@ describe('Trace Elements gatherer - Animated Elements', () => { expect(sorted).toEqual([ { - traceEventType: 'largest-contentful-paint', + ...layoutShiftNodeData, + traceEventType: 'trace-engine', + nodeId: 4, + }, + { + ...animationNodeData, + traceEventType: 'trace-engine', + nodeId: 5, + }, + { ...LCPNodeData, + traceEventType: 'largest-contentful-paint', nodeId: 6, }, { - traceEventType: 'layout-shift', ...layoutShiftNodeData, + traceEventType: 'layout-shift', nodeId: 4, }, { - traceEventType: 'animation', ...animationNodeData, + traceEventType: 'animation', animations: [ {name: 'example', failureReasonsMask: 8192, unsupportedProperties: ['height']}, ], @@ -360,7 +373,6 @@ describe('Trace Elements gatherer - Animated Elements', () => { }, }; const LCPNodeData = { - traceEventType: 'largest-contentful-paint', devtoolsNodePath: '1,HTML,1,BODY,1,DIV', selector: 'body > div#lcp', nodeLabel: 'div', @@ -377,15 +389,18 @@ describe('Trace Elements gatherer - Animated Elements', () => { }; const driver = createMockDriver(); driver._session.sendCommand - .mockResponse('DOM.resolveNode', {object: {objectId: 1}}) - .mockResponse('Runtime.callFunctionOn', {result: {value: LCPNodeData}}) - // Animation 1 + // Trace engine 1 / Animation 1 .mockResponse('DOM.resolveNode', () => { throw Error(); }) + // Trace engine 2 / Animation 2 + .mockResponse('DOM.resolveNode', {object: {objectId: 6}}) + .mockResponse('Runtime.callFunctionOn', {result: {value: animationNodeData}}) + // LCP 1 + .mockResponse('DOM.resolveNode', {object: {objectId: 7}}) + .mockResponse('Runtime.callFunctionOn', {result: {value: LCPNodeData}}) // Animation 2 - .mockResponse('DOM.resolveNode', {object: {objectId: 5}}) - .mockResponse('Runtime.callFunctionOn', {result: {value: animationNodeData}}); + .mockResponse('DOM.resolveNode', {object: {objectId: 6}}); const trace = createTestTrace({timeOrigin: 0, traceEnd: 2000}); trace.traceEvents.push(makeAnimationTraceEvent('0x363db876c8', 'b', {id: '1', nodeId: 5})); @@ -411,12 +426,19 @@ describe('Trace Elements gatherer - Animated Elements', () => { }); expect(result).toEqual([ + { + ...animationNodeData, + traceEventType: 'trace-engine', + nodeId: 6, + }, { ...LCPNodeData, + traceEventType: 'largest-contentful-paint', nodeId: 7, }, { ...animationNodeData, + traceEventType: 'animation', animations: [ {name: 'example', failureReasonsMask: 8192, unsupportedProperties: ['color']}, ], @@ -425,10 +447,8 @@ describe('Trace Elements gatherer - Animated Elements', () => { ]); }); - it('properly handles timespans without FCP', async () => { const animationNodeData = { - traceEventType: 'animation', devtoolsNodePath: '1,HTML,1,BODY,1,DIV', selector: 'body > div#animated', nodeLabel: 'div', @@ -444,6 +464,9 @@ describe('Trace Elements gatherer - Animated Elements', () => { }; const driver = createMockDriver(); driver._session.sendCommand + // Trace engine 1 (happens to be same node as Animation 1) + .mockResponse('DOM.resolveNode', {object: {objectId: 5}}) + .mockResponse('Runtime.callFunctionOn', {result: {value: animationNodeData}}) // Animation 1 .mockResponse('DOM.resolveNode', {object: {objectId: 5}}) .mockResponse('Runtime.callFunctionOn', {result: {value: animationNodeData}}); @@ -469,6 +492,13 @@ describe('Trace Elements gatherer - Animated Elements', () => { expect(result).toEqual([ { ...animationNodeData, + traceEventType: 'trace-engine', + animations: undefined, + nodeId: 5, + }, + { + ...animationNodeData, + traceEventType: 'animation', animations: [ {name: 'example', failureReasonsMask: 8192, unsupportedProperties: ['height']}, ], diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index c961fe87e2ce..16341fee53c9 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -5830,6 +5830,236 @@ ] }, "guidanceLevel": 4 + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 153 + } + }, + { + "statistic": "Most children", + "value": { + "type": "numeric", + "granularity": 1, + "value": 100 + } + }, + { + "statistic": "DOM depth", + "value": { + "type": "numeric", + "granularity": 1, + "value": 4 + } + } + ] + }, + "guidanceLevel": 3 + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "Many APIs, typically reading layout geometry, force the rendering engine to pause script execution in order to calculate the style and layout. Learn more about [forced reflow](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and its mitigations.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "interaction-to-next-paint-insight": { + "id": "interaction-to-next-paint-insight", + "title": "INP by phase", + "description": "Start investigating with the longest phase. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "lcp-phases-insight": { + "id": "lcp-phases-insight", + "title": "LCP by phase", + "description": "Each [phase has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "long-critical-network-tree-insight": { + "id": "long-critical-network-tree-insight", + "title": "Long critical network tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources/) can move these network requests out of the critical path.", + "score": 0, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 3500, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Potential Savings" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/fcp-delayer.js?delay=5000", + "totalBytes": 0, + "wastedMs": 581 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&capped", + "totalBytes": 665, + "wastedMs": 581 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", + "totalBytes": 665, + "wastedMs": 581 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.js", + "totalBytes": 486, + "wastedMs": 581 + }, + { + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200", + "totalBytes": 0, + "wastedMs": 581 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", + "totalBytes": 665, + "wastedMs": 581 + } + ] + }, + "guidanceLevel": 3 + }, + "slow-css-selector-insight": { + "id": "slow-css-selector-insight", + "title": "CSS Selector costs", + "description": "If Recalculate Style costs remain high, selector optimization can reduce them. [Optimize the selectors](https://developer.chrome.com/docs/devtools/performance/selector-stats) with both high elapsed time and high slow-path %. Simpler selectors, fewer selectors, a smaller DOM, and a shallower DOM will all reduce matching costs.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "Third parties", + "description": "Third party code can significantly impact load performance. [Reduce and defer loading of third party code](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/) to prioritize your page's content.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3 + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 0, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "INP": 300 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + {} + ] + }, + "guidanceLevel": 3 } }, "configSettings": { @@ -5924,6 +6154,76 @@ "group": "metrics", "acronym": "SI" }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interaction-to-next-paint-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-phases-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "long-critical-network-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "slow-css-selector-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, { "id": "interactive", "weight": 0, @@ -6742,6 +7042,9 @@ "metrics": { "title": "Metrics" }, + "insights": { + "title": "Insights" + }, "diagnostics": { "title": "Diagnostics", "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." @@ -8885,6 +9188,90 @@ "duration": 100, "entryType": "measure" }, + { + "startTime": 0, + "name": "lh:audit:cls-culprits-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:document-latency-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:dom-size-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:font-display-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:forced-reflow-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:image-delivery-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:interaction-to-next-paint-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:lcp-discovery-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:lcp-phases-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:long-critical-network-tree-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:render-blocking-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:slow-css-selector-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:third-parties-insight", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:audit:viewport-insight", + "duration": 100, + "entryType": "measure" + }, { "startTime": 0, "name": "lh:runner:generate", @@ -9158,7 +9545,8 @@ "audits[uses-text-compression].details.headings[0].label", "audits[uses-responsive-images].details.headings[1].label", "audits[efficient-animated-content].details.headings[0].label", - "audits[legacy-javascript].details.headings[0].label" + "audits[legacy-javascript].details.headings[0].label", + "audits[render-blocking-insight].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", @@ -9337,7 +9725,8 @@ "audits[render-blocking-resources].details.headings[1].label", "audits[unminified-javascript].details.headings[1].label", "audits[unused-javascript].details.headings[1].label", - "audits[uses-text-compression].details.headings[1].label" + "audits[uses-text-compression].details.headings[1].label", + "audits[render-blocking-insight].details.headings[1].label" ], "core/lib/i18n/i18n.js | totalResourceType": [ "audits[resource-summary].details.items[0].label" @@ -9402,7 +9791,8 @@ "audits[largest-contentful-paint-element].details.items[0].headings[0].label", "audits[layout-shifts].details.headings[0].label", "audits[non-composited-animations].details.headings[0].label", - "audits[dom-size].details.headings[1].label" + "audits[dom-size].details.headings[1].label", + "audits[dom-size-insight].details.headings[1].label" ], "core/audits/largest-contentful-paint-element.js | columnPhase": [ "audits[largest-contentful-paint-element].details.items[1].headings[0].label" @@ -9529,7 +9919,8 @@ "audits[uses-text-compression].details.headings[2].label", "audits[uses-responsive-images].details.headings[3].label", "audits[efficient-animated-content].details.headings[2].label", - "audits[legacy-javascript].details.headings[2].label" + "audits[legacy-javascript].details.headings[2].label", + "audits[render-blocking-insight].details.headings[2].label" ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" @@ -10340,6 +10731,105 @@ "core/audits/bf-cache.js | actionableFailureType": [ "audits[bf-cache].details.items[0].failureType" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": [ + "audits[interaction-to-next-paint-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | description": [ + "audits[interaction-to-next-paint-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | title": [ + "audits[lcp-phases-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | description": [ + "audits[lcp-phases-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ + "audits[long-critical-network-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | description": [ + "audits[long-critical-network-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | title": [ + "audits[slow-css-selector-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | description": [ + "audits[slow-css-selector-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], "core/config/default-config.js | performanceCategoryTitle": [ "categories.performance.title" ], @@ -10367,6 +10857,9 @@ "core/config/default-config.js | metricGroupTitle": [ "categoryGroups.metrics.title" ], + "core/config/default-config.js | insightGroupTitle": [ + "categoryGroups.insights.title" + ], "core/config/default-config.js | diagnosticsGroupTitle": [ "categoryGroups.diagnostics.title" ], diff --git a/core/test/scenarios/__snapshots__/api-test-pptr.js.snap b/core/test/scenarios/__snapshots__/api-test-pptr.js.snap index 57f14703f503..e0f34756bf51 100644 --- a/core/test/scenarios/__snapshots__/api-test-pptr.js.snap +++ b/core/test/scenarios/__snapshots__/api-test-pptr.js.snap @@ -32,6 +32,7 @@ Array [ "canonical", "charset", "clickjacking-mitigation", + "cls-culprits-insight", "color-contrast", "crawlable-anchors", "critical-request-chains", @@ -44,8 +45,10 @@ Array [ "diagnostics", "dlitem", "doctype", + "document-latency-insight", "document-title", "dom-size", + "dom-size-insight", "duplicate-id-aria", "duplicated-javascript", "efficient-animated-content", @@ -57,7 +60,9 @@ Array [ "focus-traps", "focusable-controls", "font-display", + "font-display-insight", "font-size", + "forced-reflow-insight", "form-field-multiple-labels", "frame-title", "geolocation-on-start", @@ -71,11 +76,13 @@ Array [ "identical-links-same-purpose", "image-alt", "image-aspect-ratio", + "image-delivery-insight", "image-redundant-alt", "image-size-responsive", "input-button-name", "input-image-alt", "inspector-issues", + "interaction-to-next-paint-insight", "interactive", "interactive-element-affordance", "is-crawlable", @@ -87,7 +94,9 @@ Array [ "largest-contentful-paint", "largest-contentful-paint-element", "layout-shifts", + "lcp-discovery-insight", "lcp-lazy-loaded", + "lcp-phases-insight", "legacy-javascript", "link-in-text-block", "link-name", @@ -95,6 +104,7 @@ Array [ "list", "listitem", "logical-tab-order", + "long-critical-network-tree-insight", "long-tasks", "main-thread-tasks", "mainthread-work-breakdown", @@ -119,6 +129,7 @@ Array [ "prioritize-lcp-image", "redirects", "redirects-http", + "render-blocking-insight", "render-blocking-resources", "resource-summary", "robots-txt", @@ -127,6 +138,7 @@ Array [ "select-name", "server-response-time", "skip-link", + "slow-css-selector-insight", "speed-index", "structured-data", "tabindex", @@ -136,6 +148,7 @@ Array [ "td-has-header", "td-headers-attr", "th-has-data-cells", + "third-parties-insight", "third-party-cookies", "third-party-facades", "third-party-summary", @@ -159,6 +172,7 @@ Array [ "valid-source-maps", "video-caption", "viewport", + "viewport-insight", "visual-order-follows-dom", ] `; @@ -195,6 +209,7 @@ Array [ "canonical", "charset", "clickjacking-mitigation", + "cls-culprits-insight", "color-contrast", "crawlable-anchors", "critical-request-chains", @@ -207,8 +222,10 @@ Array [ "diagnostics", "dlitem", "doctype", + "document-latency-insight", "document-title", "dom-size", + "dom-size-insight", "duplicate-id-aria", "duplicated-javascript", "efficient-animated-content", @@ -220,7 +237,9 @@ Array [ "focus-traps", "focusable-controls", "font-display", + "font-display-insight", "font-size", + "forced-reflow-insight", "form-field-multiple-labels", "frame-title", "geolocation-on-start", @@ -234,11 +253,13 @@ Array [ "identical-links-same-purpose", "image-alt", "image-aspect-ratio", + "image-delivery-insight", "image-redundant-alt", "image-size-responsive", "input-button-name", "input-image-alt", "inspector-issues", + "interaction-to-next-paint-insight", "interactive", "interactive-element-affordance", "is-crawlable", @@ -250,7 +271,9 @@ Array [ "largest-contentful-paint", "largest-contentful-paint-element", "layout-shifts", + "lcp-discovery-insight", "lcp-lazy-loaded", + "lcp-phases-insight", "legacy-javascript", "link-in-text-block", "link-name", @@ -258,6 +281,7 @@ Array [ "list", "listitem", "logical-tab-order", + "long-critical-network-tree-insight", "long-tasks", "main-thread-tasks", "mainthread-work-breakdown", @@ -282,6 +306,7 @@ Array [ "prioritize-lcp-image", "redirects", "redirects-http", + "render-blocking-insight", "render-blocking-resources", "resource-summary", "robots-txt", @@ -290,6 +315,7 @@ Array [ "select-name", "server-response-time", "skip-link", + "slow-css-selector-insight", "speed-index", "structured-data", "tabindex", @@ -299,6 +325,7 @@ Array [ "td-has-header", "td-headers-attr", "th-has-data-cells", + "third-parties-insight", "third-party-cookies", "third-party-facades", "third-party-summary", @@ -322,6 +349,7 @@ Array [ "valid-source-maps", "video-caption", "viewport", + "viewport-insight", "visual-order-follows-dom", ] `; @@ -423,19 +451,29 @@ exports[`Individual modes API startTimespan should compute ConsoleMessage result Array [ "bf-cache", "bootup-time", + "cls-culprits-insight", "cumulative-layout-shift", "deprecations", + "document-latency-insight", + "dom-size-insight", "duplicated-javascript", "efficient-animated-content", "errors-in-console", "final-screenshot", + "font-display-insight", + "forced-reflow-insight", "image-aspect-ratio", + "image-delivery-insight", "image-size-responsive", "inspector-issues", "interaction-to-next-paint", + "interaction-to-next-paint-insight", "is-on-https", "layout-shifts", + "lcp-discovery-insight", + "lcp-phases-insight", "legacy-javascript", + "long-critical-network-tree-insight", "long-tasks", "main-thread-tasks", "mainthread-work-breakdown", @@ -445,9 +483,12 @@ Array [ "network-server-latency", "no-document-write", "non-composited-animations", + "render-blocking-insight", "resource-summary", "screenshot-thumbnails", "script-treemap-data", + "slow-css-selector-insight", + "third-parties-insight", "third-party-cookies", "third-party-summary", "total-blocking-time", @@ -464,14 +505,28 @@ Array [ "uses-responsive-images", "uses-text-compression", "valid-source-maps", + "viewport-insight", "work-during-interaction", ] `; exports[`Individual modes API startTimespan should compute ConsoleMessage results across a span of time 2`] = ` Array [ + "cls-culprits-insight", + "document-latency-insight", + "dom-size-insight", + "font-display-insight", + "forced-reflow-insight", + "image-delivery-insight", + "interaction-to-next-paint-insight", "layout-shifts", + "lcp-discovery-insight", + "lcp-phases-insight", + "long-critical-network-tree-insight", "non-composited-animations", + "render-blocking-insight", + "slow-css-selector-insight", + "third-parties-insight", "third-party-summary", "user-timings", ] @@ -481,19 +536,29 @@ exports[`Individual modes API startTimespan should compute results from timespan Array [ "bf-cache", "bootup-time", + "cls-culprits-insight", "cumulative-layout-shift", "deprecations", + "document-latency-insight", + "dom-size-insight", "duplicated-javascript", "efficient-animated-content", "errors-in-console", "final-screenshot", + "font-display-insight", + "forced-reflow-insight", "image-aspect-ratio", + "image-delivery-insight", "image-size-responsive", "inspector-issues", "interaction-to-next-paint", + "interaction-to-next-paint-insight", "is-on-https", "layout-shifts", + "lcp-discovery-insight", + "lcp-phases-insight", "legacy-javascript", + "long-critical-network-tree-insight", "long-tasks", "main-thread-tasks", "mainthread-work-breakdown", @@ -503,9 +568,12 @@ Array [ "network-server-latency", "no-document-write", "non-composited-animations", + "render-blocking-insight", "resource-summary", "screenshot-thumbnails", "script-treemap-data", + "slow-css-selector-insight", + "third-parties-insight", "third-party-cookies", "third-party-summary", "total-blocking-time", @@ -522,6 +590,7 @@ Array [ "uses-responsive-images", "uses-text-compression", "valid-source-maps", + "viewport-insight", "work-during-interaction", ] `; @@ -529,14 +598,26 @@ Array [ exports[`Individual modes API startTimespan should compute results from timespan after page load 2`] = ` Array [ "bootup-time", + "cls-culprits-insight", + "document-latency-insight", "duplicated-javascript", "efficient-animated-content", + "font-display-insight", + "forced-reflow-insight", + "image-delivery-insight", + "interaction-to-next-paint-insight", "layout-shifts", + "lcp-discovery-insight", + "lcp-phases-insight", "legacy-javascript", + "long-critical-network-tree-insight", "modern-image-formats", "network-rtt", "network-server-latency", "non-composited-animations", + "render-blocking-insight", + "slow-css-selector-insight", + "third-parties-insight", "third-party-summary", "unminified-css", "unminified-javascript", diff --git a/core/test/scenarios/api-test-pptr.js b/core/test/scenarios/api-test-pptr.js index e9e48d78a163..1a5c75d4fb08 100644 --- a/core/test/scenarios/api-test-pptr.js +++ b/core/test/scenarios/api-test-pptr.js @@ -104,10 +104,16 @@ describe('Individual modes API', function() { } = getAuditsBreakdown(lhr); expect(auditResults.map(audit => audit.id).sort()).toMatchSnapshot(); - expect(notApplicableAudits.map(audit => audit.id).sort()).toMatchSnapshot(); + expect( + notApplicableAudits + // TODO(16323): Flaky in CI. + .filter(audit => audit.id !== 'viewport-insight') + .map(audit => audit.id) + .sort() + ).toMatchSnapshot(); expect(notApplicableAudits.map(audit => audit.id)).not.toContain('total-blocking-time'); - expect(erroredAudits).toHaveLength(0); + expect(erroredAudits).toStrictEqual([]); expect(failedAudits.map(audit => audit.id)).toContain('errors-in-console'); const errorsInConsole = lhr.audits['errors-in-console']; diff --git a/package.json b/package.json index 8ef0d23c036f..fcfcece52961 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,8 @@ "serve-gh-pages": "cd dist/gh-pages && python3 -m http.server 7333", "serve-treemap": "yarn serve-gh-pages", "serve-viewer": "yarn serve-gh-pages", - "flow-report": "yarn build-report --flow && node ./core/scripts/build-test-flow-report.js" + "flow-report": "yarn build-report --flow && node ./core/scripts/build-test-flow-report.js", + "generate-insight-audits": "node core/scripts/generate-insight-audits.js" }, "devDependencies": { "@build-tracker/cli": "^1.0.0-beta.15", @@ -181,7 +182,7 @@ "webtreemap-cdt": "^3.2.1" }, "dependencies": { - "@paulirish/trace_engine": "0.0.40", + "@paulirish/trace_engine": "0.0.43", "@sentry/node": "^7.0.0", "axe-core": "^4.10.2", "chrome-launcher": "^1.1.2", diff --git a/report/renderer/performance-category-renderer.js b/report/renderer/performance-category-renderer.js index a43c10e4fbf1..881a9aa3b13c 100644 --- a/report/renderer/performance-category-renderer.js +++ b/report/renderer/performance-category-renderer.js @@ -201,7 +201,23 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { filmstripEl && timelineEl.append(filmstripEl); } - const allInsights = category.auditRefs + // Insights + const insightAudits = category.auditRefs.filter(audit => audit.group === 'insights'); + if (insightAudits.length) { + const [insightsGroupEl, insightsFooterEl] = this.renderAuditGroup(groups['insights']); + insightsGroupEl.classList.add('lh-audit-group--insights'); + for (const audit of category.auditRefs.filter(audit => audit.group === 'insights')) { + const auditEl = this.renderAudit(audit); + insightsGroupEl.append(auditEl); + } + element.append(insightsGroupEl); + if (insightsFooterEl) { + element.append(insightsFooterEl); + } + } + + // Diagnostics + const allDiagnostics = category.auditRefs .filter(audit => audit.group === 'diagnostics') .map(auditRef => { const {overallImpact, overallLinearImpact} = this.overallImpact(auditRef, metricAudits); @@ -211,21 +227,20 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { return {auditRef, auditEl, overallImpact, overallLinearImpact, guidanceLevel}; }); - // Diagnostics - const diagnosticAudits = allInsights + const diagnosticAudits = allDiagnostics .filter(audit => !ReportUtils.showAsPassed(audit.auditRef.result)); - const passedAudits = allInsights + const passedAudits = allDiagnostics .filter(audit => ReportUtils.showAsPassed(audit.auditRef.result)); - const [groupEl, footerEl] = this.renderAuditGroup(groups['diagnostics']); - groupEl.classList.add('lh-audit-group--diagnostics'); + const [diagnosticsGroupEl, diagnosticsFooterEl] = this.renderAuditGroup(groups['diagnostics']); + diagnosticsGroupEl.classList.add('lh-audit-group--diagnostics'); /** * @param {string} acronym */ function refreshFilteredAudits(acronym) { - for (const audit of allInsights) { + for (const audit of allDiagnostics) { if (acronym === 'All') { audit.auditEl.hidden = false; } else { @@ -267,7 +282,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { }); for (const audit of diagnosticAudits) { - groupEl.insertBefore(audit.auditEl, footerEl); + diagnosticsGroupEl.insertBefore(audit.auditEl, diagnosticsFooterEl); } } @@ -291,7 +306,7 @@ export class PerformanceCategoryRenderer extends CategoryRenderer { refreshFilteredAudits('All'); if (diagnosticAudits.length) { - element.append(groupEl); + element.append(diagnosticsGroupEl); } if (!passedAudits.length) return element; diff --git a/shared/localization/locales/en-US.json b/shared/localization/locales/en-US.json index cf091c798838..b0047cad4fca 100644 --- a/shared/localization/locales/en-US.json +++ b/shared/localization/locales/en-US.json @@ -1673,6 +1673,9 @@ "core/config/default-config.js | firstPaintImprovementsGroupTitle": { "message": "First Paint Improvements" }, + "core/config/default-config.js | insightGroupTitle": { + "message": "Insights" + }, "core/config/default-config.js | metricGroupTitle": { "message": "Metrics" }, @@ -2681,33 +2684,138 @@ "flow-report/src/i18n/ui-strings.js | title": { "message": "Lighthouse User Flow Report" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | animation": { + "message": "Animation" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": { "message": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | fontRequest": { + "message": "Font request" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | injectedIframe": { + "message": "Injected iframe" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | layoutShiftCluster": { + "message": "Layout shift cluster @ {PH1}" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | noCulprits": { + "message": "Could not detect any layout shift culprits" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | noLayoutShifts": { + "message": "No layout shifts" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": { "message": "Layout shift culprits" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | topCulprits": { + "message": "Top layout shift culprits" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | unsizedImages": { + "message": "Unsized Images" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | worstCluster": { + "message": "Worst cluster" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | worstLayoutShiftCluster": { + "message": "Worst layout shift cluster" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": { "message": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/)." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | element": { + "message": "Element" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": { + "message": "Most children" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": { + "message": "DOM depth" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": { + "message": "Statistic" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": { "message": "Optimize DOM size" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": { + "message": "Total elements" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": { + "message": "Value" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": { "message": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedRedirects": { + "message": "Had redirects" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedServerResponseTime": { + "message": "Server responded slowly" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedTextCompression": { + "message": "No compression applied" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": { + "message": "Avoids redirects" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": { + "message": "Server responds quickly" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingTextCompression": { + "message": "Applies text compression" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | redirectsLabel": { + "message": "Redirects" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | serverResponseTimeLabel": { + "message": "Server response time" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": { "message": "Document request latency" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | uncompressedDownload": { + "message": "Uncompressed download" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": { "message": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks)." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | fontColumn": { + "message": "Font" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": { "message": "Font display" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | wastedTimeColumn": { + "message": "Wasted time" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": { + "message": "Many APIs, typically reading layout geometry, force the rendering engine to pause script execution in order to calculate the style and layout. Learn more about [forced reflow](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and its mitigations." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | relatedStackTrace": { + "message": "Stack trace" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": { + "message": "Forced reflow" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | topTimeConsumingFunctionCall": { + "message": "Top function call" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | totalReflowTime": { + "message": "Total reflow time" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": { "message": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | noOptimizableImages": { + "message": "No optimizable images" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | optimizeFile": { + "message": "Optimize file size" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | others": { + "message": "{PH1} others" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": { "message": "Improve image delivery" }, @@ -2726,36 +2834,141 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | description": { "message": "Start investigating with the longest phase. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | duration": { + "message": "Duration" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | inputDelay": { + "message": "Input delay" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | noInteractions": { + "message": "No interactions detected" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | phase": { + "message": "Phase" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | presentationDelay": { + "message": "Presentation delay" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | processingDuration": { + "message": "Processing duration" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": { "message": "INP by phase" }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": { "message": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | fetchPriorityApplied": { + "message": "fetchpriority=high applied" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": { + "message": "lazy load not applied" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lcpLoadDelay": { + "message": "LCP image loaded {PH1} after earliest start point." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | noLcp": { + "message": "No LCP detected" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | noLcpResource": { + "message": "No LCP resource detected because the LCP is not an image" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | requestDiscoverable": { + "message": "Request is discoverable in initial document" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": { "message": "LCP request discovery" }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | description": { "message": "Each [phase has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | elementRenderDelay": { + "message": "Element render delay" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | noLcp": { + "message": "No LCP detected" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | percentLCP": { + "message": "% of LCP" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | phase": { + "message": "Phase" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDelay": { + "message": "Resource load delay" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDuration": { + "message": "Resource load duration" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | timeToFirstByte": { + "message": "Time to first byte" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | title": { "message": "LCP by phase" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | description": { + "message": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | noLongCriticalNetworkTree": { + "message": "No rendering tasks impacted by long critical network tree" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": { + "message": "Long critical network tree" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": { "message": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources/) can move these network requests out of the critical path." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | duration": { + "message": "Duration" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | noRenderBlocking": { + "message": "No render blocking requests for this navigation" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | renderBlockingRequest": { + "message": "Request" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": { "message": "Render blocking requests" }, "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | description": { "message": "If Recalculate Style costs remain high, selector optimization can reduce them. [Optimize the selectors](https://developer.chrome.com/docs/devtools/performance/selector-stats) with both high elapsed time and high slow-path %. Simpler selectors, fewer selectors, a smaller DOM, and a shallower DOM will all reduce matching costs." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | elapsed": { + "message": "Elapsed time" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | enableSelectorData": { + "message": "No CSS selector data was found. CSS selector stats need to be enabled in the performance panel settings." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | matchAttempts": { + "message": "Match attempts" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | matchCount": { + "message": "Match count" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | title": { "message": "CSS Selector costs" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | topSelectors": { + "message": "Top selectors" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | total": { + "message": "Total" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnBlockingTime": { + "message": "Blocking time" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": { + "message": "Third party" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": { + "message": "Transfer size" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": { "message": "Third party code can significantly impact load performance. [Reduce and defer loading of third party code](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/) to prioritize your page's content." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | noThirdParties": { + "message": "No third parties found" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": { "message": "Third parties" }, diff --git a/shared/localization/locales/en-XL.json b/shared/localization/locales/en-XL.json index faccdfb1217a..77b038094243 100644 --- a/shared/localization/locales/en-XL.json +++ b/shared/localization/locales/en-XL.json @@ -1673,6 +1673,9 @@ "core/config/default-config.js | firstPaintImprovementsGroupTitle": { "message": "F̂ír̂śt̂ Ṕâín̂t́ Îḿp̂ŕôv́êḿêńt̂ś" }, + "core/config/default-config.js | insightGroupTitle": { + "message": "Îńŝíĝh́t̂ś" + }, "core/config/default-config.js | metricGroupTitle": { "message": "M̂ét̂ŕîćŝ" }, @@ -2681,33 +2684,138 @@ "flow-report/src/i18n/ui-strings.js | title": { "message": "L̂íĝh́t̂h́ôúŝé Ûśêŕ F̂ĺôẃ R̂ép̂ór̂t́" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | animation": { + "message": "Âńîḿât́îón̂" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": { "message": "L̂áŷóût́ ŝh́îf́t̂ś ôćĉúr̂ ẃĥén̂ él̂ém̂én̂t́ŝ ḿôv́ê áb̂śêńt̂ án̂ý ûśêŕ îńt̂ér̂áĉt́îón̂. [Ín̂v́êśt̂íĝát̂é t̂h́ê ćâúŝéŝ óf̂ ĺâýôút̂ śĥíf̂t́ŝ](https://web.dev/articles/optimize-cls), śûćĥ áŝ él̂ém̂én̂t́ŝ b́êín̂ǵ âd́d̂éd̂, ŕêḿôv́êd́, ôŕ t̂h́êír̂ f́ôńt̂ś ĉh́âńĝín̂ǵ âś t̂h́ê ṕâǵê ĺôád̂ś." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | fontRequest": { + "message": "F̂ón̂t́ r̂éq̂úêśt̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | injectedIframe": { + "message": "Îńĵéĉt́êd́ îf́r̂ám̂é" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | layoutShiftCluster": { + "message": "L̂áŷóût́ ŝh́îf́t̂ ćl̂úŝt́êŕ @ {PH1}" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | noCulprits": { + "message": "Ĉóûĺd̂ ńôt́ d̂ét̂éĉt́ âńŷ ĺâýôút̂ śĥíf̂t́ ĉúl̂ṕr̂ít̂ś" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | noLayoutShifts": { + "message": "N̂ó l̂áŷóût́ ŝh́îf́t̂ś" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": { "message": "L̂áŷóût́ ŝh́îf́t̂ ćûĺp̂ŕît́ŝ" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | topCulprits": { + "message": "T̂óp̂ ĺâýôút̂ śĥíf̂t́ ĉúl̂ṕr̂ít̂ś" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | unsizedImages": { + "message": "Ûńŝíẑéd̂ Ím̂áĝéŝ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | worstCluster": { + "message": "Ŵór̂śt̂ ćl̂úŝt́êŕ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | worstLayoutShiftCluster": { + "message": "Ŵór̂śt̂ ĺâýôút̂ śĥíf̂t́ ĉĺûśt̂ér̂" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": { "message": "Â ĺâŕĝé D̂ÓM̂ ćâń îńĉŕêáŝé t̂h́ê d́ûŕât́îón̂ óf̂ śt̂ýl̂é ĉál̂ćûĺât́îón̂ś âńd̂ ĺâýôút̂ ŕêf́l̂óŵś, îḿp̂áĉt́îńĝ ṕâǵê ŕêśp̂ón̂śîv́êńêśŝ. Á l̂ár̂ǵê D́ÔḾ ŵíl̂ĺ âĺŝó îńĉŕêáŝé m̂ém̂ór̂ý ûśâǵê. [Ĺêár̂ń ĥóŵ t́ô áv̂óîd́ âń êx́ĉéŝśîv́ê D́ÔḾ ŝíẑé](https://developer.chrome.com/docs/lighthouse/performance/dom-size/)." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | element": { + "message": "Êĺêḿêńt̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": { + "message": "M̂óŝt́ ĉh́îĺd̂ŕêń" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": { + "message": "D̂ÓM̂ d́êṕt̂h́" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": { + "message": "Ŝt́ât́îśt̂íĉ" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": { "message": "Ôṕt̂ím̂íẑé D̂ÓM̂ śîźê" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": { + "message": "T̂ót̂ál̂ él̂ém̂én̂t́ŝ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": { + "message": "V̂ál̂úê" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": { "message": "Ŷóûŕ f̂ír̂śt̂ ńêt́ŵór̂ḱ r̂éq̂úêśt̂ íŝ t́ĥé m̂óŝt́ îḿp̂ór̂t́âńt̂. Ŕêd́ûćê ít̂ś l̂át̂én̂ćŷ b́ŷ áv̂óîd́îńĝ ŕêd́îŕêćt̂ś, êńŝúr̂ín̂ǵ â f́âśt̂ śêŕv̂ér̂ ŕêśp̂ón̂śê, án̂d́ êńâb́l̂ín̂ǵ t̂éx̂t́ ĉóm̂ṕr̂éŝśîón̂." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedRedirects": { + "message": "Ĥád̂ ŕêd́îŕêćt̂ś" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedServerResponseTime": { + "message": "Ŝér̂v́êŕ r̂éŝṕôńd̂éd̂ śl̂óŵĺŷ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedTextCompression": { + "message": "N̂ó ĉóm̂ṕr̂éŝśîón̂ áp̂ṕl̂íêd́" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": { + "message": "Âv́ôíd̂ś r̂éd̂ír̂éĉt́ŝ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": { + "message": "Ŝér̂v́êŕ r̂éŝṕôńd̂ś q̂úîćk̂ĺŷ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingTextCompression": { + "message": "Âṕp̂ĺîéŝ t́êx́t̂ ćôḿp̂ŕêśŝíôń" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | redirectsLabel": { + "message": "R̂éd̂ír̂éĉt́ŝ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | serverResponseTimeLabel": { + "message": "Ŝér̂v́êŕ r̂éŝṕôńŝé t̂ím̂é" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": { "message": "D̂óĉúm̂én̂t́ r̂éq̂úêśt̂ ĺât́êńĉý" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | uncompressedDownload": { + "message": "Ûńĉóm̂ṕr̂éŝśêd́ d̂óŵńl̂óâd́" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": { "message": "Ĉón̂śîd́êŕ ŝét̂t́îńĝ [font-display](https://developer.chrome.com/blog/font-display) t́ô swap ór̂ optional t́ô én̂śûŕê t́êx́t̂ íŝ ćôńŝíŝt́êńt̂ĺŷ v́îśîb́l̂é. swap ĉán̂ b́ê f́ûŕt̂h́êŕ ôṕt̂ím̂íẑéd̂ t́ô ḿît́îǵât́ê ĺâýôút̂ śĥíf̂t́ŝ ẃît́ĥ [f́ôńt̂ ḿêt́r̂íĉ óv̂ér̂ŕîd́êś](https://developer.chrome.com/blog/font-fallbacks)." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | fontColumn": { + "message": "F̂ón̂t́" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": { "message": "F̂ón̂t́ d̂íŝṕl̂áŷ" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | wastedTimeColumn": { + "message": "Ŵáŝt́êd́ t̂ím̂é" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": { + "message": "M̂án̂ý ÂṔÎś, t̂ýp̂íĉál̂ĺŷ ŕêád̂ín̂ǵ l̂áŷóût́ ĝéôḿêt́r̂ý, f̂ór̂ćê t́ĥé r̂én̂d́êŕîńĝ én̂ǵîńê t́ô ṕâúŝé ŝćr̂íp̂t́ êx́êćût́îón̂ ín̂ ór̂d́êŕ t̂ó ĉál̂ćûĺât́ê t́ĥé ŝt́ŷĺê án̂d́ l̂áŷóût́. L̂éâŕn̂ ḿôŕê áb̂óût́ [f̂ór̂ćêd́ r̂éf̂ĺôẃ](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) âńd̂ ít̂ś m̂ít̂íĝát̂íôńŝ." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | relatedStackTrace": { + "message": "Ŝt́âćk̂ t́r̂áĉé" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": { + "message": "F̂ór̂ćêd́ r̂éf̂ĺôẃ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | topTimeConsumingFunctionCall": { + "message": "T̂óp̂ f́ûńĉt́îón̂ ćâĺl̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | totalReflowTime": { + "message": "T̂ót̂ál̂ ŕêf́l̂óŵ t́îḿê" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": { "message": "R̂éd̂úĉín̂ǵ t̂h́ê d́ôẃn̂ĺôád̂ t́îḿê óf̂ ím̂áĝéŝ ćâń îḿp̂ŕôv́ê t́ĥé p̂ér̂ćêív̂éd̂ ĺôád̂ t́îḿê óf̂ t́ĥé p̂áĝé âńd̂ ĹĈṔ. [L̂éâŕn̂ ḿôŕê áb̂óût́ ôṕt̂ím̂íẑín̂ǵ îḿâǵê śîźê](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | noOptimizableImages": { + "message": "N̂ó ôṕt̂ím̂íẑáb̂ĺê ím̂áĝéŝ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | optimizeFile": { + "message": "Ôṕt̂ím̂íẑé f̂íl̂é ŝíẑé" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | others": { + "message": "{PH1} ôt́ĥér̂ś" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": { "message": "Îḿp̂ŕôv́ê ím̂áĝé d̂él̂ív̂ér̂ý" }, @@ -2726,36 +2834,141 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | description": { "message": "Ŝt́âŕt̂ ín̂v́êśt̂íĝát̂ín̂ǵ ŵít̂h́ t̂h́ê ĺôńĝéŝt́ p̂h́âśê. [D́êĺâýŝ ćâń b̂é m̂ín̂ím̂íẑéd̂](https://web.dev/articles/optimize-inp#optimize_interactions). T́ô ŕêd́ûćê ṕr̂óĉéŝśîńĝ d́ûŕât́îón̂, [óp̂t́îḿîźê t́ĥé m̂áîń-t̂h́r̂éâd́ ĉóŝt́ŝ](https://web.dev/articles/optimize-long-tasks), óf̂t́êń ĴŚ." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | duration": { + "message": "D̂úr̂át̂íôń" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | inputDelay": { + "message": "Îńp̂út̂ d́êĺâý" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | noInteractions": { + "message": "N̂ó îńt̂ér̂áĉt́îón̂ś d̂ét̂éĉt́êd́" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | phase": { + "message": "P̂h́âśê" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | presentationDelay": { + "message": "P̂ŕêśêńt̂át̂íôń d̂él̂áŷ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | processingDuration": { + "message": "P̂ŕôćêśŝín̂ǵ d̂úr̂át̂íôń" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": { "message": "ÎŃP̂ b́ŷ ṕĥáŝé" }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": { "message": "Ôṕt̂ím̂íẑé L̂ĆP̂ b́ŷ ḿâḱîńĝ t́ĥé L̂ĆP̂ ím̂áĝé [d̂íŝćôv́êŕâb́l̂é](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) f̂ŕôḿ t̂h́ê H́T̂ḾL̂ ím̂ḿêd́îát̂él̂ý, âńd̂ [áv̂óîd́îńĝ ĺâźŷ-ĺôád̂ín̂ǵ](https://web.dev/articles/lcp-lazy-loading)" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | fetchPriorityApplied": { + "message": "f̂ét̂ćĥṕr̂íôŕît́ŷ=h́îǵĥ áp̂ṕl̂íêd́" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": { + "message": "l̂áẑý l̂óâd́ n̂ót̂ áp̂ṕl̂íêd́" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lcpLoadDelay": { + "message": "L̂ĆP̂ ím̂áĝé l̂óâd́êd́ {PH1} âf́t̂ér̂ éâŕl̂íêśt̂ śt̂ár̂t́ p̂óîńt̂." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | noLcp": { + "message": "N̂ó L̂ĆP̂ d́êt́êćt̂éd̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | noLcpResource": { + "message": "N̂ó L̂ĆP̂ ŕêśôúr̂ćê d́êt́êćt̂éd̂ b́êćâúŝé t̂h́ê ĹĈṔ îś n̂ót̂ án̂ ím̂áĝé" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | requestDiscoverable": { + "message": "R̂éq̂úêśt̂ íŝ d́îśĉóv̂ér̂áb̂ĺê ín̂ ín̂ít̂íâĺ d̂óĉúm̂én̂t́" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": { "message": "L̂ĆP̂ ŕêq́ûéŝt́ d̂íŝćôv́êŕŷ" }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | description": { "message": "Êáĉh́ [p̂h́âśê h́âś ŝṕêćîf́îć îḿp̂ŕôv́êḿêńt̂ śt̂ŕât́êǵîéŝ](https://web.dev/articles/optimize-lcp#lcp-breakdown). Íd̂éâĺl̂ý, m̂óŝt́ ôf́ t̂h́ê ĹĈṔ t̂ím̂é ŝh́ôúl̂d́ b̂é ŝṕêńt̂ ón̂ ĺôád̂ín̂ǵ t̂h́ê ŕêśôúr̂ćêś, n̂ót̂ ẃît́ĥín̂ d́êĺâýŝ." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | elementRenderDelay": { + "message": "Êĺêḿêńt̂ ŕêńd̂ér̂ d́êĺâý" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | noLcp": { + "message": "N̂ó L̂ĆP̂ d́êt́êćt̂éd̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | percentLCP": { + "message": "% ôf́ L̂ĆP̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | phase": { + "message": "P̂h́âśê" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDelay": { + "message": "R̂éŝóûŕĉé l̂óâd́ d̂él̂áŷ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDuration": { + "message": "R̂éŝóûŕĉé l̂óâd́ d̂úr̂át̂íôń" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | timeToFirstByte": { + "message": "T̂ím̂é t̂ó f̂ír̂śt̂ b́ŷt́ê" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | title": { "message": "L̂ĆP̂ b́ŷ ṕĥáŝé" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | description": { + "message": "[Âv́ôíd̂ ćĥáîńîńĝ ćr̂ít̂íĉál̂ ŕêq́ûéŝt́ŝ](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) b́ŷ ŕêd́ûćîńĝ t́ĥé l̂én̂ǵt̂h́ ôf́ ĉh́âín̂ś, r̂éd̂úĉín̂ǵ t̂h́ê d́ôẃn̂ĺôád̂ śîźê óf̂ ŕêśôúr̂ćêś, ôŕ d̂éf̂ér̂ŕîńĝ t́ĥé d̂óŵńl̂óâd́ ôf́ ûńn̂éĉéŝśâŕŷ ŕêśôúr̂ćêś t̂ó îḿp̂ŕôv́ê ṕâǵê ĺôád̂." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | noLongCriticalNetworkTree": { + "message": "N̂ó r̂én̂d́êŕîńĝ t́âśk̂ś îḿp̂áĉt́êd́ b̂ý l̂ón̂ǵ ĉŕît́îćâĺ n̂ét̂ẃôŕk̂ t́r̂éê" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": { + "message": "L̂ón̂ǵ ĉŕît́îćâĺ n̂ét̂ẃôŕk̂ t́r̂éê" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": { "message": "R̂éq̂úêśt̂ś âŕê b́l̂óĉḱîńĝ t́ĥé p̂áĝé'ŝ ín̂ít̂íâĺ r̂én̂d́êŕ, ŵh́îćĥ ḿâý d̂él̂áŷ ĹĈṔ. [D̂éf̂ér̂ŕîńĝ ór̂ ín̂ĺîńîńĝ](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources/) ćâń m̂óv̂é t̂h́êśê ńêt́ŵór̂ḱ r̂éq̂úêśt̂ś ôút̂ óf̂ t́ĥé ĉŕît́îćâĺ p̂át̂h́." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | duration": { + "message": "D̂úr̂át̂íôń" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | noRenderBlocking": { + "message": "N̂ó r̂én̂d́êŕ b̂ĺôćk̂ín̂ǵ r̂éq̂úêśt̂ś f̂ór̂ t́ĥíŝ ńâv́îǵât́îón̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | renderBlockingRequest": { + "message": "R̂éq̂úêśt̂" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": { "message": "R̂én̂d́êŕ b̂ĺôćk̂ín̂ǵ r̂éq̂úêśt̂ś" }, "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | description": { "message": "Îf́ R̂éĉál̂ćûĺât́ê Śt̂ýl̂é ĉóŝt́ŝ ŕêḿâín̂ h́îǵĥ, śêĺêćt̂ór̂ óp̂t́îḿîźât́îón̂ ćâń r̂éd̂úĉé t̂h́êḿ. [Ôṕt̂ím̂íẑé t̂h́ê śêĺêćt̂ór̂ś](https://developer.chrome.com/docs/devtools/performance/selector-stats) ŵít̂h́ b̂ót̂h́ ĥíĝh́ êĺâṕŝéd̂ t́îḿê án̂d́ ĥíĝh́ ŝĺôẃ-p̂át̂h́ %. Ŝím̂ṕl̂ér̂ śêĺêćt̂ór̂ś, f̂éŵér̂ śêĺêćt̂ór̂ś, â śm̂ál̂ĺêŕ D̂ÓM̂, án̂d́ â śĥál̂ĺôẃêŕ D̂ÓM̂ ẃîĺl̂ ál̂ĺ r̂éd̂úĉé m̂át̂ćĥín̂ǵ ĉóŝt́ŝ." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | elapsed": { + "message": "Êĺâṕŝéd̂ t́îḿê" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | enableSelectorData": { + "message": "N̂ó ĈŚŜ śêĺêćt̂ór̂ d́ât́â ẃâś f̂óûńd̂. ĆŜŚ ŝél̂éĉt́ôŕ ŝt́ât́ŝ ńêéd̂ t́ô b́ê én̂áb̂ĺêd́ îń t̂h́ê ṕêŕf̂ór̂ḿâńĉé p̂án̂él̂ śêt́t̂ín̂ǵŝ." + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | matchAttempts": { + "message": "M̂át̂ćĥ át̂t́êḿp̂t́ŝ" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | matchCount": { + "message": "M̂át̂ćĥ ćôún̂t́" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | title": { "message": "ĈŚŜ Śêĺêćt̂ór̂ ćôśt̂ś" }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | topSelectors": { + "message": "T̂óp̂ śêĺêćt̂ór̂ś" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/SlowCSSSelector.js | total": { + "message": "T̂ót̂ál̂" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnBlockingTime": { + "message": "B̂ĺôćk̂ín̂ǵ t̂ím̂é" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": { + "message": "T̂h́îŕd̂ ṕâŕt̂ý" + }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": { + "message": "T̂ŕâńŝf́êŕ ŝíẑé" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": { "message": "T̂h́îŕd̂ ṕâŕt̂ý ĉód̂é ĉán̂ śîǵn̂íf̂íĉán̂t́l̂ý îḿp̂áĉt́ l̂óâd́ p̂ér̂f́ôŕm̂án̂ćê. [Ŕêd́ûćê án̂d́ d̂éf̂ér̂ ĺôád̂ín̂ǵ ôf́ t̂h́îŕd̂ ṕâŕt̂ý ĉód̂é](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/) t̂ó p̂ŕîór̂ít̂íẑé ŷóûŕ p̂áĝé'ŝ ćôńt̂én̂t́." }, + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | noThirdParties": { + "message": "N̂ó t̂h́îŕd̂ ṕâŕt̂íêś f̂óûńd̂" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": { "message": "T̂h́îŕd̂ ṕâŕt̂íêś" }, diff --git a/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts b/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts index 6b25e1349d75..c17bc227fa61 100644 --- a/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts +++ b/third-party/devtools-tests/e2e/lighthouse/navigation_test.ts @@ -120,7 +120,7 @@ describe('Navigation', function() { }); const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr, ['max-potential-fid']); - assert.strictEqual(auditResults.length, 158); + assert.strictEqual(auditResults.length, 172); assert.deepStrictEqual(erroredAudits, []); assert.deepStrictEqual(failedAudits.map(audit => audit.id), [ 'document-title', @@ -194,11 +194,12 @@ describe('Navigation', function() { const flakyAudits = [ 'server-response-time', 'render-blocking-resources', + 'render-blocking-insight', 'max-potential-fid', ]; const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr, flakyAudits); - assert.strictEqual(auditResults.length, 158); + assert.strictEqual(auditResults.length, 172); assert.deepStrictEqual(erroredAudits, []); assert.deepStrictEqual(failedAudits.map(audit => audit.id), [ 'document-title', diff --git a/third-party/devtools-tests/e2e/lighthouse/timespan_test.ts b/third-party/devtools-tests/e2e/lighthouse/timespan_test.ts index 79c0458b6561..d5d8b85c0452 100644 --- a/third-party/devtools-tests/e2e/lighthouse/timespan_test.ts +++ b/third-party/devtools-tests/e2e/lighthouse/timespan_test.ts @@ -91,9 +91,9 @@ describe('Timespan', function() { assert.strictEqual(devicePixelRatio, 1); const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr); - assert.strictEqual(auditResults.length, 44); + assert.strictEqual(auditResults.length, 58); assert.deepStrictEqual(erroredAudits, []); - assert.deepStrictEqual(failedAudits.map(audit => audit.id), []); + assert.deepStrictEqual(failedAudits.map(audit => audit), []); // Ensure the timespan captured the user interaction. const interactionAudit = lhr.audits['interaction-to-next-paint']; diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index b750f568180e..f88c2f43b2e0 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -502,7 +502,7 @@ declare module Artifacts { } interface TraceElement { - traceEventType: 'largest-contentful-paint'|'layout-shift'|'animation'|'responsiveness'; + traceEventType: 'trace-engine'|'largest-contentful-paint'|'layout-shift'|'animation'|'responsiveness'; node: NodeDetails; nodeId: number; animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[]; diff --git a/yarn.lock b/yarn.lock index 84a30ffe516f..c60098417ebc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1080,10 +1080,10 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@paulirish/trace_engine@0.0.40": - version "0.0.40" - resolved "https://registry.yarnpkg.com/@paulirish/trace_engine/-/trace_engine-0.0.40.tgz#5fc8f73ab7094fd6a7c3d20adcc68319e96dbe1e" - integrity sha512-/ofrqtRaP6G5/YpiTHTnnncZqBpiWYibvNkcdW6TjPIcxqvV3BLFjGlPTSTYIGe4wRSiCL5pm3Sztpnfm27Z7w== +"@paulirish/trace_engine@0.0.43": + version "0.0.43" + resolved "https://registry.yarnpkg.com/@paulirish/trace_engine/-/trace_engine-0.0.43.tgz#ebdb06c86896d5a5375eb2f4b8a9754d3073ea97" + integrity sha512-QNSGTciFS2AA/kxSUI3dy+84+QBYUNwmEa0K0TV/9ggrAU2+88V6C7H0OwAXeMBXQs+0Th35mUFKIutdDu+F7g== dependencies: third-party-web latest