core(prioritize-lcp-image): use request initiators for load path (#14807)
This commit is contained in:
Родитель
621e10c219
Коммит
c958468921
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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<LH.Gatherer.Simulation.GraphNode>} 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<LH.Gatherer.Simulation.GraphNetworkNode>|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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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": "<img src=\"/logo-text.svg\" class=\"h-32 sm:h-44 mt-12 md:mt-24 mb-8 lg:mb-16\" alt=\"Mike's Cereal Shack Logo\">",
|
||||
"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": "<img class=\"rounded-md h-40 sm:h-48 md:h-60 lg:h-80\" src=\"/oscar-actually.jpg\">",
|
||||
"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"
|
||||
],
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче