diff --git a/cli/test/smokehouse/test-definitions/dobetterweb.js b/cli/test/smokehouse/test-definitions/dobetterweb.js index 744ee0be19..552bc28b76 100644 --- a/cli/test/smokehouse/test-definitions/dobetterweb.js +++ b/cli/test/smokehouse/test-definitions/dobetterweb.js @@ -551,16 +551,18 @@ const expectations = { debugData: { initiatorPath: [{ url: 'http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?redirected-lcp', + initiatorType: 'redirect', + }, { + url: 'http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?lcp&redirect=lighthouse-1024x680.jpg%3Fredirected-lcp', initiatorType: 'parser', }, { - // TOD(bckenny): missing initiator step through redirected image url. url: 'http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true', initiatorType: 'parser', }, { url: 'http://localhost:10200/dobetterweb/dbw_tester.html', initiatorType: 'other', }], - pathLength: 3, + pathLength: 4, }, }, }, diff --git a/cli/test/smokehouse/test-definitions/perf-trace-elements.js b/cli/test/smokehouse/test-definitions/perf-trace-elements.js index fb90817164..60f22cfc2f 100644 --- a/cli/test/smokehouse/test-definitions/perf-trace-elements.js +++ b/cli/test/smokehouse/test-definitions/perf-trace-elements.js @@ -195,13 +195,12 @@ const expectations = { score: 1, numericValue: 0, details: { - items: [{ - url: 'http://localhost:10200/dobetterweb/lighthouse-480x318.jpg', - }], + items: [], debugData: { initiatorPath: [{ url: 'http://localhost:10200/dobetterweb/lighthouse-480x318.jpg', - initiatorType: 'other', + // Dynamically-added, lazy-loaded images currently have broken initiator chains. + initiatorType: 'fallbackToMain', }, { url: 'http://localhost:10200/perf/trace-elements.html', initiatorType: 'other', diff --git a/core/audits/prioritize-lcp-image.js b/core/audits/prioritize-lcp-image.js index 6d331886d8..2cca99cffb 100644 --- a/core/audits/prioritize-lcp-image.js +++ b/core/audits/prioritize-lcp-image.js @@ -25,7 +25,8 @@ const UIStrings = { const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings); /** - * @typedef {Array<{url: string, initiatorType: string}>} InitiatorPath + * @typedef {LH.Crdp.Network.Initiator['type']|'redirect'|'fallbackToMain'} InitiatorType + * @typedef {Array<{url: string, initiatorType: InitiatorType}>} InitiatorPath */ class PrioritizeLcpImage extends Audit { @@ -47,18 +48,16 @@ class PrioritizeLcpImage extends Audit { * * @param {LH.Artifacts.NetworkRequest} request * @param {LH.Artifacts.NetworkRequest} mainResource - * @param {Array} initiatorPath + * @param {InitiatorPath} initiatorPath * @return {boolean} */ static shouldPreloadRequest(request, mainResource, initiatorPath) { - const mainResourceDepth = mainResource.redirects ? mainResource.redirects.length : 0; - // If it's already preloaded, no need to recommend it. if (request.isLinkPreload) return false; // It's not a request loaded over the network, don't recommend it. if (NetworkRequest.isNonNetworkRequest(request)) return false; - // It's already discoverable from the main document, don't recommend it. - if (initiatorPath.length <= mainResourceDepth) return false; + // It's already discoverable from the main document (a path of [lcpRecord, mainResource]), don't recommend it. + if (initiatorPath.length <= 2) return false; // Finally, return whether or not it belongs to the main frame return request.frameId === mainResource.frameId; } @@ -66,23 +65,57 @@ class PrioritizeLcpImage extends Audit { /** * @param {LH.Gatherer.Simulation.GraphNode} graph * @param {NetworkRequest} lcpRecord - * @return {{lcpNode: LH.Gatherer.Simulation.GraphNetworkNode|undefined, path: Array|undefined}} + * @return {LH.Gatherer.Simulation.GraphNetworkNode|undefined} */ static findLCPNode(graph, lcpRecord) { - let lcpNode; - let path; - graph.traverse((node, traversalPath) => { - if (node.type !== 'network') return; + for (const {node} of graph.traverseGenerator()) { + if (node.type !== 'network') continue; if (node.record.requestId === lcpRecord.requestId) { - lcpNode = node; - path = - traversalPath.slice(1).filter(initiator => initiator.type === 'network'); + return node; } - }); - return { - lcpNode, - path, - }; + } + } + + /** + * Get the initiator path starting with lcpRecord back to mainResource, inclusive. + * Navigation redirects *to* the mainResource are not included. + * Path returned will always be at least [lcpRecord, mainResource]. + * @param {NetworkRequest} lcpRecord + * @param {NetworkRequest} mainResource + * @return {InitiatorPath} + */ + static getLcpInitiatorPath(lcpRecord, mainResource) { + /** @type {InitiatorPath} */ + const initiatorPath = []; + let mainResourceReached = false; + /** @type {NetworkRequest|undefined} */ + let request = lcpRecord; + + while (request) { + mainResourceReached ||= request.requestId === mainResource.requestId; + + /** @type {InitiatorType} */ + let initiatorType = request.initiator?.type ?? 'other'; + // Initiator type usually comes from redirect, but 'redirect' is used for more informative debugData. + if (request.initiatorRequest && request.initiatorRequest === request.redirectSource) { + initiatorType = 'redirect'; + } + // Sometimes the initiator chain is broken and the best that can be done is stitch + // back to the main resource. Note this in the initiatorType. + if (!request.initiatorRequest && !mainResourceReached) { + initiatorType = 'fallbackToMain'; + } + + initiatorPath.push({url: request.url, initiatorType}); + + // Can't preload before the main resource, so break off initiator path there. + if (mainResourceReached) break; + + // Continue up chain, falling back to mainResource if chain is broken. + request = request.initiatorRequest || mainResource; + } + + return initiatorPath; } /** @@ -93,18 +126,14 @@ class PrioritizeLcpImage extends Audit { */ static getLCPNodeToPreload(mainResource, graph, lcpRecord) { if (!lcpRecord) return {}; - const {lcpNode, path} = PrioritizeLcpImage.findLCPNode(graph, lcpRecord); - if (!lcpNode || !path) return {}; + const lcpNode = PrioritizeLcpImage.findLCPNode(graph, lcpRecord); + const initiatorPath = PrioritizeLcpImage.getLcpInitiatorPath(lcpRecord, mainResource); + if (!lcpNode) return {initiatorPath}; // eslint-disable-next-line max-len - const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(lcpNode.record, mainResource, path); + const shouldPreload = PrioritizeLcpImage.shouldPreloadRequest(lcpRecord, mainResource, initiatorPath); const lcpNodeToPreload = shouldPreload ? lcpNode : undefined; - const initiatorPath = [ - {url: lcpNode.record.url, initiatorType: lcpNode.initiatorType}, - ...path.map(n => ({url: n.record.url, initiatorType: n.initiatorType})), - ]; - return { lcpNodeToPreload, initiatorPath, @@ -276,6 +305,7 @@ class PrioritizeLcpImage extends Audit { const lcpRecord = PrioritizeLcpImage.getLcpRecord(trace, processedNavigation, networkRecords); const graph = lanternLCP.pessimisticGraph; + // Note: if moving to LCPAllFrames, mainResource would need to be the LCP frame's main resource. const {lcpNodeToPreload, initiatorPath} = PrioritizeLcpImage.getLCPNodeToPreload(mainResource, graph, lcpRecord); diff --git a/core/test/audits/prioritize-lcp-image-test.js b/core/test/audits/prioritize-lcp-image-test.js index c98efbb76f..ec5feb894d 100644 --- a/core/test/audits/prioritize-lcp-image-test.js +++ b/core/test/audits/prioritize-lcp-image-test.js @@ -8,13 +8,15 @@ import PrioritizeLcpImage from '../../audits/prioritize-lcp-image.js'; import {networkRecordsToDevtoolsLog} from '../network-records-to-devtools-log.js'; import {createTestTrace} from '../create-test-trace.js'; -const rootNodeUrl = 'http://example.com:3000'; -const mainDocumentNodeUrl = 'http://www.example.com:3000'; -const scriptNodeUrl = 'http://www.example.com/script.js'; +const requestedUrl = 'http://example.com:3000'; +const mainDocumentUrl = 'http://www.example.com:3000'; +const finalDisplayedUrl = 'http://www.example.com:3000/some-page.html'; + +const scriptUrl = 'http://www.example.com/script.js'; const imageUrl = 'http://www.example.com/image.png'; describe('Performance: prioritize-lcp-image audit', () => { - const mockArtifacts = (networkRecords, finalDisplayedUrl) => { + const mockArtifacts = (networkRecords, URL) => { return { GatherContext: {gatherMode: 'navigation'}, traces: { @@ -26,11 +28,7 @@ describe('Performance: prioritize-lcp-image audit', () => { devtoolsLogs: { [PrioritizeLcpImage.DEFAULT_PASS]: networkRecordsToDevtoolsLog(networkRecords), }, - URL: { - requestedUrl: finalDisplayedUrl, - mainDocumentUrl: finalDisplayedUrl, - finalDisplayedUrl, - }, + URL, TraceElements: [ { traceEventType: 'largest-contentful-paint', @@ -51,8 +49,8 @@ describe('Performance: prioritize-lcp-image audit', () => { } const mockNetworkRecords = () => { - return [ - { + return { + networkRecords: [{ requestId: '2', priority: 'High', isLinkPreload: false, @@ -60,7 +58,7 @@ describe('Performance: prioritize-lcp-image audit', () => { networkEndTime: 500, timing: {receiveHeadersEnd: 500}, transferSize: 400, - url: rootNodeUrl, + url: requestedUrl, frameId: 'ROOT_FRAME', }, { @@ -71,7 +69,7 @@ describe('Performance: prioritize-lcp-image audit', () => { networkRequestTime: 500, networkEndTime: 1000, transferSize: 16_000, - url: mainDocumentNodeUrl, + url: mainDocumentUrl, frameId: 'ROOT_FRAME', }, { @@ -82,8 +80,8 @@ describe('Performance: prioritize-lcp-image audit', () => { networkRequestTime: 1000, networkEndTime: 2000, transferSize: 32_000, - url: scriptNodeUrl, - initiator: {type: 'parser', url: mainDocumentNodeUrl}, + url: scriptUrl, + initiator: {type: 'parser', url: mainDocumentUrl}, frameId: 'ROOT_FRAME', }, { @@ -95,15 +93,20 @@ describe('Performance: prioritize-lcp-image audit', () => { networkEndTime: 4500, transferSize: 64_000, url: imageUrl, - initiator: {type: 'script', url: scriptNodeUrl}, + initiator: {type: 'script', url: scriptUrl}, frameId: 'ROOT_FRAME', + }], + URL: { + requestedUrl, + mainDocumentUrl, + finalDisplayedUrl, }, - ]; + }; }; it('is not applicable if TraceElements does not include LCP', async () => { - const networkRecords = mockNetworkRecords(); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const {networkRecords, URL} = mockNetworkRecords(); + const artifacts = mockArtifacts(networkRecords, URL); artifacts.TraceElements = []; const result = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(result).toEqual({ @@ -113,8 +116,8 @@ describe('Performance: prioritize-lcp-image audit', () => { }); it('is not applicable if LCP was not an image', async () => { - const networkRecords = mockNetworkRecords(); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const {networkRecords, URL} = mockNetworkRecords(); + const artifacts = mockArtifacts(networkRecords, URL); artifacts.TraceElements[0].type = 'text'; const result = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(result).toEqual({ @@ -124,8 +127,8 @@ describe('Performance: prioritize-lcp-image audit', () => { }); it('shouldn\'t be applicable if lcp image element is not found', async () => { - const networkRecords = mockNetworkRecords(); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const {networkRecords, URL} = mockNetworkRecords(); + const artifacts = mockArtifacts(networkRecords, URL); // Make image paint event not apply to our node. const imagePaintEvent = artifacts.traces.defaultPass @@ -139,9 +142,9 @@ describe('Performance: prioritize-lcp-image audit', () => { }); it('shouldn\'t be applicable if the lcp is already preloaded', async () => { - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); networkRecords[3].isLinkPreload = true; - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results.score).toEqual(1); expect(results.details.overallSavingsMs).toEqual(0); @@ -150,18 +153,18 @@ describe('Performance: prioritize-lcp-image audit', () => { // debugData should be included even if image shouldn't be preloaded. expect(results.details.debugData).toMatchObject({ initiatorPath: [ - {url: 'http://www.example.com/image.png', initiatorType: 'script'}, - {url: 'http://www.example.com/script.js', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: imageUrl, initiatorType: 'script'}, + {url: scriptUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], pathLength: 3, }); }); it('shouldn\'t be applicable if the lcp request is not from over the network', async () => { - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); networkRecords[3].protocol = 'data'; - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results.score).toEqual(1); expect(results.details.overallSavingsMs).toEqual(0); @@ -169,8 +172,8 @@ describe('Performance: prioritize-lcp-image audit', () => { }); it('should suggest preloading a lcp image if all criteria is met', async () => { - const networkRecords = mockNetworkRecords(); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const {networkRecords, URL} = mockNetworkRecords(); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results.numericValue).toEqual(30); expect(results.details.overallSavingsMs).toEqual(30); @@ -179,18 +182,18 @@ describe('Performance: prioritize-lcp-image audit', () => { expect(results.details.debugData).toMatchObject({ initiatorPath: [ - {url: 'http://www.example.com/image.png', initiatorType: 'script'}, - {url: 'http://www.example.com/script.js', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: imageUrl, initiatorType: 'script'}, + {url: scriptUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], pathLength: 3, }); }); it('should suggest preloading when LCP is waiting on a dependency', async () => { - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); networkRecords[2].transferSize = 100 * 1000 * 1000; - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results.numericValue).toEqual(180); expect(results.details.overallSavingsMs).toEqual(180); @@ -198,16 +201,16 @@ describe('Performance: prioritize-lcp-image audit', () => { expect(results.details.items[0].wastedMs).toEqual(180); expect(results.details.debugData).toMatchObject({ initiatorPath: [ - {url: 'http://www.example.com/image.png', initiatorType: 'script'}, - {url: 'http://www.example.com/script.js', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: imageUrl, initiatorType: 'script'}, + {url: scriptUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], pathLength: 3, }); }); it('should use the initiator path of the first image instance loaded', async () => { - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); networkRecords.push({ requestId: '15', resourceType: 'Image', @@ -215,10 +218,10 @@ describe('Performance: prioritize-lcp-image audit', () => { // Completed before other image request. networkEndTime: 4000, url: imageUrl, - initiator: {type: 'parser', url: mainDocumentNodeUrl}, + initiator: {type: 'parser', url: mainDocumentUrl}, frameId: 'ROOT_FRAME', }); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results).toMatchObject({ numericValue: 0, @@ -226,8 +229,8 @@ describe('Performance: prioritize-lcp-image audit', () => { items: [], debugData: { initiatorPath: [ - {url: 'http://www.example.com/image.png', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: imageUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], pathLength: 2, }, @@ -236,7 +239,7 @@ describe('Performance: prioritize-lcp-image audit', () => { }); it('should not use the initiator path of a non-image load', async () => { - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); networkRecords.push({ requestId: '15', // Not an image load. @@ -246,10 +249,10 @@ describe('Performance: prioritize-lcp-image audit', () => { networkEndTime: 4000, url: imageUrl, // Parser, not script initiator. - initiator: {type: 'parser', url: mainDocumentNodeUrl}, + initiator: {type: 'parser', url: mainDocumentUrl}, frameId: 'ROOT_FRAME', }); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results).toMatchObject({ numericValue: 180, @@ -257,9 +260,9 @@ describe('Performance: prioritize-lcp-image audit', () => { items: [{url: imageUrl}], debugData: { initiatorPath: [ - {url: 'http://www.example.com/image.png', initiatorType: 'script'}, - {url: 'http://www.example.com/script.js', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: imageUrl, initiatorType: 'script'}, + {url: scriptUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], pathLength: 3, }, @@ -268,7 +271,7 @@ describe('Performance: prioritize-lcp-image audit', () => { }); it('should not use the initiator path of an image from a different frame', async () => { - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); networkRecords.push({ requestId: '15', resourceType: 'Image', @@ -276,11 +279,11 @@ describe('Performance: prioritize-lcp-image audit', () => { // Completed before other image request. networkEndTime: 4000, url: imageUrl, - initiator: {type: 'parser', url: mainDocumentNodeUrl}, + initiator: {type: 'parser', url: mainDocumentUrl}, // From different frame. frameId: 'CHILD_FRAME', }); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results).toMatchObject({ numericValue: 180, @@ -288,9 +291,9 @@ describe('Performance: prioritize-lcp-image audit', () => { items: [{url: imageUrl}], debugData: { initiatorPath: [ - {url: 'http://www.example.com/image.png', initiatorType: 'script'}, - {url: 'http://www.example.com/script.js', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: imageUrl, initiatorType: 'script'}, + {url: scriptUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], pathLength: 3, }, @@ -300,7 +303,7 @@ describe('Performance: prioritize-lcp-image audit', () => { it('should follow any redirected image requests', async () => { const redirectedImageUrl = 'http://www.example.com/redirect.jpg'; - const networkRecords = mockNetworkRecords(); + const {networkRecords, URL} = mockNetworkRecords(); // Redirect image request to newly added request. const redirectSource = networkRecords.at(-1); @@ -315,10 +318,10 @@ describe('Performance: prioritize-lcp-image audit', () => { networkEndTime: 4500, transferSize: 64_000, url: redirectedImageUrl, - initiator: {type: 'script', url: scriptNodeUrl}, + initiator: {type: 'script', url: scriptUrl}, frameId: 'ROOT_FRAME', }); - const artifacts = mockArtifacts(networkRecords, mainDocumentNodeUrl); + const artifacts = mockArtifacts(networkRecords, URL); const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); expect(results).toMatchObject({ numericValue: 210, @@ -326,12 +329,34 @@ describe('Performance: prioritize-lcp-image audit', () => { items: [{url: redirectedImageUrl}], debugData: { initiatorPath: [ - {url: redirectedImageUrl, initiatorType: 'script'}, - // TOD(bckenny): missing initiator step through redirected image url. - {url: 'http://www.example.com/script.js', initiatorType: 'parser'}, - {url: 'http://www.example.com:3000', initiatorType: 'other'}, + {url: redirectedImageUrl, initiatorType: 'redirect'}, + {url: imageUrl, initiatorType: 'script'}, + {url: scriptUrl, initiatorType: 'parser'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, ], - pathLength: 3, + pathLength: 4, + }, + }, + }); + }); + + it('should fall back mainResource in initiator path when chain is broken', async () => { + const {networkRecords, URL} = mockNetworkRecords(); + // For whatever reason, initiator information isn't available for this image. + networkRecords[3].initiator = {type: 'other'}; + + const artifacts = mockArtifacts(networkRecords, URL); + const results = await PrioritizeLcpImage.audit(artifacts, mockContext()); + expect(results).toMatchObject({ + numericValue: 0, + details: { + items: [], + debugData: { + initiatorPath: [ + {url: imageUrl, initiatorType: 'fallbackToMain'}, + {url: mainDocumentUrl, initiatorType: 'redirect'}, + ], + pathLength: 2, }, }, }); diff --git a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json index 85f124c253..b4d6bdb616 100644 --- a/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json +++ b/core/test/fixtures/fraggle-rock/reports/sample-flow-result.json @@ -1798,45 +1798,8 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [ - { - "key": "node", - "valueType": "node", - "label": "" - }, - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "items": [ - { - "node": { - "type": "node", - "lhId": "page-0-IMG", - "path": "1,HTML,1,BODY,0,DIV,0,DIV,4,IMG", - "selector": "body > div#__next > div.flex > img.h-32", - "boundingRect": { - "top": 192, - "bottom": 320, - "left": 38, - "right": 322, - "width": 284, - "height": 128 - }, - "snippet": "\"Mike's", - "nodeLabel": "Mike's Cereal Shack Logo" - }, - "url": "https://www.mikescerealshack.co/logo-text.svg", - "wastedMs": 0 - } - ], + "headings": [], + "items": [], "overallSavingsMs": 0, "debugData": { "type": "debugdata", @@ -6276,7 +6239,6 @@ "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", "audits[unsized-images].details.headings[1].label", - "audits[prioritize-lcp-image].details.headings[1].label", "audits[total-byte-weight].details.headings[0].label", "audits[legacy-javascript].details.headings[0].label" ], @@ -6631,10 +6593,6 @@ "core/audits/prioritize-lcp-image.js | description": [ "audits[prioritize-lcp-image].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[prioritize-lcp-image].details.headings[2].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" ], @@ -7052,6 +7010,9 @@ "path": "audits[legacy-javascript].displayValue" } ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[legacy-javascript].details.headings[2].label" + ], "core/audits/dobetterweb/doctype.js | title": [ "audits.doctype.title" ], @@ -17450,45 +17411,8 @@ "displayValue": "", "details": { "type": "opportunity", - "headings": [ - { - "key": "node", - "valueType": "node", - "label": "" - }, - { - "key": "url", - "valueType": "url", - "label": "URL" - }, - { - "key": "wastedMs", - "valueType": "timespanMs", - "label": "Potential Savings" - } - ], - "items": [ - { - "node": { - "type": "node", - "lhId": "page-0-IMG", - "path": "1,HTML,1,BODY,0,DIV,0,DIV,3,MAIN,2,DIV,0,IMG", - "selector": "div.flex > main#main-content > div.flex > img.rounded-md", - "boundingRect": { - "top": 228, - "bottom": 388, - "left": 38, - "right": 322, - "width": 284, - "height": 160 - }, - "snippet": "", - "nodeLabel": "div.flex > main#main-content > div.flex > img.rounded-md" - }, - "url": "https://www.mikescerealshack.co/oscar-actually.jpg", - "wastedMs": 0 - } - ], + "headings": [], + "items": [], "overallSavingsMs": 0, "debugData": { "type": "debugdata", @@ -22066,7 +21990,6 @@ "audits[network-server-latency].details.headings[0].label", "audits[long-tasks].details.headings[0].label", "audits[unsized-images].details.headings[1].label", - "audits[prioritize-lcp-image].details.headings[1].label", "audits[total-byte-weight].details.headings[0].label", "audits[modern-image-formats].details.headings[1].label", "audits[uses-responsive-images].details.headings[1].label", @@ -22413,12 +22336,6 @@ "core/audits/prioritize-lcp-image.js | description": [ "audits[prioritize-lcp-image].description" ], - "core/lib/i18n/i18n.js | columnWastedBytes": [ - "audits[prioritize-lcp-image].details.headings[2].label", - "audits[modern-image-formats].details.headings[3].label", - "audits[uses-responsive-images].details.headings[3].label", - "audits[legacy-javascript].details.headings[2].label" - ], "core/audits/csp-xss.js | title": [ "audits[csp-xss].title" ], @@ -22813,6 +22730,11 @@ "audits[modern-image-formats].details.headings[2].label", "audits[uses-responsive-images].details.headings[2].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" + ], "core/audits/byte-efficiency/uses-optimized-images.js | title": [ "audits[uses-optimized-images].title" ], diff --git a/core/test/results/sample_v2.json b/core/test/results/sample_v2.json index e370b31517..1c6afebb3e 100644 --- a/core/test/results/sample_v2.json +++ b/core/test/results/sample_v2.json @@ -2649,6 +2649,10 @@ "initiatorPath": [ { "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?redirected-lcp", + "initiatorType": "redirect" + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-1024x680.jpg?lcp&redirect=lighthouse-1024x680.jpg%3Fredirected-lcp", "initiatorType": "parser" }, { @@ -2660,7 +2664,7 @@ "initiatorType": "other" } ], - "pathLength": 3 + "pathLength": 4 } } },