diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap index dd4636eb9237..c164f10bd5de 100644 --- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap +++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap @@ -120,6 +120,9 @@ Object { Object { "path": "offline-start-url", }, + Object { + "path": "resource-summary", + }, Object { "path": "manual/pwa-cross-browser", }, @@ -811,6 +814,11 @@ Object { "id": "font-display", "weight": 0, }, + Object { + "group": "diagnostics", + "id": "resource-summary", + "weight": 0, + }, Object { "id": "network-requests", "weight": 0, diff --git a/lighthouse-core/audits/resource-summary.js b/lighthouse-core/audits/resource-summary.js new file mode 100644 index 000000000000..b39add3ae424 --- /dev/null +++ b/lighthouse-core/audits/resource-summary.js @@ -0,0 +1,103 @@ +/** + * @license Copyright 2019 Google Inc. 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'; + +const Audit = require('./audit.js'); +const ComputedResourceSummary = require('../computed/resource-summary.js'); +const i18n = require('../lib/i18n/i18n.js'); + +const UIStrings = { + /** Imperative title of a Lighthouse audit that tells the user to minimize the size and quantity of resources used to load the page. */ + title: 'Keep request counts low and transfer sizes small', + /** Description of a Lighthouse audit that tells the user that they can setup a budgets for the quantity and size of page resources. No character length limits. */ + description: 'To set budgets for the quantity and size of page resources,' + + ' add a budget.json file.', + /** [ICU Syntax] Label for an audit identifying the number of requests and kilobytes used to load the page. */ + displayValue: `{requestCount, plural, =1 {1 request} other {# requests}}` + + ` • { byteCount, number, bytes } KB`, +}; + +const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings); + +class ResourceSummary extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'resource-summary', + title: str_(UIStrings.title), + description: str_(UIStrings.description), + scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE, + requiredArtifacts: ['devtoolsLogs', 'URL'], + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; + const summary = await ComputedResourceSummary + .request({devtoolsLog, URL: artifacts.URL}, context); + + /** @type {LH.Audit.Details.Table['headings']} */ + const headings = [ + {key: 'label', itemType: 'text', text: 'Resource Type'}, + {key: 'requestCount', itemType: 'numeric', text: 'Requests'}, + {key: 'size', itemType: 'bytes', text: 'Transfer Size'}, + ]; + + + /** @type {Record} */ + const strMappings = { + 'total': str_(i18n.UIStrings.totalResourceType), + 'document': str_(i18n.UIStrings.documentResourceType), + 'script': str_(i18n.UIStrings.scriptResourceType), + 'stylesheet': str_(i18n.UIStrings.stylesheetResourceType), + 'image': str_(i18n.UIStrings.imageResourceType), + 'media': str_(i18n.UIStrings.mediaResourceType), + 'font': str_(i18n.UIStrings.fontResourceType), + 'other': str_(i18n.UIStrings.otherResourceType), + 'third-party': str_(i18n.UIStrings.thirdPartyResourceType), + }; + + const types = /** @type {Array} */ (Object.keys(summary)); + const rows = types.map(type => { + return { + // ResourceType is included as an "id" for ease of use. + // It does not appear directly in the table. + resourceType: type, + label: strMappings[type], + requestCount: summary[type].count, + size: summary[type].size, + }; + }); + // Force third-party to be last, descending by size otherwise + const thirdPartyRow = rows.find(r => r.resourceType === 'third-party') || []; + const otherRows = rows.filter(r => r.resourceType !== 'third-party') + .sort((a, b) => { + return b.size - a.size; + }); + const tableItems = otherRows.concat(thirdPartyRow); + + const tableDetails = Audit.makeTableDetails(headings, tableItems); + + return { + details: tableDetails, + score: 1, + displayValue: str_(UIStrings.displayValue, { + requestCount: summary.total.count, + byteCount: summary.total.size, + }), + }; + } +} + +module.exports = ResourceSummary; +module.exports.UIStrings = UIStrings; diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js index 48951810ba07..5cd33e467d78 100644 --- a/lighthouse-core/config/default-config.js +++ b/lighthouse-core/config/default-config.js @@ -192,6 +192,7 @@ const defaultConfig = { 'main-thread-tasks', 'metrics', 'offline-start-url', + 'resource-summary', 'manual/pwa-cross-browser', 'manual/pwa-page-transitions', 'manual/pwa-each-page-has-url', @@ -378,6 +379,7 @@ const defaultConfig = { {id: 'bootup-time', weight: 0, group: 'diagnostics'}, {id: 'mainthread-work-breakdown', weight: 0, group: 'diagnostics'}, {id: 'font-display', weight: 0, group: 'diagnostics'}, + {id: 'resource-summary', weight: 0, group: 'diagnostics'}, // Audits past this point don't belong to a group and will not be shown automatically {id: 'network-requests', weight: 0}, {id: 'network-rtt', weight: 0}, diff --git a/lighthouse-core/lib/i18n/en-US.json b/lighthouse-core/lib/i18n/en-US.json index 16d73ca6ac0a..02716a56e9d6 100644 --- a/lighthouse-core/lib/i18n/en-US.json +++ b/lighthouse-core/lib/i18n/en-US.json @@ -751,6 +751,18 @@ "message": "Avoid multiple page redirects", "description": "Imperative title of a Lighthouse audit that tells the user to eliminate the redirects taken through multiple URLs to load the page. This is shown in a list of audits that Lighthouse generates." }, + "lighthouse-core/audits/resource-summary.js | description": { + "message": "To set budgets for the quantity and size of page resources, add a budget.json file.", + "description": "Description of a Lighthouse audit that tells the user that they can setup a budgets for the quantity and size of page resources. No character length limits." + }, + "lighthouse-core/audits/resource-summary.js | displayValue": { + "message": "{requestCount, plural, =1 {1 request} other {# requests}} • { byteCount, number, bytes } KB", + "description": "[ICU Syntax] Label for an audit identifying the number of requests and kilobytes used to load the page." + }, + "lighthouse-core/audits/resource-summary.js | title": { + "message": "Keep request counts low and transfer sizes small", + "description": "Imperative title of a Lighthouse audit that tells the user to minimize the size and quantity of resources used to load the page." + }, "lighthouse-core/audits/seo/canonical.js | description": { "message": "Canonical links suggest which URL to show in search results. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/canonical).", "description": "Description of a Lighthouse audit that tells the user *why* they need to have a valid rel=canonical link. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation." @@ -1223,14 +1235,50 @@ "message": "Potential savings of {wastedMs, number, milliseconds} ms", "description": "Label shown per-audit to show how many milliseconds faster the page load could be if the user implemented the suggestions. The `{wastedMs}` placeholder will be replaced with the time duration, shown in milliseconds (e.g. 140 ms)" }, + "lighthouse-core/lib/i18n/i18n.js | documentResourceType": { + "message": "Document", + "description": "Label for a row in a data table; entries will be the total number and byte size of all 'Document' resources loaded by a web page." + }, + "lighthouse-core/lib/i18n/i18n.js | fontResourceType": { + "message": "Font", + "description": "Label for a row in a data table; entries will be the total number and byte size of all 'Font' resources loaded by a web page." + }, + "lighthouse-core/lib/i18n/i18n.js | imageResourceType": { + "message": "Image", + "description": "Label for a row in a data table; entries will be the total number and byte size of all 'Image' resources loaded by a web page." + }, + "lighthouse-core/lib/i18n/i18n.js | mediaResourceType": { + "message": "Media", + "description": "Label for a row in a data table; entries will be the total number and byte size of all 'Media' resources loaded by a web page. 'Media' refers to audio and video files." + }, "lighthouse-core/lib/i18n/i18n.js | ms": { "message": "{timeInMs, number, milliseconds} ms", "description": "Used to show the duration in milliseconds that something lasted. The `{timeInMs}` placeholder will be replaced with the time duration, shown in milliseconds (e.g. 63 ms)" }, + "lighthouse-core/lib/i18n/i18n.js | otherResourceType": { + "message": "Other", + "description": "Label for a row in a data table; entries will be the total number and byte size of all resources loaded by a web page that don't fit into the categories of Document, Script, Stylesheet, Image, Media, & Font." + }, + "lighthouse-core/lib/i18n/i18n.js | scriptResourceType": { + "message": "Script", + "description": "Label for a row in a data table; entries will be the total number and byte size of all 'Script' resources loaded by a web page. 'Script' refers to JavaScript or other files that are executable by a browser." + }, "lighthouse-core/lib/i18n/i18n.js | seconds": { "message": "{timeInMs, number, seconds} s", "description": "Used to show the duration in seconds that something lasted. The {timeInMs} placeholder will be replaced with the time duration, shown in seconds (e.g. 5.2 s)" }, + "lighthouse-core/lib/i18n/i18n.js | stylesheetResourceType": { + "message": "Stylesheet", + "description": "Label for a row in a data table; entries will be the total number and byte size of all 'Stylesheet' resources loaded by a web page. 'Stylesheet' refers to CSS stylesheets." + }, + "lighthouse-core/lib/i18n/i18n.js | thirdPartyResourceType": { + "message": "Third-party", + "description": "Label for a row in a data table; entries will be the total number and byte size of all third-party resources loaded by a web page. 'Third-party resources are items loaded from URLs that aren't controlled by the owner of the web page." + }, + "lighthouse-core/lib/i18n/i18n.js | totalResourceType": { + "message": "Total", + "description": "Label for a row in a data table; entries will be the total number and byte size of all resources loaded by a web page." + }, "lighthouse-core/lib/lh-error.js | badTraceRecording": { "message": "Something went wrong with recording the trace over your page load. Please run Lighthouse again. ({errorCode})", "description": "Error message explaining that the network trace was not able to be recorded for the Lighthouse run." diff --git a/lighthouse-core/lib/i18n/i18n.js b/lighthouse-core/lib/i18n/i18n.js index 9c4ba39347d5..b93423b4a83f 100644 --- a/lighthouse-core/lib/i18n/i18n.js +++ b/lighthouse-core/lib/i18n/i18n.js @@ -56,6 +56,24 @@ const UIStrings = { columnWastedMs: 'Potential Savings', /** Label for the time spent column in data tables, entries will be the number of milliseconds spent during a particular activity */ columnTimeSpent: 'Time Spent', + /** Label for a row in a data table; entries will be the total number and byte size of all resources loaded by a web page. */ + totalResourceType: 'Total', + /** Label for a row in a data table; entries will be the total number and byte size of all 'Document' resources loaded by a web page. */ + documentResourceType: 'Document', + /** Label for a row in a data table; entries will be the total number and byte size of all 'Script' resources loaded by a web page. 'Script' refers to JavaScript or other files that are executable by a browser. */ + scriptResourceType: 'Script', + /** Label for a row in a data table; entries will be the total number and byte size of all 'Stylesheet' resources loaded by a web page. 'Stylesheet' refers to CSS stylesheets. */ + stylesheetResourceType: 'Stylesheet', + /** Label for a row in a data table; entries will be the total number and byte size of all 'Image' resources loaded by a web page. */ + imageResourceType: 'Image', + /** Label for a row in a data table; entries will be the total number and byte size of all 'Media' resources loaded by a web page. 'Media' refers to audio and video files. */ + mediaResourceType: 'Media', + /** Label for a row in a data table; entries will be the total number and byte size of all 'Font' resources loaded by a web page. */ + fontResourceType: 'Font', + /** Label for a row in a data table; entries will be the total number and byte size of all resources loaded by a web page that don't fit into the categories of Document, Script, Stylesheet, Image, Media, & Font.*/ + otherResourceType: 'Other', + /** Label for a row in a data table; entries will be the total number and byte size of all third-party resources loaded by a web page. 'Third-party resources are items loaded from URLs that aren't controlled by the owner of the web page. */ + thirdPartyResourceType: 'Third-party', }; const formats = { diff --git a/lighthouse-core/test/audits/resource-summary-test.js b/lighthouse-core/test/audits/resource-summary-test.js new file mode 100644 index 000000000000..0b745ff38232 --- /dev/null +++ b/lighthouse-core/test/audits/resource-summary-test.js @@ -0,0 +1,87 @@ +/** + * @license Copyright 2019 Google Inc. 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'; + +const ResourceSummaryAudit = require('../../audits/resource-summary.js'); +const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.js'); + +/* eslint-env jest */ + +describe('Performance: Resource summary audit', () => { + let artifacts; + let context; + beforeEach(() => { + context = {computedCache: new Map()}; + + artifacts = { + devtoolsLogs: { + defaultPass: networkRecordsToDevtoolsLog([ + {url: 'http://example.com/file.html', resourceType: 'Document', transferSize: 30}, + {url: 'http://example.com/app.js', resourceType: 'Script', transferSize: 10}, + {url: 'http://third-party.com/script.js', resourceType: 'Script', transferSize: 50}, + {url: 'http://third-party.com/file.jpg', resourceType: 'Image', transferSize: 70}, + ])}, + URL: {requestedUrl: 'https://example.com', finalUrl: 'https://example.com'}, + }; + }); + + it('has three table columns', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + expect(result.details.headings).toHaveLength(3); + }); + + it('has the correct score', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + expect(result.score).toBe(1); + }); + + it('has the correct display value', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + expect(result.displayValue).toBeDisplayString('4 requests • 0 KB'); + }); + + it('includes the correct properties for each table item', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + const item = result.details.items[0]; + expect(item.resourceType).toEqual('total'); + expect(item.label).toBeDisplayString('Total'); + expect(item.requestCount).toBe(4); + expect(item.size).toBe(160); + }); + + it('includes all resource types, regardless of whether page contains them', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + expect(Object.keys(result.details.items)).toHaveLength(9); + }); + + it('it displays "0" if there are no resources of that type', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + const fontItem = result.details.items.find(item => item.resourceType === 'font'); + expect(fontItem.requestCount).toBe(0); + expect(fontItem.size).toBe(0); + }); + + describe('table ordering', () => { + it('except for the last row, it sorts items by size (descending)', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + const items = result.details.items; + items.slice(0, -2).forEach((item, index) => { + expect(item.size).toBeGreaterThanOrEqual(items[index + 1].size); + }); + }); + + it('"Total" is the first row', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + expect(result.details.items[0].resourceType).toBe('total'); + }); + + it('"Third-party" is the last-row', async () => { + const result = await ResourceSummaryAudit.audit(artifacts, context); + const items = result.details.items; + expect(items[items.length - 1].resourceType).toBe('third-party'); + }); + }); +}); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 3a83a0721253..ece7e5480c24 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -1273,6 +1273,90 @@ "explanation": "No usable web app manifest found on page.", "warnings": [] }, + "resource-summary": { + "id": "resource-summary", + "title": "Keep request counts low and transfer sizes small", + "description": "To set budgets for the quantity and size of page resources, add a budget.json file.", + "score": null, + "scoreDisplayMode": "informative", + "displayValue": "18 requests • 157 KB", + "details": { + "type": "table", + "headings": [ + { + "key": "label", + "itemType": "text", + "text": "Resource Type" + }, + { + "key": "requestCount", + "itemType": "numeric", + "text": "Requests" + }, + { + "key": "size", + "itemType": "bytes", + "text": "Transfer Size" + } + ], + "items": [ + { + "resourceType": "total", + "label": "Total", + "requestCount": 18, + "size": 160738 + }, + { + "resourceType": "script", + "label": "Script", + "requestCount": 4, + "size": 103675 + }, + { + "resourceType": "image", + "label": "Image", + "requestCount": 2, + "size": 24741 + }, + { + "resourceType": "document", + "label": "Document", + "requestCount": 3, + "size": 14109 + }, + { + "resourceType": "other", + "label": "Other", + "requestCount": 2, + "size": 12861 + }, + { + "resourceType": "stylesheet", + "label": "Stylesheet", + "requestCount": 7, + "size": 5352 + }, + { + "resourceType": "media", + "label": "Media", + "requestCount": 0, + "size": 0 + }, + { + "resourceType": "font", + "label": "Font", + "requestCount": 0, + "size": 0 + }, + { + "resourceType": "third-party", + "label": "Third-party", + "requestCount": 2, + "size": 30174 + } + ] + } + }, "pwa-cross-browser": { "id": "pwa-cross-browser", "title": "Site works cross-browser", @@ -3123,6 +3207,11 @@ "weight": 0, "group": "diagnostics" }, + { + "id": "resource-summary", + "weight": 0, + "group": "diagnostics" + }, { "id": "network-requests", "weight": 0 @@ -4090,6 +4179,18 @@ "duration": 100, "entryType": "measure" }, + { + "startTime": 0, + "name": "lh:audit:resource-summary", + "duration": 100, + "entryType": "measure" + }, + { + "startTime": 0, + "name": "lh:computed:ResourceSummary", + "duration": 100, + "entryType": "measure" + }, { "startTime": 0, "name": "lh:audit:pwa-cross-browser", @@ -4866,6 +4967,48 @@ "lighthouse-core/audits/network-server-latency.js | description": [ "audits[network-server-latency].description" ], + "lighthouse-core/audits/resource-summary.js | title": [ + "audits[resource-summary].title" + ], + "lighthouse-core/audits/resource-summary.js | description": [ + "audits[resource-summary].description" + ], + "lighthouse-core/audits/resource-summary.js | displayValue": [ + { + "values": { + "requestCount": 18, + "byteCount": 160738 + }, + "path": "audits[resource-summary].displayValue" + } + ], + "lighthouse-core/lib/i18n/i18n.js | totalResourceType": [ + "audits[resource-summary].details.items[0].label" + ], + "lighthouse-core/lib/i18n/i18n.js | scriptResourceType": [ + "audits[resource-summary].details.items[1].label" + ], + "lighthouse-core/lib/i18n/i18n.js | imageResourceType": [ + "audits[resource-summary].details.items[2].label" + ], + "lighthouse-core/lib/i18n/i18n.js | documentResourceType": [ + "audits[resource-summary].details.items[3].label" + ], + "lighthouse-core/lib/i18n/i18n.js | otherResourceType": [ + "audits[resource-summary].details.items[4].label" + ], + "lighthouse-core/lib/i18n/i18n.js | stylesheetResourceType": [ + "audits[resource-summary].details.items[5].label" + ], + "lighthouse-core/lib/i18n/i18n.js | mediaResourceType": [ + "audits[resource-summary].details.items[6].label" + ], + "lighthouse-core/lib/i18n/i18n.js | fontResourceType": [ + "audits[resource-summary].details.items[7].label" + ], + "lighthouse-core/lib/i18n/i18n.js | thirdPartyResourceType": [ + "audits[resource-summary].details.items[8].label" + ], "lighthouse-core/audits/accessibility/accesskeys.js | title": [ "audits.accesskeys.title" ], diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json index 3212277cee01..5f1ec1314c4b 100644 --- a/proto/sample_v2_round_trip.json +++ b/proto/sample_v2_round_trip.json @@ -2141,7 +2141,91 @@ "score": 0.46, "scoreDisplayMode": "numeric", "title": "Eliminate render-blocking resources" - }, + }, + "resource-summary": { + "description": "To set budgets for the quantity and size of page resources, add a budget.json file.", + "details": { + "type": "table", + "headings": [ + { + "key": "label", + "itemType": "text", + "text": "Resource Type" + }, + { + "key": "requestCount", + "itemType": "numeric", + "text": "Requests" + }, + { + "key": "size", + "itemType": "bytes", + "text": "Transfer Size" + } + ], + "items": [ + { + "resourceType": "total", + "label": "Total", + "requestCount": 18, + "size": 160738 + }, + { + "resourceType": "script", + "label": "Script", + "requestCount": 4, + "size": 103675 + }, + { + "resourceType": "image", + "label": "Image", + "requestCount": 2, + "size": 24741 + }, + { + "resourceType": "document", + "label": "Document", + "requestCount": 3, + "size": 14109 + }, + { + "resourceType": "other", + "label": "Other", + "requestCount": 2, + "size": 12861 + }, + { + "resourceType": "stylesheet", + "label": "Stylesheet", + "requestCount": 7, + "size": 5352 + }, + { + "resourceType": "media", + "label": "Media", + "requestCount": 0, + "size": 0 + }, + { + "resourceType": "font", + "label": "Font", + "requestCount": 0, + "size": 0 + }, + { + "resourceType": "third-party", + "label": "Third-party", + "requestCount": 2, + "size": 30174 + } + ] + }, + "displayValue": "18 requests • 157 KB", + "id": "resource-summary", + "score": null, + "scoreDisplayMode": "informative", + "title": "Keep request counts low and transfer sizes small" + }, "robots-txt": { "description": "If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed.", "id": "robots-txt", @@ -3358,7 +3442,12 @@ "group": "diagnostics", "id": "font-display", "weight": 0.0 - }, + }, + { + "group": "diagnostics", + "id": "resource-summary", + "weight": 0 + }, { "id": "network-requests", "weight": 0.0 @@ -3636,24 +3725,24 @@ "finalUrl": "http://localhost:10200/dobetterweb/dbw_tester.html", "i18n": { "rendererFormattedStrings": { - "auditGroupExpandTooltip": "Show audits", - "crcInitialNavigation": "Initial Navigation", - "crcLongestDurationLabel": "Maximum critical path latency:", - "errorLabel": "Error!", - "errorMissingAuditInfo": "Report error: no audit information", - "labDataTitle": "Lab Data", - "lsPerformanceCategoryDescription": "[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.", - "manualAuditsGroupTitle": "Additional items to manually check", - "notApplicableAuditsGroupTitle": "Not applicable", - "opportunityResourceColumnLabel": "Opportunity", - "opportunitySavingsColumnLabel": "Estimated Savings", - "passedAuditsGroupTitle": "Passed audits", - "snippetCollapseButtonLabel": "Collapse snippet", - "snippetExpandButtonLabel": "Expand snippet", - "thirdPartyResourcesLabel": "Show 3rd-party resources", - "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", - "varianceDisclaimer": "Values are estimated and may vary.", - "warningAuditsGroupTitle": "Passed audits but with warnings", + "auditGroupExpandTooltip": "Show audits", + "crcInitialNavigation": "Initial Navigation", + "crcLongestDurationLabel": "Maximum critical path latency:", + "errorLabel": "Error!", + "errorMissingAuditInfo": "Report error: no audit information", + "labDataTitle": "Lab Data", + "lsPerformanceCategoryDescription": "[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.", + "manualAuditsGroupTitle": "Additional items to manually check", + "notApplicableAuditsGroupTitle": "Not applicable", + "opportunityResourceColumnLabel": "Opportunity", + "opportunitySavingsColumnLabel": "Estimated Savings", + "passedAuditsGroupTitle": "Passed audits", + "snippetCollapseButtonLabel": "Collapse snippet", + "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", + "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", + "varianceDisclaimer": "Values are estimated and may vary.", + "warningAuditsGroupTitle": "Passed audits but with warnings", "warningHeader": "Warnings: " } }, @@ -4092,7 +4181,19 @@ "entryType": "measure", "name": "lh:audit:offline-start-url", "startTime": 0.0 - }, + }, + { + "duration": 100, + "entryType": "measure", + "name": "lh:audit:resource-summary", + "startTime": 0.0 + }, + { + "duration": 100, + "entryType": "measure", + "name": "lh:computed:ResourceSummary", + "startTime": 0.0 + }, { "duration": 100.0, "entryType": "measure",