diff --git a/core/audits/insights/image-delivery-insight.js b/core/audits/insights/image-delivery-insight.js index 2b87d9f3331d..e62c9e16397d 100644 --- a/core/audits/insights/image-delivery-insight.js +++ b/core/audits/insights/image-delivery-insight.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ // TODO: remove once implemented. - /** * @license * Copyright 2025 Google LLC @@ -10,7 +8,7 @@ import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ImageDeli import {Audit} from '../audit.js'; import * as i18n from '../../lib/i18n/i18n.js'; -import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.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/ImageDelivery.js', UIStrings); @@ -36,14 +34,41 @@ class ImageDeliveryInsight extends Audit { * @return {Promise} */ static async audit(artifacts, context) { - // TODO: implement. return adaptInsightToAuditProduct(artifacts, context, 'ImageDelivery', (insight) => { + if (!insight.optimizableImages.length) { + // TODO: show UIStrings.noOptimizableImages? + return; + } + + const relatedEventsMap = insight.relatedEvents && !Array.isArray(insight.relatedEvents) ? + insight.relatedEvents : + null; + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ + /* eslint-disable max-len */ + {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL), subItemsHeading: {key: 'reason', valueType: 'text'}}, + {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)}, + {key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes), subItemsHeading: {key: 'wastedBytes', valueType: 'bytes'}}, + /* eslint-enable max-len */ ]; + /** @type {LH.Audit.Details.Table['items']} */ - const items = [ - ]; + const items = insight.optimizableImages.map(image => ({ + url: image.request.args.data.url, + totalBytes: image.request.args.data.decodedBodyLength, + wastedBytes: image.byteSavings, + subItems: { + type: /** @type {const} */ ('subitems'), + // TODO: when strings update to remove number from "reason" uistrings, update this + // to use `image.optimizations.map(...)` and construct strings from the type. + items: (relatedEventsMap?.get(image.request) ?? []).map((reason, i) => ({ + reason, + wastedBytes: image.optimizations[i].byteSavings, + })), + }, + })); + return Audit.makeTableDetails(headings, items); }); } diff --git a/core/audits/insights/insight-audit.js b/core/audits/insights/insight-audit.js index 3c22587fccac..40ad82cc78cb 100644 --- a/core/audits/insights/insight-audit.js +++ b/core/audits/insights/insight-audit.js @@ -44,6 +44,14 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat } const insight = insights.model[insightName]; + if (insight instanceof Error) { + return { + errorMessage: insight.message, + errorStack: insight.stack, + score: null, + }; + } + const details = createDetails(insight); if (!details || (details.type === 'table' && details.headings.length === 0)) { return { @@ -63,10 +71,18 @@ async function adaptInsightToAuditProduct(artifacts, context, insightName, creat metricSavings = {...metricSavings, LCP: /** @type {any} */ (0)}; } + let score = insight.shouldShow ? 0 : 1; + // TODO: change insight model to denote passing/failing/informative. Until then... hack it. + if (insightName === 'LCPPhases') { + score = metricSavings?.LCP ?? 0 >= 1000 ? 0 : 1; + } else if (insightName === 'InteractionToNextPaint') { + score = metricSavings?.INP ?? 0 >= 500 ? 0 : 1; + } + return { scoreDisplayMode: insight.metricSavings ? Audit.SCORING_MODES.METRIC_SAVINGS : Audit.SCORING_MODES.NUMERIC, - score: insight.shouldShow ? 0 : 1, + score, metricSavings, warnings: insight.warnings, details, @@ -93,7 +109,25 @@ function makeNodeItemForNodeId(traceElements, nodeId) { return Audit.makeNodeItem(node); } +/** + * @param {LH.Artifacts.TraceElement[]} traceElements + * @param {number|null|undefined} nodeId + * @param {LH.IcuMessage|string} label + * @return {LH.Audit.Details.Table|undefined} + */ +function maybeMakeNodeElementTable(traceElements, nodeId, label) { + const node = makeNodeItemForNodeId(traceElements, nodeId); + if (!node) { + return; + } + + return Audit.makeTableDetails([ + {key: 'node', valueType: 'node', label}, + ], [{node}]); +} + export { adaptInsightToAuditProduct, makeNodeItemForNodeId, + maybeMakeNodeElementTable, }; diff --git a/core/audits/insights/interaction-to-next-paint-insight.js b/core/audits/insights/interaction-to-next-paint-insight.js index 344e81c874d2..ac379604f18f 100644 --- a/core/audits/insights/interaction-to-next-paint-insight.js +++ b/core/audits/insights/interaction-to-next-paint-insight.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ // TODO: remove once implemented. - /** * @license * Copyright 2025 Google LLC @@ -10,7 +8,7 @@ import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/Interacti import {Audit} from '../audit.js'; import * as i18n from '../../lib/i18n/i18n.js'; -import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; +import {adaptInsightToAuditProduct, maybeMakeNodeElementTable} 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); @@ -36,15 +34,35 @@ class InteractionToNextPaintInsight extends Audit { * @return {Promise} */ static async audit(artifacts, context) { - // TODO: implement. return adaptInsightToAuditProduct(artifacts, context, 'InteractionToNextPaint', (insight) => { + const event = insight.longestInteractionEvent; + if (!event) { + // TODO: show UIStrings.noInteractions? + return; + } + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ + {key: 'label', valueType: 'text', label: str_(UIStrings.phase)}, + {key: 'duration', valueType: 'ms', label: str_(i18n.UIStrings.columnDuration)}, ]; + /** @type {LH.Audit.Details.Table['items']} */ const items = [ + /* eslint-disable max-len */ + {phase: 'inputDelay', label: str_(UIStrings.inputDelay), duration: event.inputDelay / 1000}, + {phase: 'processingDuration', label: str_(UIStrings.processingDuration), duration: event.mainThreadHandling / 1000}, + {phase: 'presentationDelay', label: str_(UIStrings.presentationDelay), duration: event.presentationDelay / 1000}, + /* eslint-enable max-len */ ]; - return Audit.makeTableDetails(headings, items); + + return Audit.makeListDetails([ + maybeMakeNodeElementTable( + artifacts.TraceElements, + event.args.data.beginEvent.args.data.nodeId, + str_(i18n.UIStrings.columnElement)), + Audit.makeTableDetails(headings, items), + ].filter(table => !!table)); }); } } diff --git a/core/audits/insights/lcp-phases-insight.js b/core/audits/insights/lcp-phases-insight.js index b1a0a8304ea0..062c8e3e2ac2 100644 --- a/core/audits/insights/lcp-phases-insight.js +++ b/core/audits/insights/lcp-phases-insight.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ // TODO: remove once implemented. - /** * @license * Copyright 2025 Google LLC @@ -10,7 +8,7 @@ import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/LCPPhases import {Audit} from '../audit.js'; import * as i18n from '../../lib/i18n/i18n.js'; -import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.js'; +import {adaptInsightToAuditProduct, maybeMakeNodeElementTable} 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); @@ -30,21 +28,57 @@ class LCPPhasesInsight extends Audit { }; } + /** + * @param {Required['phases']} phases + * @return {LH.Audit.Details.Table} + */ + static makePhaseTable(phases) { + const {ttfb, loadDelay, loadTime, renderDelay} = phases; + + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + {key: 'label', valueType: 'text', label: str_(UIStrings.phase)}, + {key: 'duration', valueType: 'ms', label: str_(i18n.UIStrings.columnDuration)}, + ]; + + /** @type {LH.Audit.Details.Table['items']} */ + let items = [ + /* eslint-disable max-len */ + {phase: 'timeToFirstByte', label: str_(UIStrings.timeToFirstByte), duration: ttfb}, + {phase: 'resourceLoadDelay', label: str_(UIStrings.resourceLoadDelay), duration: loadDelay}, + {phase: 'resourceLoadDuration', label: str_(UIStrings.resourceLoadDuration), duration: loadTime}, + {phase: 'elementRenderDelay', label: str_(UIStrings.elementRenderDelay), duration: renderDelay}, + /* eslint-enable max-len */ + ]; + + if (loadDelay === undefined) { + items = items.filter(item => item.phase !== 'resourceLoadDelay'); + } + if (loadTime === undefined) { + items = items.filter(item => item.phase !== 'resourceLoadDuration'); + } + + return Audit.makeTableDetails(headings, items); + } + /** * @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); + if (!insight.phases) { + return; + } + + return Audit.makeListDetails([ + maybeMakeNodeElementTable( + artifacts.TraceElements, + insight.lcpEvent?.args.data?.nodeId, + str_(i18n.UIStrings.columnElement)), + LCPPhasesInsight.makePhaseTable(insight.phases), + ].filter(table => table !== undefined)); }); } } diff --git a/core/audits/insights/third-parties-insight.js b/core/audits/insights/third-parties-insight.js index 13873ee63056..8a7ca42f17c0 100644 --- a/core/audits/insights/third-parties-insight.js +++ b/core/audits/insights/third-parties-insight.js @@ -1,5 +1,3 @@ -/* eslint-disable no-unused-vars */ // TODO: remove once implemented. - /** * @license * Copyright 2025 Google LLC @@ -10,11 +8,18 @@ import {UIStrings} from '@paulirish/trace_engine/models/trace/insights/ThirdPart import {Audit} from '../audit.js'; import * as i18n from '../../lib/i18n/i18n.js'; -import {adaptInsightToAuditProduct, makeNodeItemForNodeId} from './insight-audit.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/ThirdParties.js', UIStrings); +/** + * @typedef URLSummary + * @property {number} transferSize + * @property {number} mainThreadTime + * @property {string | LH.IcuMessage} url + */ + class ThirdPartiesInsight extends Audit { /** * @return {LH.Audit.Meta} @@ -30,21 +35,53 @@ class ThirdPartiesInsight extends Audit { }; } + /** + * @param {LH.Artifacts.Entity} entity + * @param {import('@paulirish/trace_engine/models/trace/insights/ThirdParties.js').ThirdPartiesInsightModel} insight + * @return {Array} + */ + static makeSubItems(entity, insight) { + const urls = [...insight.urlsByEntity.get(entity) ?? []]; + return urls + .map(url => ({ + url, + mainThreadTime: 0, + transferSize: 0, + ...insight.summaryByUrl.get(url), + })) + // Sort by main thread time first, then transfer size to break ties. + .sort((a, b) => (b.mainThreadTime - a.mainThreadTime) || (b.transferSize - a.transferSize)); + } + /** * @param {LH.Artifacts} artifacts * @param {LH.Audit.Context} context * @return {Promise} */ static async audit(artifacts, context) { - // TODO: implement. return adaptInsightToAuditProduct(artifacts, context, 'ThirdParties', (insight) => { + const thirdPartyEntities = [...insight.summaryByEntity.entries()] + .filter((([entity, _]) => entity !== insight.firstPartyEntity)); + /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ + /* eslint-disable max-len */ + {key: 'entity', valueType: 'text', label: str_(UIStrings.columnThirdParty), subItemsHeading: {key: 'url', valueType: 'url'}}, + {key: 'transferSize', granularity: 1, valueType: 'bytes', label: str_(UIStrings.columnTransferSize), subItemsHeading: {key: 'transferSize'}}, + {key: 'mainThreadTime', granularity: 1, valueType: 'ms', label: str_(UIStrings.columnMainThreadTime), subItemsHeading: {key: 'mainThreadTime'}}, + /* eslint-enable max-len */ ]; /** @type {LH.Audit.Details.Table['items']} */ - const items = [ - ]; - return Audit.makeTableDetails(headings, items); + const items = thirdPartyEntities.map(([entity, summary]) => ({ + entity: entity.name, + transferSize: summary.transferSize, + mainThreadTime: summary.mainThreadTime, + subItems: { + type: /** @type {const} */ ('subitems'), + items: ThirdPartiesInsight.makeSubItems(entity, insight), + }, + })); + return Audit.makeTableDetails(headings, items, {isEntityGrouped: true}); }); } } diff --git a/core/computed/trace-engine-result.js b/core/computed/trace-engine-result.js index e158e2cd778a..56ceea1a8f0f 100644 --- a/core/computed/trace-engine-result.js +++ b/core/computed/trace-engine-result.js @@ -4,6 +4,7 @@ * 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'; @@ -27,9 +28,96 @@ 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) { + /** + * Execute `cb(traceEngineI18nObject)` on every i18n object, recursively. The cb return + * value replaces traceEngineI18nObject. + * @param {any} obj + * @param {(traceEngineI18nObject: {i18nId: string, values?: {}}) => LH.IcuMessage} cb + * @param {Set} seen + */ + function recursiveReplaceLocalizableStrings(obj, cb, seen) { + if (seen.has(seen)) { + return; + } + + seen.add(obj); + + if (obj instanceof Map) { + for (const [key, value] of obj) { + if (value && typeof value === 'object' && 'i18nId' in value) { + obj.set(key, cb(value)); + } else { + recursiveReplaceLocalizableStrings(value, cb, seen); + } + } + } else if (obj && typeof obj === 'object' && !Array.isArray(obj)) { + Object.keys(obj).forEach(key => { + const value = obj[key]; + if (value && typeof value === 'object' && 'i18nId' in value) { + obj[key] = cb(value); + } else { + recursiveReplaceLocalizableStrings(value, cb, seen); + } + }); + } else if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + const value = obj[i]; + if (value && typeof value === 'object' && 'i18nId' in value) { + obj[i] = cb(value); + } else { + recursiveReplaceLocalizableStrings(value, cb, seen); + } + } + } + } + + for (const insightSet of insightSets.values()) { + for (const [name, model] of Object.entries(insightSet.model)) { + if (model instanceof Error) { + continue; + } + + /** @type {Record} */ + let traceEngineUIStrings; + if (name in TraceEngine.Insights.Models) { + const nameAsKey = /** @type {keyof typeof TraceEngine.Insights.Models} */ (name); + traceEngineUIStrings = TraceEngine.Insights.Models[nameAsKey].UIStrings; + } else { + throw new Error(`insight missing UIStrings: ${name}`); + } + + const key = `node_modules/@paulirish/trace_engine/models/trace/insights/${name}.js`; + const str_ = i18n.createIcuMessageFn(key, traceEngineUIStrings); + + // Pass `{i18nId: string, values?: {}}` through Lighthouse's i18n pipeline. + // This is equivalent to if we directly did `str_(UIStrings.whatever, ...)` + recursiveReplaceLocalizableStrings(model, (traceEngineI18nObject) => { + let values = traceEngineI18nObject.values; + if (values) { + values = structuredClone(values); + for (const [key, value] of Object.entries(values)) { + if (value && typeof value === 'object' && '__i18nBytes' in value) { + // @ts-expect-error + values[key] = value.__i18nBytes; + // TODO: use an actual byte formatter. Right now, this shows the exact number of bytes. + } + } + } + + return str_(traceEngineI18nObject.i18nId, values); + }, new Set()); + } + } + } + /** * @param {{trace: LH.Trace}} data * @param {LH.Artifacts.ComputedContext} context diff --git a/core/gather/gatherers/trace-elements.js b/core/gather/gatherers/trace-elements.js index ed8874e76666..7d7ef1c42858 100644 --- a/core/gather/gatherers/trace-elements.js +++ b/core/gather/gatherers/trace-elements.js @@ -92,7 +92,7 @@ class TraceElements extends BaseGatherer { /** * Execute `cb(obj, key)` on every object property (non-objects only), recursively. * @param {any} obj - * @param {(obj: Record, key: string) => void} cb + * @param {(obj: Record, key: string) => void} cb * @param {Set} seen */ function recursiveObjectEnumerate(obj, cb, seen) { diff --git a/core/lib/trace-engine.js b/core/lib/trace-engine.js index b000c6bfcca2..7e6e97239847 100644 --- a/core/lib/trace-engine.js +++ b/core/lib/trace-engine.js @@ -10,9 +10,11 @@ polyfillDOMRect(); const TraceProcessor = TraceEngine.Processor.TraceProcessor; const TraceHandlers = TraceEngine.Handlers.ModelHandlers; const RootCauses = TraceEngine.RootCauses.RootCauses.RootCauses; +const Insights = TraceEngine.Insights; export { TraceProcessor, TraceHandlers, RootCauses, + Insights, }; diff --git a/core/scripts/generate-insight-audits.js b/core/scripts/generate-insight-audits.js index 3da6e0561251..50f8f44cc19a 100644 --- a/core/scripts/generate-insight-audits.js +++ b/core/scripts/generate-insight-audits.js @@ -77,6 +77,8 @@ class ${insightName}Insight extends Audit { return adaptInsightToAuditProduct(artifacts, context, '${insightName}', (insight) => { /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ + /* eslint-disable max-len */ + /* eslint-enable max-len */ ]; /** @type {LH.Audit.Details.Table['items']} */ const items = [ diff --git a/core/scripts/i18n/collect-strings.js b/core/scripts/i18n/collect-strings.js index 8c448b15e74c..39988572a7f3 100644 --- a/core/scripts/i18n/collect-strings.js +++ b/core/scripts/i18n/collect-strings.js @@ -29,7 +29,7 @@ import {escapeIcuMessage} from '../../../shared/localization/format.js'; // Match declarations of UIStrings, terminating in either a `};\n` (very likely to always be right) // or `}\n\n` (allowing semicolon to be optional, but insisting on a double newline so that an // closing brace in the middle of the declaration does not prematurely end the pattern) -const UISTRINGS_REGEX = /UIStrings = .*?\}(;|\n)\n/s; +const UISTRINGS_REGEX = /\bUIStrings = .*?\}(;|\n)\n/s; /** @typedef {import('./bake-ctc-to-lhl.js').CtcMessage} CtcMessage */ /** @typedef {Required>} IncrementalCtc */ 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 562245a3f8e0..7b34f9fafad4 100644 --- a/core/test/fixtures/user-flows/reports/sample-flow-result.json +++ b/core/test/fixtures/user-flows/reports/sample-flow-result.json @@ -3975,7 +3975,7 @@ "type": "checklist", "items": { "priorityHinted": { - "label": "fetchpriority=high applied", + "label": "fetchpriority=high should be applied", "value": false }, "requestDiscoverable": { @@ -3994,8 +3994,53 @@ "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", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Phase" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "phase": "timeToFirstByte", + "label": "Time to first byte", + "duration": 145.333 + }, + { + "phase": "resourceLoadDelay", + "label": "Resource load delay", + "duration": 24.97999999999999 + }, + { + "phase": "resourceLoadDuration", + "label": "Resource load duration", + "duration": 63.870000000000005 + }, + { + "phase": "elementRenderDelay", + "label": "Element render delay", + "duration": 3031.475 + } + ] + } + ] + }, "guidanceLevel": 3 }, "long-critical-network-tree-insight": { @@ -4026,8 +4071,103 @@ "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", + "score": 0, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [ + { + "entity": "Google Tag Manager", + "transferSize": 112588, + "mainThreadTime": 182, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://www.googletagmanager.com/gtag/js?id=G-RTW9M3W5HC", + "mainThreadTime": 182, + "transferSize": 112588 + } + ] + } + }, + { + "entity": "Google Fonts", + "transferSize": 16723, + "mainThreadTime": 0, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://fonts.gstatic.com/s/poppins/v22/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2", + "mainThreadTime": 0, + "transferSize": 7927 + }, + { + "url": "https://fonts.gstatic.com/s/poppins/v22/pxiByp8kv8JHgFVrLCz7Z1xlFd2JQEk.woff2", + "mainThreadTime": 0, + "transferSize": 7875 + }, + { + "url": "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap", + "mainThreadTime": 0, + "transferSize": 921 + } + ] + } + }, + { + "entity": "Google Analytics", + "transferSize": 0, + "mainThreadTime": 0, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://www.google-analytics.com/g/collect?v=2&tid=G-RTW9M3W5HC>m=45je5230v9102781914za200&_p=1738719102855&gcd=13l3l3l3l1l1&npa=0&dma=0&tag_exp=102067808~102081485~102123608~102482432~102539968~102558064&cid=974257356.1738719103&ul=en-us&sr=412x823&uaa=&uab=64&uafvl=Chromium%3B134.0.6998.0%7CNot%253AA-Brand%3B24.0.0.0%7CGoogle%2520Chrome%3B134.0.6998.0&uamb=1&uam=moto%20g%20power%20(2022)&uap=Android&uapv=11.0&uaw=0&are=1&frm=0&pscdl=noapi&_s=1&sid=1738719103&sct=1&seg=0&dl=https%3A%2F%2Fwww.mikescerealshack.co%2F&dt=Search%20-%20Mike%27s%20Cereal%20Shack%3A%20The%20Office%20Search%20Engine&en=page_view&_fv=1&_nsi=1&_ss=1&_ee=1&tfd=427", + "mainThreadTime": 0, + "transferSize": 0 + }, + { + "url": "https://www.google-analytics.com/g/collect?v=2&tid=G-RTW9M3W5HC>m=45je5230v9102781914za200&_p=1738719102855&gcd=13l3l3l3l1l1&npa=0&dma=0&tag_exp=102067808~102081485~102123608~102482432~102539968~102558064&cid=974257356.1738719103&ul=en-us&sr=412x823&uaa=&uab=64&uafvl=Chromium%3B134.0.6998.0%7CNot%253AA-Brand%3B24.0.0.0%7CGoogle%2520Chrome%3B134.0.6998.0&uamb=1&uam=moto%20g%20power%20(2022)&uap=Android&uapv=11.0&uaw=0&are=1&frm=0&pscdl=noapi&_eu=AEA&_s=2&sid=1738719103&sct=1&seg=0&dl=https%3A%2F%2Fwww.mikescerealshack.co%2F&dt=Search%20-%20Mike%27s%20Cereal%20Shack%3A%20The%20Office%20Search%20Engine&en=scroll&epn.percent_scrolled=90&tfd=5436", + "mainThreadTime": 0, + "transferSize": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, "guidanceLevel": 3 }, "viewport-insight": { @@ -7604,7 +7744,8 @@ ], "core/lib/i18n/i18n.js | columnDuration": [ "audits[user-timings].details.headings[3].label", - "audits[long-tasks].details.headings[2].label" + "audits[long-tasks].details.headings[2].label", + "audits[lcp-phases-insight].details.items[0].headings[1].label" ], "core/audits/critical-request-chains.js | title": [ "audits[critical-request-chains].title" @@ -8614,6 +8755,15 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ "audits[document-latency-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + "audits[document-latency-insight].details.items.serverResponseIsFast.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ "audits[dom-size-insight].title" ], @@ -8665,12 +8815,36 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ "audits[lcp-discovery-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | fetchPriorityShouldBeApplied": [ + "audits[lcp-discovery-insight].details.items.priorityHinted.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | requestDiscoverable": [ + "audits[lcp-discovery-insight].details.items.requestDiscoverable.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": [ + "audits[lcp-discovery-insight].details.items.eagerlyLoaded.label" + ], "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/LCPPhases.js | phase": [ + "audits[lcp-phases-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | timeToFirstByte": [ + "audits[lcp-phases-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDelay": [ + "audits[lcp-phases-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDuration": [ + "audits[lcp-phases-insight].details.items[0].items[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | elementRenderDelay": [ + "audits[lcp-phases-insight].details.items[0].items[3].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ "audits[long-critical-network-tree-insight].title" ], @@ -8695,6 +8869,15 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ "audits[third-parties-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ "audits[viewport-insight].title" ], @@ -11144,8 +11327,48 @@ "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", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Phase" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "phase": "inputDelay", + "label": "Input delay", + "duration": 5.949 + }, + { + "phase": "processingDuration", + "label": "Processing duration", + "duration": 11.502 + }, + { + "phase": "presentationDelay", + "label": "Presentation delay", + "duration": 23.568 + } + ] + } + ] + }, "guidanceLevel": 3 }, "lcp-discovery-insight": { @@ -11192,8 +11415,58 @@ "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", + "score": 0, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [ + { + "entity": "Google Tag Manager", + "transferSize": 0, + "mainThreadTime": 80, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://www.googletagmanager.com/gtag/js?id=G-RTW9M3W5HC", + "mainThreadTime": 80, + "transferSize": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, "guidanceLevel": 3 }, "viewport-insight": { @@ -12667,7 +12940,8 @@ ], "core/lib/i18n/i18n.js | columnDuration": [ "audits[user-timings].details.headings[3].label", - "audits[long-tasks].details.headings[2].label" + "audits[long-tasks].details.headings[2].label", + "audits[interaction-to-next-paint-insight].details.items[0].headings[1].label" ], "core/audits/image-aspect-ratio.js | title": [ "audits[image-aspect-ratio].title" @@ -13068,6 +13342,18 @@ "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/InteractionToNextPaint.js | phase": [ + "audits[interaction-to-next-paint-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | inputDelay": [ + "audits[interaction-to-next-paint-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | processingDuration": [ + "audits[interaction-to-next-paint-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | presentationDelay": [ + "audits[interaction-to-next-paint-insight].details.items[0].items[2].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ "audits[lcp-discovery-insight].title" ], @@ -13104,6 +13390,15 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ "audits[third-parties-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ "audits[viewport-insight].title" ], @@ -22696,8 +22991,60 @@ "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", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + } + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Resource Size" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Potential Savings", + "subItemsHeading": { + "key": "wastedBytes", + "valueType": "bytes" + } + } + ], + "items": [ + { + "url": "https://www.mikescerealshack.co/oscar-actually.jpg", + "totalBytes": 122114, + "wastedBytes": 91856, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "Using a modern image format (WebP, AVIF) or increasing the image compression could improve this image's download size. (Est 31094)", + "wastedBytes": 31094 + }, + { + "reason": "This image file is larger than it needs to be (984x555) for its displayed dimensions (567x320). Use responsive images to reduce the image download size. (Est 81519)", + "wastedBytes": 81519 + } + ] + } + } + ] + }, "guidanceLevel": 3 }, "interaction-to-next-paint-insight": { @@ -22721,7 +23068,7 @@ "type": "checklist", "items": { "priorityHinted": { - "label": "fetchpriority=high applied", + "label": "fetchpriority=high should be applied", "value": false }, "requestDiscoverable": { @@ -22740,8 +23087,53 @@ "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", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Phase" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "phase": "timeToFirstByte", + "label": "Time to first byte", + "duration": 24.795 + }, + { + "phase": "resourceLoadDelay", + "label": "Resource load delay", + "duration": 159.486 + }, + { + "phase": "resourceLoadDuration", + "label": "Resource load duration", + "duration": 291.039 + }, + { + "phase": "elementRenderDelay", + "label": "Element render delay", + "duration": 21.466000000000008 + } + ] + } + ] + }, "guidanceLevel": 3 }, "long-critical-network-tree-insight": { @@ -22772,8 +23164,98 @@ "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", + "score": 0, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [ + { + "entity": "Google Tag Manager", + "transferSize": 0, + "mainThreadTime": 705, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://www.googletagmanager.com/gtag/js?id=G-RTW9M3W5HC", + "mainThreadTime": 705, + "transferSize": 0 + } + ] + } + }, + { + "entity": "Google Fonts", + "transferSize": 0, + "mainThreadTime": 0, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap", + "mainThreadTime": 0, + "transferSize": 0 + }, + { + "url": "https://fonts.gstatic.com/s/poppins/v22/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2", + "mainThreadTime": 0, + "transferSize": 0 + }, + { + "url": "https://fonts.gstatic.com/s/poppins/v22/pxiByp8kv8JHgFVrLCz7Z1xlFd2JQEk.woff2", + "mainThreadTime": 0, + "transferSize": 0 + } + ] + } + }, + { + "entity": "Google Analytics", + "transferSize": 0, + "mainThreadTime": 0, + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://www.google-analytics.com/g/collect?v=2&tid=G-RTW9M3W5HC>m=45je5230v9102781914za200&_p=1738719121904&gcd=13l3l3l3l1l1&npa=0&dma=0&tag_exp=102067808~102081485~102123608~102482432~102539968~102558064&cid=974257356.1738719103&ul=en-us&sr=412x823&uaa=&uab=64&uafvl=Chromium%3B134.0.6998.0%7CNot%253AA-Brand%3B24.0.0.0%7CGoogle%2520Chrome%3B134.0.6998.0&uamb=1&uam=moto%20g%20power%20(2022)&uap=Android&uapv=11.0&uaw=0&are=1&frm=0&pscdl=noapi&_s=1&sid=1738719103&sct=1&seg=1&dl=https%3A%2F%2Fwww.mikescerealshack.co%2Fcorrections&dt=Corrections%20-%20Mike%27s%20Cereal%20Shack%3A%20The%20Office%20Search%20Engine&en=page_view&_ee=1&tfd=226", + "mainThreadTime": 0, + "transferSize": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, "guidanceLevel": 3 }, "viewport-insight": { @@ -26366,7 +26848,8 @@ "audits[total-byte-weight].details.headings[0].label", "audits[modern-image-formats].details.headings[1].label", "audits[uses-responsive-images].details.headings[1].label", - "audits[legacy-javascript].details.headings[0].label" + "audits[legacy-javascript].details.headings[0].label", + "audits[image-delivery-insight].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ "audits[server-response-time].details.headings[1].label", @@ -26407,7 +26890,8 @@ ], "core/lib/i18n/i18n.js | columnDuration": [ "audits[user-timings].details.headings[3].label", - "audits[long-tasks].details.headings[2].label" + "audits[long-tasks].details.headings[2].label", + "audits[lcp-phases-insight].details.items[0].headings[1].label" ], "core/audits/critical-request-chains.js | title": [ "audits[critical-request-chains].title" @@ -27183,12 +27667,14 @@ ], "core/lib/i18n/i18n.js | columnResourceSize": [ "audits[modern-image-formats].details.headings[2].label", - "audits[uses-responsive-images].details.headings[2].label" + "audits[uses-responsive-images].details.headings[2].label", + "audits[image-delivery-insight].details.headings[1].label" ], "core/lib/i18n/i18n.js | columnWastedBytes": [ "audits[modern-image-formats].details.headings[3].label", "audits[uses-responsive-images].details.headings[3].label", - "audits[legacy-javascript].details.headings[2].label" + "audits[legacy-javascript].details.headings[2].label", + "audits[image-delivery-insight].details.headings[2].label" ], "core/audits/byte-efficiency/uses-optimized-images.js | title": [ "audits[uses-optimized-images].title" @@ -27413,6 +27899,15 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ "audits[document-latency-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + "audits[document-latency-insight].details.items.serverResponseIsFast.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ "audits[dom-size-insight].title" ], @@ -27452,6 +27947,24 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ "audits[image-delivery-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | useModernFormat": [ + { + "values": { + "PH1": 31094 + }, + "path": "audits[image-delivery-insight].details.items[0].subItems.items[0].reason" + } + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | useResponsiveSize": [ + { + "values": { + "PH1": 81519, + "PH2": "984x555", + "PH3": "567x320" + }, + "path": "audits[image-delivery-insight].details.items[0].subItems.items[1].reason" + } + ], "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": [ "audits[interaction-to-next-paint-insight].title" ], @@ -27464,12 +27977,36 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ "audits[lcp-discovery-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | fetchPriorityShouldBeApplied": [ + "audits[lcp-discovery-insight].details.items.priorityHinted.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | requestDiscoverable": [ + "audits[lcp-discovery-insight].details.items.requestDiscoverable.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": [ + "audits[lcp-discovery-insight].details.items.eagerlyLoaded.label" + ], "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/LCPPhases.js | phase": [ + "audits[lcp-phases-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | timeToFirstByte": [ + "audits[lcp-phases-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDelay": [ + "audits[lcp-phases-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDuration": [ + "audits[lcp-phases-insight].details.items[0].items[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | elementRenderDelay": [ + "audits[lcp-phases-insight].details.items[0].items[3].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ "audits[long-critical-network-tree-insight].title" ], @@ -27494,6 +28031,15 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ "audits[third-parties-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ "audits[viewport-insight].title" ], diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index 85e456acc3eb..f4c1bc370ad6 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -5945,8 +5945,126 @@ "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", + "score": 0, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 50 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + } + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Resource Size" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Potential Savings", + "subItemsHeading": { + "key": "wastedBytes", + "valueType": "bytes" + } + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/lighthouse-rotating.gif", + "totalBytes": 934285, + "wastedBytes": 682028, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "Using video formats instead of GIFs can improve the download size of animated content. (Est 682028)", + "wastedBytes": 682028 + } + ] + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?iar1", + "totalBytes": 112710, + "wastedBytes": 111541, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "This image file is larger than it needs to be (1024x678) for its displayed dimensions (240x30). Use responsive images to reduce the image download size. (Est 111541)", + "wastedBytes": 111541 + } + ] + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?isr2", + "totalBytes": 112710, + "wastedBytes": 106476, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "This image file is larger than it needs to be (1024x678) for its displayed dimensions (240x160). Use responsive images to reduce the image download size. (Est 106476)", + "wastedBytes": 106476 + } + ] + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?iar2", + "totalBytes": 112710, + "wastedBytes": 106476, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "This image file is larger than it needs to be (1024x678) for its displayed dimensions (240x160). Use responsive images to reduce the image download size. (Est 106476)", + "wastedBytes": 106476 + } + ] + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?isr3", + "totalBytes": 112710, + "wastedBytes": 56604, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "This image file is larger than it needs to be (1024x678) for its displayed dimensions (720x480). Use responsive images to reduce the image download size. (Est 56604)", + "wastedBytes": 56604 + } + ] + } + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg", + "totalBytes": 112710, + "wastedBytes": 56604, + "subItems": { + "type": "subitems", + "items": [ + { + "reason": "This image file is larger than it needs to be (1024x678) for its displayed dimensions (720x480). Use responsive images to reduce the image download size. (Est 56604)", + "wastedBytes": 56604 + } + ] + } + } + ] + }, "guidanceLevel": 3 }, "interaction-to-next-paint-insight": { @@ -5970,7 +6088,7 @@ "type": "checklist", "items": { "priorityHinted": { - "label": "fetchpriority=high applied", + "label": "fetchpriority=high should be applied", "value": false }, "requestDiscoverable": { @@ -5989,8 +6107,53 @@ "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", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Phase" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "phase": "timeToFirstByte", + "label": "Time to first byte", + "duration": 5.453 + }, + { + "phase": "resourceLoadDelay", + "label": "Resource load delay", + "duration": 6783.663 + }, + { + "phase": "resourceLoadDuration", + "label": "Resource load duration", + "duration": 4063.4230000000007 + }, + { + "phase": "elementRenderDelay", + "label": "Element render delay", + "duration": 41.86499999999978 + } + ] + } + ] + }, "guidanceLevel": 3 }, "long-critical-network-tree-insight": { @@ -9590,6 +9753,7 @@ "audits[uses-responsive-images].details.headings[1].label", "audits[efficient-animated-content].details.headings[0].label", "audits[legacy-javascript].details.headings[0].label", + "audits[image-delivery-insight].details.headings[0].label", "audits[render-blocking-insight].details.headings[0].label" ], "core/lib/i18n/i18n.js | columnTimeSpent": [ @@ -9632,7 +9796,8 @@ ], "core/lib/i18n/i18n.js | columnDuration": [ "audits[user-timings].details.headings[3].label", - "audits[long-tasks].details.headings[2].label" + "audits[long-tasks].details.headings[2].label", + "audits[lcp-phases-insight].details.items[0].headings[1].label" ], "core/audits/critical-request-chains.js | title": [ "audits[critical-request-chains].title" @@ -9964,6 +10129,7 @@ "audits[uses-responsive-images].details.headings[3].label", "audits[efficient-animated-content].details.headings[2].label", "audits[legacy-javascript].details.headings[2].label", + "audits[image-delivery-insight].details.headings[2].label", "audits[render-blocking-insight].details.headings[2].label" ], "core/audits/csp-xss.js | title": [ @@ -10536,7 +10702,8 @@ "core/lib/i18n/i18n.js | columnResourceSize": [ "audits[modern-image-formats].details.headings[2].label", "audits[uses-responsive-images].details.headings[2].label", - "audits[efficient-animated-content].details.headings[1].label" + "audits[efficient-animated-content].details.headings[1].label", + "audits[image-delivery-insight].details.headings[1].label" ], "core/audits/byte-efficiency/uses-optimized-images.js | title": [ "audits[uses-optimized-images].title" @@ -10787,6 +10954,15 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ "audits[document-latency-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + "audits[document-latency-insight].details.items.serverResponseIsFast.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ "audits[dom-size-insight].title" ], @@ -10826,6 +11002,56 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ "audits[image-delivery-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | useVideoFormat": [ + { + "values": { + "PH1": 682028 + }, + "path": "audits[image-delivery-insight].details.items[0].subItems.items[0].reason" + } + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | useResponsiveSize": [ + { + "values": { + "PH1": 111541, + "PH2": "1024x678", + "PH3": "240x30" + }, + "path": "audits[image-delivery-insight].details.items[1].subItems.items[0].reason" + }, + { + "values": { + "PH1": 106476, + "PH2": "1024x678", + "PH3": "240x160" + }, + "path": "audits[image-delivery-insight].details.items[2].subItems.items[0].reason" + }, + { + "values": { + "PH1": 106476, + "PH2": "1024x678", + "PH3": "240x160" + }, + "path": "audits[image-delivery-insight].details.items[3].subItems.items[0].reason" + }, + { + "values": { + "PH1": 56604, + "PH2": "1024x678", + "PH3": "720x480" + }, + "path": "audits[image-delivery-insight].details.items[4].subItems.items[0].reason" + }, + { + "values": { + "PH1": 56604, + "PH2": "1024x678", + "PH3": "720x480" + }, + "path": "audits[image-delivery-insight].details.items[5].subItems.items[0].reason" + } + ], "node_modules/@paulirish/trace_engine/models/trace/insights/InteractionToNextPaint.js | title": [ "audits[interaction-to-next-paint-insight].title" ], @@ -10838,12 +11064,36 @@ "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ "audits[lcp-discovery-insight].description" ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | fetchPriorityShouldBeApplied": [ + "audits[lcp-discovery-insight].details.items.priorityHinted.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | requestDiscoverable": [ + "audits[lcp-discovery-insight].details.items.requestDiscoverable.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": [ + "audits[lcp-discovery-insight].details.items.eagerlyLoaded.label" + ], "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/LCPPhases.js | phase": [ + "audits[lcp-phases-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | timeToFirstByte": [ + "audits[lcp-phases-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDelay": [ + "audits[lcp-phases-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | resourceLoadDuration": [ + "audits[lcp-phases-insight].details.items[0].items[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPPhases.js | elementRenderDelay": [ + "audits[lcp-phases-insight].details.items[0].items[3].label" + ], "node_modules/@paulirish/trace_engine/models/trace/insights/LongCriticalNetworkTree.js | title": [ "audits[long-critical-network-tree-insight].title" ], diff --git a/core/test/scenarios/__snapshots__/api-test-pptr.js.snap b/core/test/scenarios/__snapshots__/api-test-pptr.js.snap index e0f34756bf51..ceee54ae8c6f 100644 --- a/core/test/scenarios/__snapshots__/api-test-pptr.js.snap +++ b/core/test/scenarios/__snapshots__/api-test-pptr.js.snap @@ -605,7 +605,6 @@ Array [ "font-display-insight", "forced-reflow-insight", "image-delivery-insight", - "interaction-to-next-paint-insight", "layout-shifts", "lcp-discovery-insight", "lcp-phases-insight", diff --git a/package.json b/package.json index fcfcece52961..0fb49a18ac22 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "webtreemap-cdt": "^3.2.1" }, "dependencies": { - "@paulirish/trace_engine": "0.0.43", + "@paulirish/trace_engine": "0.0.44", "@sentry/node": "^7.0.0", "axe-core": "^4.10.2", "chrome-launcher": "^1.1.2", diff --git a/shared/localization/locales/en-US.json b/shared/localization/locales/en-US.json index b0047cad4fca..4ad42801f854 100644 --- a/shared/localization/locales/en-US.json +++ b/shared/localization/locales/en-US.json @@ -2861,6 +2861,9 @@ "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 | fetchPriorityShouldBeApplied": { + "message": "fetchpriority=high should be applied" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": { "message": "lazy load not applied" }, @@ -2954,8 +2957,8 @@ "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 | columnMainThreadTime": { + "message": "Main thread time" }, "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": { "message": "Third party" diff --git a/shared/localization/locales/en-XL.json b/shared/localization/locales/en-XL.json index 77b038094243..13158fe8f6ac 100644 --- a/shared/localization/locales/en-XL.json +++ b/shared/localization/locales/en-XL.json @@ -2861,6 +2861,9 @@ "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 | fetchPriorityShouldBeApplied": { + "message": "f̂ét̂ćĥṕr̂íôŕît́ŷ=h́îǵĥ śĥóûĺd̂ b́ê áp̂ṕl̂íêd́" + }, "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | lazyLoadNotApplied": { "message": "l̂áẑý l̂óâd́ n̂ót̂ áp̂ṕl̂íêd́" }, @@ -2954,8 +2957,8 @@ "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 | columnMainThreadTime": { + "message": "M̂áîń t̂h́r̂éâd́ t̂ím̂é" }, "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": { "message": "T̂h́îŕd̂ ṕâŕt̂ý" diff --git a/yarn.lock b/yarn.lock index c60098417ebc..03525b990ad0 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.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== +"@paulirish/trace_engine@0.0.44": + version "0.0.44" + resolved "https://registry.yarnpkg.com/@paulirish/trace_engine/-/trace_engine-0.0.44.tgz#27f2188856c4800e02a68e6f51b6ade9c460cfb8" + integrity sha512-QjDv5qVaUXd5WZzE2ktKvqtGA17v4HFtj6MROCGkK57AZr9n0ZKgcx7dEFho+5EHZ6V6h1upW2eqheo8C4Y4dA== dependencies: third-party-web latest