Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new_audit: third party facades #11290

Merged
merged 90 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from 87 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
fa4ef82
starting shell
adamraine Aug 17, 2020
f7a73ca
basic audit
adamraine Aug 19, 2020
5204114
update sample (yikes)
adamraine Aug 19, 2020
4e68d60
license
adamraine Aug 19, 2020
812d217
Merge branch 'master' into lazy-third-party
adamraine Aug 20, 2020
3740ba1
remove code duplication
adamraine Aug 20, 2020
e600084
update sample
adamraine Aug 20, 2020
585aa8b
nit
adamraine Aug 20, 2020
94c1487
define lazy loading
adamraine Aug 20, 2020
339213f
Merge branch 'lazy-third-party' of github.com:GoogleChrome/lighthouse…
adamraine Aug 20, 2020
ffd6077
fix typo
adamraine Aug 20, 2020
6fec26f
add test cases
adamraine Aug 20, 2020
6eba114
hide third party ui filter
adamraine Aug 20, 2020
4dc8993
add temporary learn more link
adamraine Aug 20, 2020
597fdd0
update snapshot
adamraine Aug 20, 2020
30397ec
add displayValue to test
adamraine Aug 20, 2020
3c622da
use entity related resources
adamraine Aug 24, 2020
912e8d3
switch to product based report
adamraine Aug 24, 2020
b916432
move to experimental
adamraine Aug 24, 2020
ef46796
Merge branch 'master' into lazy-third-party
adamraine Aug 24, 2020
c59d76a
update sample and snapshot
adamraine Aug 24, 2020
34648e8
fix start end time bug
adamraine Aug 24, 2020
6ff862c
update sample
adamraine Aug 24, 2020
05450bd
Remove unnecessary typedef
adamraine Aug 24, 2020
79a2982
fix timing start bug
adamraine Aug 26, 2020
707fe72
move to default config
adamraine Aug 26, 2020
fdefa48
update sample
adamraine Aug 26, 2020
2a1284e
update index snapshot
adamraine Aug 26, 2020
ad69849
nits
adamraine Sep 9, 2020
7c6dc12
definitions
adamraine Sep 9, 2020
b17c292
add smoke test
adamraine Sep 9, 2020
4d56357
nits
adamraine Sep 11, 2020
acb984a
two pass
adamraine Sep 11, 2020
b609197
fix timing bugs
adamraine Sep 15, 2020
fce91cd
nits
adamraine Sep 15, 2020
c2f07e2
update test case structure
adamraine Sep 15, 2020
fe3246e
add test case
adamraine Sep 15, 2020
bae311b
the big rename
adamraine Sep 15, 2020
612c933
update sample and snapshot
adamraine Sep 15, 2020
1d47eaa
add real trace test case
adamraine Sep 15, 2020
446f2d3
update smoke test
adamraine Sep 15, 2020
085fb7e
smoke
adamraine Sep 15, 2020
c3f7198
Merge branch 'master' into lazy-third-party
adamraine Sep 15, 2020
812affa
revert
adamraine Sep 16, 2020
db17a66
update devtools web tests
adamraine Sep 16, 2020
4eb7481
change to category column
adamraine Sep 17, 2020
4229b06
move category to first column
adamraine Sep 21, 2020
4c1d528
type check
adamraine Sep 21, 2020
236fb9b
update description
adamraine Sep 21, 2020
3008fa8
add strings
adamraine Sep 22, 2020
26c463b
update strings
adamraine Sep 22, 2020
974491c
rebaseline devtools
adamraine Sep 22, 2020
b22afdd
Merge branch 'master' into lazy-third-party
adamraine Sep 24, 2020
5e5179a
rebaseline devtools
adamraine Sep 29, 2020
36ea5aa
rebaseline devtools
adamraine Oct 1, 2020
fb8b5a3
cleanup
adamraine Oct 1, 2020
aa30702
rename cutoff
adamraine Oct 1, 2020
8c86174
more cleanup
adamraine Oct 1, 2020
c30bf61
Merge branch 'master' into lazy-third-party
adamraine Oct 1, 2020
088f73c
identify first resource in LH
adamraine Oct 5, 2020
703bd72
rename firstEndTime
adamraine Oct 5, 2020
5bee091
update sample
adamraine Oct 5, 2020
b9328f3
condense last row
adamraine Oct 6, 2020
8ae6608
use entire entity
adamraine Oct 6, 2020
fda9948
update sample
adamraine Oct 6, 2020
fd97c9e
updates sample again
adamraine Oct 6, 2020
572a4e3
small nit
adamraine Oct 6, 2020
d4a792d
small nit 2
adamraine Oct 6, 2020
2bbaf12
update tpw
adamraine Oct 6, 2020
9396979
remove entity summary
adamraine Oct 7, 2020
3a41dd1
nits
adamraine Oct 9, 2020
d207b8c
sort
adamraine Oct 9, 2020
d8a793f
comments
adamraine Nov 11, 2020
7d5d2b7
more comment
adamraine Nov 11, 2020
87b15d0
rename rowOther
adamraine Nov 11, 2020
02210e7
rename rowOther pt2
adamraine Nov 11, 2020
86690f0
update condense function
adamraine Nov 11, 2020
b90640a
join the dark side
adamraine Nov 11, 2020
9a534e3
rn other resources
adamraine Nov 11, 2020
11d2d65
update translator comment
adamraine Nov 11, 2020
f97895b
more translator comment updates
adamraine Nov 11, 2020
cb27904
update comments
adamraine Nov 11, 2020
b67a145
update html
adamraine Nov 11, 2020
1776f1e
update smoke expectations
adamraine Nov 11, 2020
1539de8
update sample
adamraine Nov 11, 2020
2256b7c
Merge branch 'master' into lazy-third-party
adamraine Nov 11, 2020
d617f06
Update lighthouse-core/audits/third-party-facades.js
adamraine Nov 11, 2020
2d7158e
Update learn more link
adamraine Dec 1, 2020
bb4f9be
Merge branch 'master' into lazy-third-party
adamraine Dec 2, 2020
482dd42
update sample
adamraine Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ Object {
Object {
"path": "third-party-summary",
},
Object {
"path": "third-party-facades",
},
Object {
"path": "largest-contentful-paint-element",
},
Expand Down Expand Up @@ -998,6 +1001,11 @@ Object {
"id": "third-party-summary",
"weight": 0,
},
Object {
"group": "diagnostics",
"id": "third-party-facades",
"weight": 0,
},
Object {
"group": "diagnostics",
"id": "largest-contentful-paint-element",
Expand Down
14 changes: 14 additions & 0 deletions lighthouse-cli/test/fixtures/perf/third-party.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!--
* Copyright 2020 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-->
<!DOCTYPE html>
<html>
<body>

<div>We need some content to have a valid FCP/LCP</div>
<iframe width="420" height="315" src="https://www.youtube.com/embed/tgbNymZ7vqY"></iframe>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -359,4 +359,31 @@ module.exports = [
},
},
},
{
lhr: {
adamraine marked this conversation as resolved.
Show resolved Hide resolved
requestedUrl: 'http://localhost:10200/perf/third-party.html',
finalUrl: 'http://localhost:10200/perf/third-party.html',
audits: {
'third-party-facades': {
score: 0,
displayValue: '1 facade alternative available',
details: {
items: [
{
product: 'YouTube Embedded Player (Video)',
blockingTime: 0,
transferSize: '651128 +/- 100000',
subItems: {
type: 'subitems',
items: {
length: '>5', // We don't care exactly how many it has, just ensure we surface the subresources.
},
},
},
],
},
},
},
},
},
];
220 changes: 220 additions & 0 deletions lighthouse-core/audits/third-party-facades.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/**
* @license Copyright 2020 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

/**
* @fileoverview Audit which identifies third-party code on the page which can be lazy loaded.
* The audit will recommend a facade alternative which is used to imitate the third-party resource until it is needed.
*
* Entity: Set of domains which are used by a company or product area to deliver third-party resources
* Product: Specific piece of software belonging to an entity. Entities can have multiple products.
* Facade: Placeholder for a product which looks likes the actual product and replaces itself with that product when the user needs it.
*/

/** @typedef {import("third-party-web").IEntity} ThirdPartyEntity */
/** @typedef {import("third-party-web").IProduct} ThirdPartyProduct*/
/** @typedef {import("third-party-web").IFacade} ThirdPartyFacade*/

/** @typedef {{product: ThirdPartyProduct, entity: ThirdPartyEntity}} FacadableProduct */

const Audit = require('./audit.js');
const i18n = require('../lib/i18n/i18n.js');
const thirdPartyWeb = require('../lib/third-party-web.js');
const NetworkRecords = require('../computed/network-records.js');
const MainResource = require('../computed/main-resource.js');
const MainThreadTasks = require('../computed/main-thread-tasks.js');
const ThirdPartySummary = require('./third-party-summary.js');

const UIStrings = {
/** Title of a diagnostic audit that provides details about the third-party code on a web page that can be lazy loaded with a facade alternative. This descriptive title is shown to users when no resources have facade alternatives available. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
title: 'Lazy load third-party resources with facades',
/** Title of a diagnostic audit that provides details about the third-party code on a web page that can be lazy loaded with a facade alternative. This descriptive title is shown to users when one or more third-party resources have available facade alternatives. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
failureTitle: 'Some third-party resources can be lazy loaded with a facade',
/** Description of a Lighthouse audit that identifies the third-party code on the page that can be lazy loaded with a facade alternative. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. A facade is a lightweight component which looks like the desired resource. Lazy loading means resources are deferred until they are needed. Third-party code refers to resources that are not within the control of the site owner. */
description: 'Some third-party embeds can be lazy loaded. ' +
'Consider replacing them with a facade until they are required. [Learn more](https://web.dev/efficiently-load-third-party-javascript/).',
adamraine marked this conversation as resolved.
Show resolved Hide resolved
/** Summary text for the result of a Lighthouse audit that identifies the third-party code on a web page that can be lazy loaded with a facade alternative. This text summarizes the number of lazy loading facades that can be used on the page. A facade is a lightweight component which looks like the desired resource. */
displayValue: `{itemCount, plural,
=1 {# facade alternative available}
other {# facade alternatives available}
}`,
/** Label for a table column that displays the name of the product that a URL is used for. The products in the column will be pieces of software used on the page, like the "YouTube Embedded Player" or the "Drift Live Chat" box. */
columnProduct: 'Product',
/**
* @description Template for a table entry that gives the name of a product which we categorize as video related.
* @example {YouTube Embedded Player} productName
*/
categoryVideo: '{productName} (Video)',
/**
* @description Template for a table entry that gives the name of a product which we categorize as customer success related. Customer success means the product supports customers by offering chat and contact solutions.
* @example {Intercom Widget} productName
*/
categoryCustomerSuccess: '{productName} (Customer Success)',
/**
* @description Template for a table entry that gives the name of a product which we categorize as marketing related.
* @example {Drift Live Chat} productName
*/
categoryMarketing: '{productName} (Marketing)',
/**
* @description Template for a table entry that gives the name of a product which we categorize as social related.
* @example {Facebook Messenger Customer Chat} productName
*/
categorySocial: '{productName} (Social)',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

/** @type {Record<string, string>} */
adamraine marked this conversation as resolved.
Show resolved Hide resolved
const CATEGORY_UI_MAP = {
'video': UIStrings.categoryVideo,
'customer-success': UIStrings.categoryCustomerSuccess,
'marketing': UIStrings.categoryMarketing,
'social': UIStrings.categorySocial,
};

class ThirdPartyFacades extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'third-party-facades',
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['traces', 'devtoolsLogs', 'URL'],
};
}

/**
* Sort items by transfer size and combine small items into a single row.
* Items will be mutated in place to a maximum of 6 rows.
* @param {ThirdPartySummary.URLSummary[]} items
adamraine marked this conversation as resolved.
Show resolved Hide resolved
*/
static condenseItems(items) {
adamraine marked this conversation as resolved.
Show resolved Hide resolved
items.sort((a, b) => b.transferSize - a.transferSize);

// Items <1KB are condensed. If all items are <1KB, condense all but the largest.
let splitIndex = items.findIndex((item) => item.transferSize < 1000) || 1;
// Show details for top 5 items.
if (splitIndex === -1 || splitIndex > 5) splitIndex = 5;
// If there is only 1 item to condense, leave it as is.
if (splitIndex >= items.length - 1) return;

const remainder = items.splice(splitIndex);
const finalItem = remainder.reduce((result, item) => {
adamraine marked this conversation as resolved.
Show resolved Hide resolved
result.transferSize += item.transferSize;
result.blockingTime += item.blockingTime;
return result;
});

// If condensed row is still <1KB, don't show it.
if (finalItem.transferSize < 1000) return;

finalItem.url = str_(i18n.UIStrings.otherResourcesLabel);
items.push(finalItem);
}

/**
* @param {Map<string, ThirdPartySummary.Summary>} byURL
* @param {ThirdPartyEntity | undefined} mainEntity
* @return {FacadableProduct[]}
*/
static getProductsWithFacade(byURL, mainEntity) {
/** @type {Map<string, FacadableProduct>} */
const facadableProductMap = new Map();
for (const url of byURL.keys()) {
const entity = thirdPartyWeb.getEntity(url);
if (!entity || thirdPartyWeb.isFirstParty(url, mainEntity)) continue;

const product = thirdPartyWeb.getProduct(url);
if (!product || !product.facades || !product.facades.length) continue;

if (facadableProductMap.has(product.name)) continue;
facadableProductMap.set(product.name, {product, entity});
}

return Array.from(facadableProductMap.values());
}

/**
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static async audit(artifacts, context) {
const settings = context.settings;
const trace = artifacts.traces[Audit.DEFAULT_PASS];
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const networkRecords = await NetworkRecords.request(devtoolsLog, context);
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);
const mainEntity = thirdPartyWeb.getEntity(mainResource.url);
const tasks = await MainThreadTasks.request(trace, context);
const multiplier = settings.throttlingMethod === 'simulate' ?
settings.throttling.cpuSlowdownMultiplier : 1;
const summaries = ThirdPartySummary.getSummaries(networkRecords, tasks, multiplier);
const facadableProducts =
ThirdPartyFacades.getProductsWithFacade(summaries.byURL, mainEntity);

/** @type {LH.Audit.Details.TableItem[]} */
const results = [];
for (const {product, entity} of facadableProducts) {
const categoryTemplate = CATEGORY_UI_MAP[product.categories[0]];

let productWithCategory;
if (categoryTemplate) {
// Display product name with category next to it in the same column.
productWithCategory = str_(categoryTemplate, {productName: product.name});
} else {
// Just display product name if no category is found.
productWithCategory = product.name;
brendankenny marked this conversation as resolved.
Show resolved Hide resolved
}

const urls = summaries.urls.get(entity);
const entitySummary = summaries.byEntity.get(entity);
if (!urls || !entitySummary) continue;

const items = Array.from(urls).map((url) => {
const urlStats = summaries.byURL.get(url);
return /** @type {ThirdPartySummary.URLSummary} */ ({url, ...urlStats});
});
this.condenseItems(items);
results.push({
product: productWithCategory,
transferSize: entitySummary.transferSize,
blockingTime: entitySummary.blockingTime,
subItems: {type: 'subitems', items},
});
}

if (!results.length) {
return {
score: 1,
notApplicable: true,
};
}

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
/* eslint-disable max-len */
{key: 'product', itemType: 'text', subItemsHeading: {key: 'url', itemType: 'url'}, text: str_(UIStrings.columnProduct)},
{key: 'transferSize', itemType: 'bytes', subItemsHeading: {key: 'transferSize'}, granularity: 1, text: str_(i18n.UIStrings.columnTransferSize)},
{key: 'blockingTime', itemType: 'ms', subItemsHeading: {key: 'blockingTime'}, granularity: 1, text: str_(i18n.UIStrings.columnBlockingTime)},
/* eslint-enable max-len */
];

return {
score: 0,
displayValue: str_(UIStrings.displayValue, {
itemCount: results.length,
}),
details: Audit.makeTableDetails(headings, results),
};
}
}

module.exports = ThirdPartyFacades;
module.exports.UIStrings = UIStrings;
35 changes: 17 additions & 18 deletions lighthouse-core/audits/third-party-summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ const UIStrings = {
'your page has primarily finished loading. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).',
/** Label for a table column that displays the name of a third-party provider that potentially links to their website. */
columnThirdParty: 'Third-Party',
/** Label for a table column that displays how much time each row spent blocking other work on the main thread, entries will be the number of milliseconds spent. */
columnBlockingTime: 'Main-Thread Blocking Time',
/** Summary text for the result of a Lighthouse audit that identifies the code on a web page that the user doesn't control (referred to as "third-party code"). This text summarizes the number of distinct entities that were found on the page. */
displayValue: 'Third-party code blocked the main thread for ' +
`{timeInMs, number, milliseconds}\xa0ms`,
/** Label used to identify a value in a table where many individual values are aggregated to a single value, for brevity. "Other resources" could also be read as "the rest of the resources". Resource refers to network resources requested by the browser. */
otherValue: 'Other resources',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
Expand All @@ -41,21 +37,24 @@ const PASS_THRESHOLD_IN_MS = 250;
/** @typedef {import("third-party-web").IEntity} ThirdPartyEntity */

/**
* @typedef {{
* mainThreadTime: number,
* transferSize: number,
* blockingTime: number,
* }} Summary
* @typedef Summary
* @property {number} mainThreadTime
* @property {number} transferSize
* @property {number} blockingTime
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of style changes in this file with no accompanying functional changes anymore. Is it worth keeping the style changes anyway?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changes are good, lets keep

*/

/**
* @typedef {{
* transferSize: number,
* blockingTime: number,
* url: string | LH.IcuMessage,
* }} URLSummary
* @typedef URLSummary
* @property {number} transferSize
* @property {number} blockingTime
* @property {string | LH.IcuMessage} url
*/

/** @typedef SummaryMaps
* @property {Map<ThirdPartyEntity, Summary>} byEntity Map of impact summaries for each entity.
* @property {Map<string, Summary>} byURL Map of impact summaries for each URL.
* @property {Map<ThirdPartyEntity, string[]>} urls Map of URLs under each entity.
*/

/**
* Don't bother showing resources smaller than 4KiB since they're likely to be pixels, which isn't
Expand Down Expand Up @@ -85,7 +84,7 @@ class ThirdPartySummary extends Audit {
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
* @param {Array<LH.Artifacts.TaskNode>} mainThreadTasks
* @param {number} cpuMultiplier
* @return {{byEntity: Map<ThirdPartyEntity, Summary>, byURL: Map<string, Summary>, urls: Map<ThirdPartyEntity, string[]>}}
* @return {SummaryMaps}
*/
static getSummaries(networkRecords, mainThreadTasks, cpuMultiplier) {
/** @type {Map<string, Summary>} */
Expand Down Expand Up @@ -142,7 +141,7 @@ class ThirdPartySummary extends Audit {

/**
* @param {ThirdPartyEntity} entity
* @param {{byEntity: Map<ThirdPartyEntity, Summary>, byURL: Map<string, Summary>, urls: Map<ThirdPartyEntity, string[]>}} summaries
* @param {SummaryMaps} summaries
* @param {Summary} stats
* @return {Array<URLSummary>}
*/
Expand Down Expand Up @@ -179,7 +178,7 @@ class ThirdPartySummary extends Audit {
// we'll replace the tail entries with single remainder entry.
items = items.slice(0, numSubItems);
const remainder = {
url: str_(UIStrings.otherValue),
url: str_(i18n.UIStrings.otherResourcesLabel),
transferSize: stats.transferSize - subitemSummary.transferSize,
blockingTime: stats.blockingTime - subitemSummary.blockingTime,
};
Expand Down Expand Up @@ -237,7 +236,7 @@ class ThirdPartySummary extends Audit {
/* eslint-disable max-len */
{key: 'entity', itemType: 'link', text: str_(UIStrings.columnThirdParty), subItemsHeading: {key: 'url', itemType: 'url'}},
{key: 'transferSize', granularity: 1, itemType: 'bytes', text: str_(i18n.UIStrings.columnTransferSize), subItemsHeading: {key: 'transferSize'}},
{key: 'blockingTime', granularity: 1, itemType: 'ms', text: str_(UIStrings.columnBlockingTime), subItemsHeading: {key: 'blockingTime'}},
{key: 'blockingTime', granularity: 1, itemType: 'ms', text: str_(i18n.UIStrings.columnBlockingTime), subItemsHeading: {key: 'blockingTime'}},
/* eslint-enable max-len */
];

Expand Down
2 changes: 2 additions & 0 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ const defaultConfig = {
'timing-budget',
'resource-summary',
'third-party-summary',
'third-party-facades',
'largest-contentful-paint-element',
'layout-shift-elements',
'long-tasks',
Expand Down Expand Up @@ -467,6 +468,7 @@ const defaultConfig = {
{id: 'timing-budget', weight: 0, group: 'budgets'},
{id: 'resource-summary', weight: 0, group: 'diagnostics'},
{id: 'third-party-summary', weight: 0, group: 'diagnostics'},
{id: 'third-party-facades', weight: 0, group: 'diagnostics'},
{id: 'largest-contentful-paint-element', weight: 0, group: 'diagnostics'},
{id: 'layout-shift-elements', weight: 0, group: 'diagnostics'},
{id: 'uses-passive-event-listeners', weight: 0, group: 'diagnostics'},
Expand Down
Loading