diff --git a/docs/readme.md b/docs/readme.md index 6b5c26393914..cb8f5d110b99 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -110,18 +110,16 @@ As an example, here's a trace-only run that's reporting on user timings and crit } }, - "aggregations": [{ - "name": "Performance Metrics", - "description": "These encapsulate your app's performance.", - "scored": false, - "categorizable": false, - "items": [{ - "audits": { - "user-timings": { "expectedValue": 0, "weight": 1 }, - "critical-request-chains": { "expectedValue": 0, "weight": 1} - } - }] - }] + "categories": { + "performance": { + "name": "Performance Metrics", + "description": "These encapsulate your app's performance.", + "audits": [ + {"id": "user-timings", "weight": 1}, + {"id": "critical-request-chains", "weight": 1} + ] + } + } } ``` diff --git a/lighthouse-cli/test/smokehouse/pwa-config.js b/lighthouse-cli/test/smokehouse/pwa-config.js index ceda5f103f4f..45db5e995371 100644 --- a/lighthouse-cli/test/smokehouse/pwa-config.js +++ b/lighthouse-cli/test/smokehouse/pwa-config.js @@ -57,7 +57,5 @@ module.exports = { 'themed-omnibox', // https://github.com/GoogleChrome/lighthouse/issues/566 // 'cache-start-url' - ], - - aggregations: [] + ] }; diff --git a/lighthouse-cli/types/types.ts b/lighthouse-cli/types/types.ts index ea0d296e1469..54701bc85ac6 100644 --- a/lighthouse-cli/types/types.ts +++ b/lighthouse-cli/types/types.ts @@ -29,22 +29,8 @@ interface AuditFullResults { [metric: string]: AuditFullResult } -interface AggregationResultItem { - overall: number; - name: string; - scored: boolean; - subItems: Array; -} - -interface Aggregation { - name: string; - score: Array; - total: number; -} - interface Results { url: string; - aggregations: Array; audits: AuditFullResults; lighthouseVersion: string; artifacts?: Object; @@ -54,8 +40,6 @@ interface Results { export { Results, - Aggregation, - AggregationResultItem, AuditResult, AuditResults, AuditFullResult, diff --git a/lighthouse-core/aggregator/aggregate.js b/lighthouse-core/aggregator/aggregate.js deleted file mode 100644 index 588d27de4c03..000000000000 --- a/lighthouse-core/aggregator/aggregate.js +++ /dev/null @@ -1,222 +0,0 @@ -/** - * @license - * Copyright 2016 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'; - -class Aggregate { - - /** - * @private - * @param {!Array} results - * @param {!AggregationCriteria} expected - * @return {!Array} - */ - static _filterResultsByAuditNames(results, expected) { - const expectedNames = Object.keys(expected); - return results.filter(r => expectedNames.includes(/** @type {string} */ (r.name))); - } - - /** - * @private - * @param {!AggregationCriteria} expected - * @return {number} - */ - static _getTotalWeight(expected) { - const expectedNames = Object.keys(expected); - const totalWeight = expectedNames.reduce((last, e) => last + (expected[e].weight || 0), 0); - return totalWeight; - } - - /** - * @private - * @param {!Array} results - * @return {!Object} - */ - static _remapResultsByName(results) { - const remapped = {}; - results.forEach(r => { - if (remapped[r.name]) { - throw new Error(`Cannot remap: ${r.name} already exists`); - } - - remapped[r.name] = r; - }); - return remapped; - } - - /** - * Converts each raw audit output to a weighted value for the aggregation. - * @private - * @param {!AuditResult} result The audit's output value. - * @param {!AggregationCriterion} expected The aggregation's expected value and weighting for this result. - * @param {!string} name The name of the audit. - * @return {number} The weighted result. - */ - static _convertToWeight(result, expected, name) { - let weight = 0; - - if (typeof expected === 'undefined' || - typeof expected.expectedValue === 'undefined' || - typeof expected.weight === 'undefined') { - const msg = - `aggregations: ${name} audit does not contain expectedValue or weight properties`; - throw new Error(msg); - } - - // Audit resulted in an error, so doesn't contribute to score. - // TODO: could do NaN instead, as score becomes somewhat meaningless. - if (result.error) { - return 0; - } - - if (typeof result === 'undefined' || - typeof result.score === 'undefined') { - let msg = - `${name} audit result is undefined or does not contain score property`; - if (result && result.debugString) { - msg += ': ' + result.debugString; - } - throw new Error(msg); - } - - if (typeof result.score !== typeof expected.expectedValue) { - const expectedType = typeof expected.expectedValue; - const resultType = typeof result.rawValue; - let msg = `Expected expectedValue of type ${expectedType}, got ${resultType}`; - if (result.debugString) { - msg += ': ' + result.debugString; - } - throw new Error(msg); - } - - switch (typeof expected.expectedValue) { - case 'boolean': - weight = this._convertBooleanToWeight(result.score, - expected.expectedValue, expected.weight); - break; - - case 'number': - weight = this._convertNumberToWeight(result.score, expected.expectedValue, expected.weight); - break; - - default: - weight = 0; - break; - } - - return weight; - } - - /** - * Converts a numeric result to a weight. - * @param {number} resultValue The result. - * @param {number} expectedValue The expected value. - * @param {number} weight The weight to assign. - * @return {number} The final weight. - */ - static _convertNumberToWeight(resultValue, expectedValue, weight) { - return (resultValue / expectedValue) * weight; - } - - /** - * Converts a boolean result to a weight. - * @param {boolean} resultValue The result. - * @param {boolean} expectedValue The expected value. - * @param {number} weight The weight to assign. - * @return {number} The final weight. - */ - static _convertBooleanToWeight(resultValue, expectedValue, weight) { - return (resultValue === expectedValue) ? weight : 0; - } - - /** - * Compares the set of audit results to the expected values. - * @param {!Array} results The audit results. - * @param {!Array} items The aggregation's expected values and weighting. - * @return {!Array} The aggregation score. - */ - static compare(results, items) { - return items.map(item => { - const expectedNames = Object.keys(item.audits); - - // Filter down and remap the results to something more comparable to - // the expected set of results. - const filteredAndRemappedResults = - Aggregate._remapResultsByName( - Aggregate._filterResultsByAuditNames(results, item.audits) - ); - - const subItems = []; - let overallScore = 0; - let maxScore = 1; - - // Step through each item in the expected results, and add them - // to the overall score and add each to the subItems list. - expectedNames.forEach(e => { - if (!filteredAndRemappedResults[e]) { - throw new Error(`aggregations: expected audit results not found under audit name ${e}`); - } - - subItems.push(filteredAndRemappedResults[e].name); - - overallScore += Aggregate._convertToWeight( - filteredAndRemappedResults[e], - item.audits[e], - e); - }); - - maxScore = Aggregate._getTotalWeight(item.audits); - - return { - overall: (overallScore / maxScore), - name: item.name, - description: item.description, - subItems: subItems - }; - }); - } - - /** - * Calculates total score of an aggregate. - * @param {!Array} scores - * @return {number} - */ - static getTotal(scores) { - return scores.reduce((total, s) => total + s.overall, 0) / scores.length; - } - - /** - * Aggregates all the results. - * @param {!Aggregation} aggregation - * @param {!Array} auditResults - * @return {!AggregationResult} - */ - static aggregate(aggregation, auditResults) { - const score = Aggregate.compare(auditResults, aggregation.items); - return { - name: aggregation.name, - description: aggregation.description, - scored: aggregation.scored, - additional: aggregation.additional, - total: (aggregation.scored ? Aggregate.getTotal(score) : null), - categorizable: aggregation.categorizable, - score: score - }; - } -} - -module.exports = Aggregate; diff --git a/lighthouse-core/closure/typedefs/Aggregation.js b/lighthouse-core/closure/typedefs/Aggregation.js deleted file mode 100644 index c0a29470725c..000000000000 --- a/lighthouse-core/closure/typedefs/Aggregation.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @license - * Copyright 2016 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. - */ - -/** - * Typing externs file for collected output of the artifact gatherers stage. - * @externs - */ - -/** - * @struct - * @record - */ -function AggregationCriterion() {} - -/** @type {(boolean|number|undefined)} */ -AggregationCriterion.prototype.rawValue; - -/** @type {number} */ -AggregationCriterion.prototype.weight; - -/** @type {string|undefined} */ -AggregationCriterion.prototype.category; - -/** @type {string|undefined} */ -AggregationCriterion.prototype.description; - -/** - * @typedef {!Object} - */ -var AggregationCriteria; - -/** - * @struct - * @record - */ -function AggregationItem() {} - -/** @type {!Object} */ -AggregationItem.prototype.audits; - -/** @type {string} */ -AggregationItem.prototype.name; - -/** @type {string} */ -AggregationItem.prototype.description; - -/** - * @struct - * @record - */ -function Aggregation() {} - -/** @type {string} */ -Aggregation.prototype.name; - -/** @type {string} */ -Aggregation.prototype.description; - -/** @type {boolean} */ -Aggregation.prototype.scored; - -/** @type {boolean} */ -Aggregation.prototype.categorizable; - -/** @type {!Array} */ -Aggregation.prototype.items; - -/** - * @struct - * @record - */ -function AggregationResultItem() {} - -/** @type {number} */ -AggregationResultItem.prototype.overall; - -/** @type {string} */ -AggregationResultItem.prototype.name; - -/** @type {string} */ -AggregationResultItem.prototype.description; - -/** @type {!Array} */ -AggregationResultItem.prototype.subItems; - -/** - * @struct - * @record - */ -function AggregationResult() {} - -/** @type {string} */ -AggregationResult.prototype.name; - -/** @type {string} */ -AggregationResult.prototype.description; - -/** @type {boolean} */ -AggregationResult.prototype.scored; - -/** @type {boolean} */ -AggregationResult.prototype.categorizable; - -/** @type {!Array} */ -AggregationResult.prototype.score; diff --git a/lighthouse-core/config/config.js b/lighthouse-core/config/config.js index 983e1555f8db..eee01c05e9a3 100644 --- a/lighthouse-core/config/config.js +++ b/lighthouse-core/config/config.js @@ -319,8 +319,6 @@ class Config { throw new Error('config.auditResults must be an array'); } - this._aggregations = configJSON.aggregations || null; - this._audits = Config.requireAudits(configJSON.audits, this._configDir); this._artifacts = expandArtifacts(configJSON.artifacts); this._categories = configJSON.categories; @@ -452,7 +450,7 @@ class Config { } /** - * Creates mapping from audit path (used in config.audits) to audit.name (used in config.aggregations) + * Creates mapping from audit path (used in config.audits) to audit.name (used in categories) * @param {!Object} config Lighthouse config object. * @return {Map} */ @@ -577,11 +575,6 @@ class Config { return this._artifacts; } - /** @type {Array} */ - get aggregations() { - return this._aggregations; - } - /** @type {Object<{audits: !Array<{id: string, weight: number}>}>} */ get categories() { return this._categories; diff --git a/lighthouse-core/config/default.js b/lighthouse-core/config/default.js index 7330fb1cf2d0..8f571b896d4e 100644 --- a/lighthouse-core/config/default.js +++ b/lighthouse-core/config/default.js @@ -136,464 +136,6 @@ module.exports = { "dobetterweb/uses-passive-event-listeners" ], - "aggregations": [{ - "name": "Progressive Web App", - "id": "pwa", - "description": "These audits validate the aspects of a Progressive Web App. They are a subset of the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).", - "scored": true, - "categorizable": true, - "items": [{ - "name": "App can load on offline/flaky connections", - "description": "Ensuring your web app can respond when the network connection is unavailable or flaky is critical to providing your users a good experience. This is achieved through use of a [Service Worker](https://developers.google.com/web/fundamentals/primers/service-worker/).", - "audits": { - "service-worker": { - "expectedValue": true, - "weight": 1 - }, - "works-offline": { - "expectedValue": true, - "weight": 1 - } - } - },{ - "name": "Page load performance is fast", - "description": "Users notice if sites and apps don't perform well. These top-level metrics capture the most important perceived performance concerns.", - "audits": { - "load-fast-enough-for-pwa": { - "expectedValue": true, - "weight": 1 - }, - "first-meaningful-paint": { - "expectedValue": 100, - "weight": 1 - }, - "speed-index-metric": { - "expectedValue": 100, - "weight": 1 - }, - "estimated-input-latency": { - "expectedValue": 100, - "weight": 1 - }, - // "time-to-firstbyte": { - // "expectedValue": true, - // "weight": 1 - // }, - "time-to-interactive": { - "expectedValue": 100, - "weight": 1 - } - } - }, { - "name": "Site is progressively enhanced", - "description": "Progressive enhancement means that everyone can access the basic content and functionality of a page in any browser, and those without certain browser features may receive a reduced but still functional experience.", - "audits": { - "without-javascript": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Network connection is secure", - "description": "Security is an important part of the web for both developers and users. Moving forward, Transport Layer Security (TLS) support will be required for many APIs.", - "audits": { - "is-on-https": { - "expectedValue": true, - "weight": 1 - }, - "redirects-http": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "User can be prompted to Add to Homescreen", - "audits": { - "webapp-install-banner": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Installed web app will launch with custom splash screen", - "audits": { - "splash-screen": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Address bar matches brand colors", - "audits": { - "themed-omnibox": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Design is mobile-friendly", - "description": "Users increasingly experience your app on mobile devices, so it's important to ensure that the experience can adapt to smaller screens.", - "audits": { - "viewport": { - "expectedValue": true, - "weight": 1 - }, - "content-width": { - "expectedValue": true, - "weight": 1 - } - } - }] - }, { - "name": "Best Practices", - "id": "bp", - "description": "We've compiled some recommendations for modernizing your web app and avoiding performance pitfalls. These audits do not affect your score but are worth a look.", - "scored": false, - "categorizable": true, - "items": [{ - "name": "Using modern offline features", - "audits": { - "appcache-manifest": { - "expectedValue": true, - "weight": 1 - }, - "no-websql": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Using modern protocols", - "audits": { - "is-on-https": { - "expectedValue": true, - "weight": 1 - }, - "uses-http2": { - "expectedValue": true, - "description": "Resources made by this application should be severed over HTTP/2 for improved performance.", - "weight": 1 - } - } - }, { - "name": "Using modern CSS features", - "audits": { - "no-old-flexbox": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Using modern JavaScript features", - "audits": { - "uses-passive-event-listeners": { - "expectedValue": true, - "weight": 1 - }, - "no-mutation-events": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Avoiding APIs that harm the user experience", - "audits": { - "no-document-write": { - "expectedValue": true, - "weight": 1 - }, - "external-anchors-use-rel-noopener": { - "expectedValue": true, - "weight": 1 - }, - "geolocation-on-start": { - "expectedValue": true, - "weight": 1 - }, - "notification-on-start": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Avoiding deprecated APIs and browser interventions", - "audits": { - "deprecations": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Other", - "audits": { - "manifest-short-name-length": { - "expectedValue": true, - "weight": 1 - } - } - }] - }, { - "name": "Accessibility", - "id": "accessibility", - "description": "These checks highlight opportunities to [improve the accessibility of your app](https://developers.google.com/web/fundamentals/accessibility).", - "scored": false, - "categorizable": true, - "items": [{ - "name": "Color contrast of elements is satisfactory", - "audits": { - "color-contrast": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Elements are well structured", - "audits": { - "definition-list": { - "expectedValue": true, - "weight": 1 - }, - "dlitem": { - "expectedValue": true, - "weight": 1 - }, - "duplicate-id": { - "expectedValue": true, - "weight": 1 - }, - "list": { - "expectedValue": true, - "weight": 1 - }, - "listitem": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Elements avoid incorrect use of attributes", - "audits": { - "accesskeys": { - "expectedValue": true, - "weight": 1 - }, - "audio-caption": { - "expectedValue": true, - "weight": 1 - }, - "image-alt": { - "expectedValue": true, - "weight": 1 - }, - "input-image-alt": { - "expectedValue": true, - "weight": 1 - }, - "tabindex": { - "expectedValue": true, - "weight": 1 - }, - "td-headers-attr": { - "expectedValue": true, - "weight": 1 - }, - "th-has-data-cells": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Elements applying ARIA follow correct practices", - "audits": { - "aria-allowed-attr": { - "expectedValue": true, - "weight": 1 - }, - "aria-required-attr": { - "expectedValue": true, - "weight": 1 - }, - "aria-required-children": { - "expectedValue": true, - "weight": 1 - }, - "aria-required-parent": { - "expectedValue": true, - "weight": 1 - }, - "aria-roles": { - "expectedValue": true, - "weight": 1 - }, - "aria-valid-attr-value": { - "expectedValue": true, - "weight": 1 - }, - "aria-valid-attr": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Elements describe their contents well", - "audits": { - "document-title": { - "expectedValue": true, - "weight": 1 - }, - "frame-title": { - "expectedValue": true, - "weight": 1 - }, - "label": { - "expectedValue": true, - "weight": 1 - }, - "layout-table": { - "expectedValue": true, - "weight": 1 - }, - "object-alt": { - "expectedValue": true, - "weight": 1 - }, - "video-caption": { - "expectedValue": true, - "weight": 1 - }, - "video-description": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "The page's language is specified and valid", - "audits": { - "html-has-lang": { - "expectedValue": true, - "weight": 1 - }, - "html-lang-valid": { - "expectedValue": true, - "weight": 1 - }, - "valid-lang": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "The document does not use ", - "audits": { - "meta-refresh": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "The element follows best practices", - "audits": { - "meta-viewport": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Links and buttons have discernable names", - "audits": { - "button-name": { - "expectedValue": true, - "weight": 1 - }, - "link-name": { - "expectedValue": true, - "weight": 1 - } - } - }, { - "name": "Repetitive content can be bypassed", - "audits": { - "bypass": { - "expectedValue": true, - "weight": 1 - } - } - }] - }, { - "name": "Performance", - "id": "perf", - "description": "These encapsulate your app's performance.", - "scored": false, - "categorizable": false, - "items": [{ - "audits": { - "total-byte-weight": { - "expectedValue": 100, - "weight": 1 - }, - "dom-size": { - "expectedValue": 100, - "weight": 1 - }, - "uses-optimized-images": { - "expectedValue": true, - "weight": 1 - }, - "uses-request-compression": { - "expectedValue": true, - "weight": 1 - }, - "uses-responsive-images": { - "expectedValue": true, - "weight": 1 - }, - "offscreen-images": { - "expectedValue": true, - "weight": 1 - }, - // "unused-css-rules": { - // "expectedValue": true, - // "weight": 1 - // }, - "link-blocking-first-paint": { - "expectedValue": true, - "weight": 1 - }, - "script-blocking-first-paint": { - "expectedValue": true, - "weight": 1 - }, - "critical-request-chains": { - "expectedValue": true, - "weight": 1 - }, - "user-timings": { - "expectedValue": true, - "weight": 1 - } - } - }] - }, { - "name": "Fancier stuff", - "id": "fancy", - "description": "A list of newer features that you could be using in your app. These audits do not affect your score and are just suggestions.", - "scored": false, - "categorizable": true, - "additional": true, - "items": [{ - "name": "New JavaScript features", - "audits": { - "no-datenow": { - "expectedValue": true, - "weight": 1 - }, - "no-console-time": { - "expectedValue": true, - "weight": 1 - } - } - }] - }], "groups": { "perf-metric": { "title": "Metrics", diff --git a/lighthouse-core/index.js b/lighthouse-core/index.js index 24b4c47a205f..cfabb88077a2 100644 --- a/lighthouse-core/index.js +++ b/lighthouse-core/index.js @@ -27,12 +27,12 @@ const Config = require('./config/config'); * * index.js - the require('lighthouse') hook for Node modules (including the CLI) * - * runner.js - marshalls the actions that must be taken (Gather / Audit / Aggregate) + * runner.js - marshalls the actions that must be taken (Gather / Audit) * config file is used to determine which of these actions are needed * * lighthouse-cli \ * -- index.js \ - * ----- runner.js ----> [Gather / Audit / Aggregate] + * ----- runner.js ----> [Gather / Audit] * lighthouse-extension / * */ diff --git a/lighthouse-core/report/v2/report-generator.js b/lighthouse-core/report/v2/report-generator.js index 00ceb3ce634d..b42ab3f9ea7f 100644 --- a/lighthouse-core/report/v2/report-generator.js +++ b/lighthouse-core/report/v2/report-generator.js @@ -70,31 +70,6 @@ class ReportGeneratorV2 { .join(firstReplacement.replacement); } - /** - * Convert categories into old-school aggregations for old HTML report compat. - * @param {!Array<{name: string, description: string, id: string, score: number, - * audits: !Array<{result: Object}>}>} categories - * @return {!Array} - */ - static _getAggregations(reportCategories) { - return reportCategories.map(category => { - const name = category.name; - const description = category.description; - - return { - name, description, - categorizable: false, - scored: category.id === 'pwa', - total: category.score / 100, - score: [{ - name, description, - overall: category.score / 100, - subItems: category.audits.map(audit => audit.result), - }], - }; - }); - } - /** * Returns the report JSON object with computed scores. * @param {{categories: !Object<{audits: !Array}>}} config @@ -122,9 +97,7 @@ class ReportGeneratorV2 { }); const overallScore = ReportGeneratorV2.arithmeticMean(categories); - // TODO: remove aggregations when old report is fully replaced - const aggregations = ReportGeneratorV2._getAggregations(categories); - return {score: overallScore, categories, aggregations}; + return {score: overallScore, categories}; } /** diff --git a/lighthouse-core/runner.js b/lighthouse-core/runner.js index 145250c8cfce..d3f32b8d7efe 100644 --- a/lighthouse-core/runner.js +++ b/lighthouse-core/runner.js @@ -128,7 +128,7 @@ class Runner { return Promise.reject(err); } - // Format and aggregate results before returning. + // Format and generate JSON report before returning. run = run .then(runResults => { log.log('status', 'Generating results...'); @@ -138,14 +138,12 @@ class Runner { return results; }, {}); - let aggregations = []; let reportCategories = []; let score = 0; if (config.categories) { const reportGenerator = new ReportGeneratorV2(); const report = reportGenerator.generateReportJson(config, resultsById); reportCategories = report.categories; - aggregations = report.aggregations; score = report.score; } @@ -161,7 +159,6 @@ class Runner { score, reportCategories, reportGroups: config.groups, - aggregations }; }); diff --git a/lighthouse-core/test/aggregator/aggregate-test.js b/lighthouse-core/test/aggregator/aggregate-test.js deleted file mode 100644 index 11eb86ba0adb..000000000000 --- a/lighthouse-core/test/aggregator/aggregate-test.js +++ /dev/null @@ -1,504 +0,0 @@ -/** - * Copyright 2016 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 Aggregate = require('../../aggregator/aggregate'); -const assert = require('assert'); - -/* eslint-env mocha */ - -describe('Aggregate', () => { - it('filters empty results', () => { - const a = []; - const b = { - c: 1, f: 2, a: 3 - }; - - const c = Aggregate._filterResultsByAuditNames(a, b); - return assert.equal(c.length, 0); - }); - - it('filters results against an empty set', () => { - const a = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; - const b = {}; - - const c = Aggregate._filterResultsByAuditNames(a, b); - return assert.equal(c.length, 0); - }); - - it('filters results against an expected set', () => { - const a = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; - const b = { - c: 1, f: 2, a: 3 - }; - - const c = Aggregate._filterResultsByAuditNames(a, b); - assert.equal(c[0], a[0]); - return assert.equal(c[1], a[2]); - }); - - it('returns a weight for an empty set', () => { - const a = {}; - - const weight = Aggregate._getTotalWeight(a); - return assert.equal(weight, 0); - }); - - it('generates the correct total weight', () => { - const a = { - x: { - weight: 1 - }, - y: { - weight: 2 - }, - z: { - weight: 3 - } - }; - - const weight = Aggregate._getTotalWeight(a); - return assert.equal(weight, 6); - }); - - it('remaps results to an object', () => { - const a = [{ - name: 'test', - rawValue: 1 - }, { - name: 'test-2', - rawValue: 2 - }, { - name: 'test-3', - rawValue: 3 - }]; - - const remapped = Aggregate._remapResultsByName(a); - return assert.deepEqual(remapped, { - 'test': { - name: 'test', - rawValue: 1 - }, - 'test-2': { - name: 'test-2', - rawValue: 2 - }, - 'test-3': { - name: 'test-3', - rawValue: 3 - } - }); - }); - - it('throws if key already exists during remapping', () => { - const a = [{ - name: 'test', - rawValue: 1 - }, { - name: 'test', - rawValue: 2 - }]; - - return assert.throws(_ => Aggregate._remapResultsByName(a), - 'Cannot remap: test already exists'); - }); - - it('throws for undefined inputs', () => { - return assert.throws(_ => Aggregate._convertToWeight(), 0); - }); - - it('throws for undefined results', () => { - const expected = { - expectedValue: true, - weight: 10 - }; - return assert.throws(_ => Aggregate._convertToWeight(undefined, expected)); - }); - - it('returns a weight of zero for undefined expectations', () => { - const result = { - rawValue: true, - score: true, - displayValue: '' - }; - return assert.throws(_ => Aggregate._convertToWeight(result, undefined)); - }); - - it('returns the correct weight for a boolean result', () => { - const expected = { - expectedValue: true, - weight: 10 - }; - - const result = { - rawValue: true, - score: true, - displayValue: '' - }; - - return assert.equal(Aggregate._convertToWeight(result, expected), 10); - }); - - it('returns the correct weight for a numeric result', () => { - const expected = { - expectedValue: 100, - weight: 10 - }; - - const result = { - rawValue: 50, - score: 50, - displayValue: '50' - }; - - return assert.equal(Aggregate._convertToWeight(result, expected), 5); - }); - - it('throws if weight is missing from the expected', () => { - const expected = { - expectedValue: 100 - }; - - const result = { - rawValue: 50, - score: 50, - displayValue: '50' - }; - - return assert.throws(_ => Aggregate._convertToWeight(result, expected), 0); - }); - - it('returns a weight of zero for other inputs', () => { - const expected = { - expectedValue: [], - weight: 10 - }; - - const result = { - rawValue: [], - score: [], - displayValue: '' - }; - - return assert.equal(Aggregate._convertToWeight(result, expected), 0); - }); - - it('throws if types do not match', () => { - const expected = { - expectedValue: true, - weight: 10 - }; - - const result = { - rawValue: 20, - score: 20, - displayValue: '20' - }; - - return assert.throws(_ => Aggregate._convertToWeight(result, expected)); - }); - - it('returns 0 if audit result was an error', () => { - const expected = { - expectedValue: true, - weight: 1 - }; - - const result = { - rawValue: null, - error: true, - }; - - assert.strictEqual(Aggregate._convertToWeight(result, expected), 0); - }); - - it('scores a set correctly (contributesToScore: true)', () => { - const items = [{ - audits: { - 'test': { - expectedValue: true, - weight: 1 - }, - 'alternate-test': { - expectedValue: 100, - weight: 3 - } - } - }]; - - const results = [{ - name: 'test', - rawValue: false, - score: false, - displayValue: '' - }, { - name: 'alternate-test', - rawValue: 50, - score: 50, - displayValue: '50' - }]; - - return assert.deepEqual(Aggregate.compare(results, items)[0], { - overall: 0.375, - name: undefined, - description: undefined, - subItems: [ - 'test', - 'alternate-test' - ] - }); - }); - - it('scores a set correctly (contributesToScore: false)', () => { - const items = [{ - audits: { - 'test': { - expectedValue: true, - weight: 1 - }, - 'alternate-test': { - expectedValue: 100, - weight: 3 - } - } - }]; - - const results = [{ - name: 'test', - rawValue: false, - score: false, - displayValue: '' - }, { - name: 'alternate-test', - rawValue: 50, - score: 50, - displayValue: '50' - }]; - return assert.deepEqual(Aggregate.compare(results, items)[0], { - overall: 0.375, - name: undefined, - description: undefined, - subItems: [ - 'test', - 'alternate-test' - ] - }); - }); - - it('throws when given a result containing no score property', () => { - const items = [{ - audits: { - test: { - expectedValue: true, - weight: 1 - } - } - }]; - - const results = [{ - name: 'test', - value: 'should be rawValue', - displayValue: '' - }]; - - return assert.throws(_ => Aggregate.compare(results, items)); - }); - - it('throws when given an audit containing no expectedValue property', () => { - const items = [{ - audits: { - test: { - weight: 1 - } - } - }]; - - const results = [{ - name: 'test', - score: false, - displayValue: '' - }]; - - return assert.throws(_ => Aggregate.compare(results, items)); - }); - - it('throws when attempting to aggregate an audit name not in audit results', () => { - const items = [{ - audits: { - 'my-audit-test-name': { - expectedValue: true, - weight: 1 - } - } - }]; - - const results = [{ - name: 'alternate-test', - rawValue: 50, - score: 50, - displayValue: '50', - contributesToScore: true - }]; - - assert.throws(_ => Aggregate.compare(results, items)[0], - /my-audit-test-name/); - }); - - it('filters out non-aggregated audit results correctly', () => { - const items = [{ - audits: { - test: { - expectedValue: true, - weight: 1 - } - } - }]; - - const results = [{ - name: 'test', - rawValue: true, - score: true, - displayValue: 'true', - contributesToScore: true - }, { - name: 'alternate-test', - rawValue: 50, - score: 50, - displayValue: '50', - contributesToScore: true - }]; - - const aggregation = Aggregate.compare(results, items)[0]; - assert.deepEqual(aggregation, { - overall: 1, - name: undefined, - description: undefined, - subItems: [ - 'test' - ] - }); - }); - - it('outputs a score', () => { - const items = [{ - audits: { - test: { - expectedValue: true, - weight: 1 - } - } - }]; - - const results = [{ - name: 'test', - rawValue: true, - score: true, - displayValue: '' - }]; - return assert.equal(Aggregate.compare(results, items)[0].overall, 1); - }); - - it('if audit result is an error it does not contribute to score', () => { - const items = [{ - audits: { - 'test': { - expectedValue: true, - weight: 1 - }, - 'alternate-test': { - expectedValue: 100, - weight: 1 - } - } - }]; - - const errorResult = new Error('error message'); - errorResult.name = 'alternate-test'; - const results = [{ - name: 'test', - rawValue: true, - score: true, - displayValue: '' - }, { - name: 'alternate-test', - rawValue: null, - error: true - }]; - - const aggregate = Aggregate.compare(results, items)[0]; - assert.strictEqual(aggregate.overall, 0.5); - }); - - it('outputs subitems', () => { - const items = [{ - audits: { - test: { - expectedValue: true, - weight: 1 - } - } - }]; - - const results = [{ - name: 'test', - rawValue: true, - score: true, - displayValue: '' - }]; - - return assert.ok(Array.isArray(Aggregate.compare(results, items)[0].subItems)); - }); - - it('aggregates', () => { - // Make a fake aggregation and test it. - const aggregation = { - name: 'name', - description: 'description', - scored: true, - categorizable: true, - items: [{ - audits: { - test: { - expectedValue: true, - weight: 1 - } - } - }] - }; - - const results = [{ - name: 'test', - rawValue: true, - score: true, - displayValue: '' - }]; - - const output = Aggregate.aggregate(aggregation, results); - assert.equal(output.name, aggregation.name); - assert.equal(output.shortName, aggregation.shortName); - assert.equal(output.description, aggregation.description); - assert.equal(output.score[0].overall, 1); - return assert.equal(output.score[0].subItems.length, 1); - }); - - it('counts a total', () => { - const scores = [ - {overall: 1}, - {overall: 0.5} - ]; - assert.equal(Aggregate.getTotal(scores), 0.75); - }); -}); diff --git a/lighthouse-core/test/config/config-test.js b/lighthouse-core/test/config/config-test.js index b20b32937166..362165f03c6d 100644 --- a/lighthouse-core/test/config/config-test.js +++ b/lighthouse-core/test/config/config-test.js @@ -67,7 +67,7 @@ describe('Config', () => { it('uses the default config when no config is provided', () => { const config = new Config(); - assert.deepStrictEqual(origConfig.aggregations, config.aggregations); + assert.deepStrictEqual(origConfig.categories, config.categories); assert.equal(origConfig.audits.length, config.audits.length); }); @@ -129,7 +129,7 @@ describe('Config', () => { assert.equal(configJSON.passes[0].gatherers.length, 2); }); - it('contains new copies of auditResults and aggregations', () => { + it('contains new copies of auditResults', () => { const configJSON = origConfig; configJSON.auditResults = [{ value: 1, @@ -146,10 +146,7 @@ describe('Config', () => { const config = new Config(configJSON); assert.notEqual(config, configJSON, 'Objects are strictly different'); - assert.ok(config.aggregations, 'Aggregations array exists'); assert.ok(config.auditResults, 'Audits array exists'); - assert.deepStrictEqual(config.aggregations, configJSON.aggregations, 'Aggregations match'); - assert.notEqual(config.aggregations, configJSON.aggregations, 'Aggregations not same object'); assert.notEqual(config.auditResults, configJSON.auditResults, 'Audits not same object'); assert.deepStrictEqual(config.auditResults, configJSON.auditResults, 'Audits match'); }); @@ -490,15 +487,15 @@ describe('Config', () => { assert.deepEqual(merged.audits, ['a', 'b', 'c']); }); - it('should merge aggregations', () => { - const configA = {aggregations: [{name: 'A'}, {name: 'B'}]}; - const configB = {aggregations: [{name: 'C'}]}; + it('should merge categories', () => { + const configA = {categories: {A: {name: 'Acat'}, B: {name: 'Bcat'}}}; + const configB = {categories: {C: {name: 'Ccat'}}}; const merged = Config.extendConfigJSON(configA, configB); - assert.deepEqual(merged.aggregations, [ - {name: 'A'}, - {name: 'B'}, - {name: 'C'}, - ]); + assert.deepStrictEqual(merged.categories, { + A: {name: 'Acat'}, + B: {name: 'Bcat'}, + C: {name: 'Ccat'}, + }); }); it('should merge other values', () => { @@ -515,12 +512,12 @@ describe('Config', () => { }); describe('getCategories', () => { - it('returns the IDs & names of the aggregations', () => { + it('returns the IDs & names of the categories', () => { const categories = Config.getCategories(origConfig); assert.equal(Array.isArray(categories), true); assert.equal(categories.length, 4, 'Found the correct number of categories'); - const haveName = categories.every(agg => agg.name.length); - const haveID = categories.every(agg => agg.id.length); + const haveName = categories.every(cat => cat.name.length); + const haveID = categories.every(cat => cat.id.length); assert.equal(haveName === haveID === true, true, 'they have IDs and names'); }); }); @@ -560,7 +557,7 @@ describe('Config', () => { const selectedCategory = origConfig.categories.performance; const auditCount = Object.keys(selectedCategory.audits).length; - assert.equal(config.audits.length, auditCount, '# of audits match aggregation list'); + assert.equal(config.audits.length, auditCount, '# of audits match category list'); }); it('should only run specified audits', () => { diff --git a/lighthouse-core/test/fixtures/dbw_tester-perf-results.json b/lighthouse-core/test/fixtures/dbw_tester-perf-results.json index f5d276cd2231..bb03fa1b1500 100644 --- a/lighthouse-core/test/fixtures/dbw_tester-perf-results.json +++ b/lighthouse-core/test/fixtures/dbw_tester-perf-results.json @@ -2394,1134 +2394,5 @@ "title": "Meta Tags Used Properly", "description": "Screen readers and other assitive technologies require annotations to understand otherwise ambiguous content." } - }, - "aggregations": [ - { - "name": "Performance", - "description": "These encapsulate your app's performance.", - "categorizable": false, - "scored": false, - "total": 0.8195454545454546, - "score": [ - { - "name": "Performance", - "description": "These encapsulate your app's performance.", - "overall": 0.8195454545454546, - "subItems": [ - { - "score": 67, - "displayValue": "3218.1ms", - "rawValue": 3218.1, - "optimalValue": "< 1,600 ms", - "extendedInfo": { - "value": { - "timestamps": { - "navStart": 360903135559, - "fCP": 360906353680, - "fMP": 360906353689, - "onLoad": 360906539771, - "endOfTrace": 360916726147 - }, - "timings": { - "navStart": 0, - "fCP": 3218.121, - "fMP": 3218.13, - "onLoad": 3404.212, - "endOfTrace": 13590.588 - } - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "first-meaningful-paint", - "category": "Performance", - "description": "First meaningful paint", - "helpText": "First meaningful paint measures when the primary content of a page is visible. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)." - }, - { - "score": 78, - "displayValue": "3226", - "rawValue": 3226, - "optimalValue": "< 1,250", - "extendedInfo": { - "formatter": "speedline", - "value": { - "timings": { - "firstVisualChange": 3226, - "visuallyReady": 3226.121999979019, - "visuallyComplete": 3226, - "speedIndex": 3226.121999979019, - "perceptualSpeedIndex": 3226.121999979019 - }, - "timestamps": { - "firstVisualChange": 360906361559, - "visuallyReady": 360906361681, - "visuallyComplete": 360906361559, - "speedIndex": 360906361681, - "perceptualSpeedIndex": 360906361681 - }, - "frames": [ - { - "timestamp": 360903135.559, - "progress": 0 - }, - { - "timestamp": 360906361.681, - "progress": 100 - }, - { - "timestamp": 360906538.904, - "progress": 100 - } - ] - } - }, - "scoringMode": "numeric", - "name": "speed-index-metric", - "category": "Performance", - "description": "Perceptual Speed Index", - "helpText": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index)." - }, - { - "score": 100, - "displayValue": "16ms", - "rawValue": 16, - "optimalValue": "< 50 ms", - "extendedInfo": { - "value": [ - { - "percentile": 0.5, - "time": 16 - }, - { - "percentile": 0.75, - "time": 16 - }, - { - "percentile": 0.9, - "time": 16 - }, - { - "percentile": 0.99, - "time": 89.41036000000713 - }, - { - "percentile": 1, - "time": 193.1729999999934 - } - ], - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "estimated-input-latency", - "category": "Performance", - "description": "Estimated Input Latency", - "helpText": "The score above is an estimate of how long your app takes to respond to user input, in milliseconds. There is a 90% probability that a user encounters this amount of latency, or less. 10% of the time a user can expect additional latency. If your score is higher than Lighthouse's target score, users may perceive your app as laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency)." - }, - { - "score": 76, - "displayValue": "3326.1ms", - "rawValue": 3326.1, - "optimalValue": "< 5,000 ms", - "extendedInfo": { - "value": { - "timings": { - "onLoad": 3404.2120000123978, - "fMP": 3218.13, - "visuallyReady": 3226.122, - "timeToInteractive": 3326.122, - "timeToInteractiveB": 3368.1299999952316, - "timeToInteractiveC": 3218.1299999952316, - "endOfTrace": 13590.587999999523 - }, - "timestamps": { - "onLoad": 360906539771, - "fMP": 360906353689, - "visuallyReady": 360906361681, - "timeToInteractive": 360906461681, - "timeToInteractiveB": 360906503689, - "timeToInteractiveC": 360906353689, - "endOfTrace": 360916726147 - }, - "latencies": { - "timeToInteractive": [ - { - "estLatency": 142.53999998378708, - "startTime": "3226.1" - }, - { - "estLatency": 92.53999998378708, - "startTime": "3276.1" - }, - { - "estLatency": 42.539999983787084, - "startTime": "3326.1" - } - ], - "timeToInteractiveB": [ - { - "estLatency": 143.17299999999972, - "startTime": "3218.1" - }, - { - "estLatency": 100.53199996757462, - "startTime": "3268.1" - }, - { - "estLatency": 50.53199996757462, - "startTime": "3318.1" - }, - { - "estLatency": 16.42571428339819, - "startTime": "3368.1" - } - ], - "timeToInteractiveC": [ - { - "estLatency": 16, - "startTime": "3218.1" - } - ] - }, - "expectedLatencyAtTTI": 42.54 - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "time-to-interactive", - "category": "Performance", - "description": "Time To Interactive (alpha)", - "helpText": "Time to Interactive identifies the time at which your app appears to be ready enough to interact with. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive)." - }, - { - "score": 91, - "displayValue": "3,400ms", - "rawValue": 3402.6619999628065, - "extendedInfo": { - "value": { - "timeInMs": 3402.6619999628065, - "timestamp": 360906538220.99994 - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "first-interactive", - "category": "Performance", - "description": "First Interactive (beta)", - "helpText": "The first point at which necessary scripts of the page have loaded and the CPU is idle enough to handle most user input." - }, - { - "score": 91, - "displayValue": "3,400ms", - "rawValue": 3402.661999938965, - "extendedInfo": { - "value": { - "cpuQuietPeriod": { - "start": 360906538.22099996, - "end": 360916726.147 - }, - "networkQuietPeriod": { - "start": 360906260.27099997, - "end": 360916726.147 - }, - "cpuQuietPeriods": [ - { - "start": 360906538.22099996, - "end": 360916726.147 - } - ], - "networkQuietPeriods": [ - { - "start": 360906260.27099997, - "end": 360916726.147 - } - ], - "timestamp": 360906538220.99994, - "timeInMs": 3402.661999938965 - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "consistently-interactive", - "category": "Performance", - "description": "Consistently Interactive (beta)", - "helpText": "The point at which most network resources have finished loading and the CPU is idle for a prolonged period." - }, - { - "score": 0, - "displayValue": "4 resources delayed first paint by 2236ms", - "rawValue": 2236, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 2236, - "results": [ - { - "url": "/dobetterweb/dbw_tester.css?delay=100", - "totalKb": "1 KB", - "totalMs": "155ms" - }, - { - "url": "/dobetterweb/unknown404.css?delay=200", - "totalKb": "0 KB", - "totalMs": "220ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=2200", - "totalKb": "1 KB", - "totalMs": "2236ms" - }, - { - "url": "/dobetterweb/dbw_partial_a.html?delay=200", - "totalKb": "1 KB", - "totalMs": "369ms" - } - ], - "tableHeadings": { - "url": "URL", - "totalKb": "Size (KB)", - "totalMs": "Delayed Paint By (ms)" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "link-blocking-first-paint", - "category": "Performance", - "description": "Reduce render-blocking stylesheets", - "helpText": "Link elements are blocking the first paint of your page. Consider inlining critical links and deferring non-critical ones. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Size (KB)" - }, - { - "type": "text", - "itemType": "text", - "text": "Delayed Paint By (ms)" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=100" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "155ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/unknown404.css?delay=200" - }, - { - "type": "text", - "text": "0 KB" - }, - { - "type": "text", - "text": "220ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=2200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "2236ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_partial_a.html?delay=200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "369ms" - } - ] - ] - } - }, - { - "score": 65, - "displayValue": "1 resource delayed first paint by 409ms", - "rawValue": 409, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 409, - "results": [ - { - "url": "/dobetterweb/dbw_tester.js", - "totalKb": "2 KB", - "totalMs": "409ms" - } - ], - "tableHeadings": { - "url": "URL", - "totalKb": "Size (KB)", - "totalMs": "Delayed Paint By (ms)" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "script-blocking-first-paint", - "category": "Performance", - "description": "Reduce render-blocking scripts", - "helpText": "Script elements are blocking the first paint of your page. Consider inlining critical scripts and deferring non-critical ones. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Size (KB)" - }, - { - "type": "text", - "itemType": "text", - "text": "Delayed Paint By (ms)" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.js" - }, - { - "type": "text", - "text": "2 KB" - }, - { - "type": "text", - "text": "409ms" - } - ] - ] - } - }, - { - "score": 100, - "displayValue": "", - "rawValue": 0, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 0, - "wastedKb": 0, - "results": [], - "tableHeadings": { - "preview": "", - "url": "URL", - "totalKb": "Original", - "webpSavings": "Savings as WebP", - "jpegSavings": "Savings as JPEG" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "uses-optimized-images", - "category": "Images", - "description": "Optimize images", - "helpText": "Images should be optimized to save network bytes. The following images could have smaller file sizes when compressed with [WebP](https://developers.google.com/speed/webp/) or JPEG at 80 quality. [Learn more about image optimization](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "thumbnail", - "text": "" - }, - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Original" - }, - { - "type": "text", - "itemType": "text", - "text": "Savings as WebP" - }, - { - "type": "text", - "itemType": "text", - "text": "Savings as JPEG" - } - ], - "items": [] - } - }, - { - "score": 90, - "displayValue": "Potential savings of 62 KB (~290ms)", - "rawValue": 290, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 290, - "wastedKb": 62, - "results": [ - { - "url": "/zone.js", - "totalBytes": 71501, - "wastedBytes": 56204, - "wastedPercent": 78.6058936238654, - "potentialSavings": "55 KB (79%)", - "wastedKb": "55 KB", - "wastedMs": "260ms", - "totalKb": "70 KB", - "totalMs": "330ms" - }, - { - "url": "/dobetterweb/dbw_tester.html", - "totalBytes": 10390, - "wastedBytes": 6935, - "wastedPercent": 66.74687199230029, - "potentialSavings": "7 KB (67%)", - "wastedKb": "7 KB", - "wastedMs": "30ms", - "totalKb": "10 KB", - "totalMs": "50ms" - } - ], - "tableHeadings": { - "url": "Uncompressed resource URL", - "totalKb": "Original", - "potentialSavings": "GZIP Savings" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "uses-request-compression", - "category": "Performance", - "description": "Enable text compression", - "helpText": "Text-based responses should be served with compression (gzip, deflate or brotli) to minimize total network bytes. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "Uncompressed resource URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Original" - }, - { - "type": "text", - "itemType": "text", - "text": "GZIP Savings" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/zone.js" - }, - { - "type": "text", - "text": "70 KB" - }, - { - "type": "text", - "text": "55 KB (79%)" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": "10 KB" - }, - { - "type": "text", - "text": "7 KB (67%)" - } - ] - ] - } - }, - { - "score": 100, - "displayValue": "", - "rawValue": 0, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 0, - "wastedKb": 0, - "results": [], - "tableHeadings": { - "preview": "", - "url": "URL", - "totalKb": "Original", - "potentialSavings": "Potential Savings" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "uses-responsive-images", - "category": "Images", - "description": "Properly size images", - "helpText": "Image sizes served should be based on the device display size to save network bytes. Learn more about [responsive images](https://developers.google.com/web/fundamentals/design-and-ui/media/images) and [client hints](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "thumbnail", - "text": "" - }, - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Original" - }, - { - "type": "text", - "itemType": "text", - "text": "Potential Savings" - } - ], - "items": [] - } - }, - { - "score": 100, - "displayValue": "Total size was 130 KB", - "rawValue": 133319, - "optimalValue": "< 1,600 KB", - "extendedInfo": { - "formatter": "table", - "value": { - "results": [ - { - "url": "/zone.js", - "totalBytes": 71654, - "totalKb": "70 KB", - "totalMs": "330ms" - }, - { - "url": "…3.2.1/jquery.min.js", - "totalBytes": 31671, - "totalKb": "31 KB", - "totalMs": "140ms" - }, - { - "url": "/dobetterweb/dbw_tester.html", - "totalBytes": 10511, - "totalKb": "10 KB", - "totalMs": "50ms" - }, - { - "url": "/dobetterweb/dbw_tester.html", - "totalBytes": 10511, - "totalKb": "10 KB", - "totalMs": "50ms" - }, - { - "url": "/dobetterweb/dbw_tester.js", - "totalBytes": 1630, - "totalKb": "2 KB", - "totalMs": "10ms" - }, - { - "url": "/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "totalBytes": 1146, - "totalKb": "1 KB", - "totalMs": "10ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=100", - "totalBytes": 859, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=3000&async=true", - "totalBytes": 859, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "totalBytes": 859, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=2200", - "totalBytes": 859, - "totalKb": "1 KB", - "totalMs": "0ms" - } - ], - "tableHeadings": { - "url": "URL", - "totalKb": "Total Size", - "totalMs": "Transfer Time" - } - } - }, - "scoringMode": "numeric", - "name": "total-byte-weight", - "category": "Network", - "description": "Avoids enormous network payloads", - "helpText": "Network transfer size [costs users real dollars](https://whatdoesmysitecost.com/) and is [highly correlated](http://httparchive.org/interesting.php#onLoad) with long load times. Try to find ways to reduce the size of required files.", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Total Size" - }, - { - "type": "text", - "itemType": "text", - "text": "Transfer Time" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/zone.js" - }, - { - "type": "text", - "text": "70 KB" - }, - { - "type": "text", - "text": "330ms" - } - ], - [ - { - "type": "url", - "text": "…3.2.1/jquery.min.js" - }, - { - "type": "text", - "text": "31 KB" - }, - { - "type": "text", - "text": "140ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": "10 KB" - }, - { - "type": "text", - "text": "50ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": "10 KB" - }, - { - "type": "text", - "text": "50ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.js" - }, - { - "type": "text", - "text": "2 KB" - }, - { - "type": "text", - "text": "10ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_disabled.css?delay=200&isdisabled" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "10ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=100" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=3000&async=true" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?scriptActivated&delay=200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=2200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ] - ] - } - }, - { - "score": 100, - "displayValue": "35 nodes", - "rawValue": 35, - "optimalValue": "< 1,500 nodes", - "extendedInfo": { - "formatter": "cards", - "value": [ - { - "title": "Total DOM Nodes", - "value": "35", - "target": "< 1,500 nodes" - }, - { - "title": "DOM Depth", - "value": "4", - "snippet": "html >\n body >\n div >\n h2", - "target": "< 32" - }, - { - "title": "Maximum Children", - "value": "22", - "snippet": "Element with most children:\nhead", - "target": "< 60 nodes" - } - ] - }, - "scoringMode": "numeric", - "name": "dom-size", - "category": "Performance", - "description": "Avoids an excessive DOM size", - "helpText": "Browser engineers recommend pages contain fewer than ~1,500 DOM nodes. The sweet spot is a tree depth < 32 elements and fewer than 60 children/parent element. A large DOM can increase memory, cause longer [style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/fundamentals/performance/rendering/).", - "details": { - "type": "cards", - "header": { - "type": "text", - "text": "View details" - }, - "items": [ - { - "title": "Total DOM Nodes", - "value": "35", - "target": "< 1,500 nodes" - }, - { - "title": "DOM Depth", - "value": "4", - "snippet": "html >\n body >\n div >\n h2", - "target": "< 32" - }, - { - "title": "Maximum Children", - "value": "22", - "snippet": "Element with most children:\nhead", - "target": "< 60 nodes" - } - ] - } - }, - { - "score": false, - "displayValue": "12", - "rawValue": false, - "optimalValue": 0, - "extendedInfo": { - "formatter": "critical-request-chains", - "value": { - "chains": { - "53431.1": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.html", - "startTime": 360903.137836, - "endTime": 360903.341978, - "responseReceivedTime": 360903.299006, - "transferSize": 10511 - }, - "children": { - "53431.2": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", - "startTime": 360903.334926, - "endTime": 360905.373365, - "responseReceivedTime": 360905.372944, - "transferSize": 859 - }, - "children": {} - }, - "53431.3": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", - "startTime": 360903.337748, - "endTime": 360903.492647, - "responseReceivedTime": 360903.492073, - "transferSize": 859 - }, - "children": {} - }, - "53431.4": { - "request": { - "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200", - "startTime": 360903.337965, - "endTime": 360903.557522, - "responseReceivedTime": 360903.556451, - "transferSize": 139 - }, - "children": {} - }, - "53431.5": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", - "startTime": 360903.338157, - "endTime": 360905.574069, - "responseReceivedTime": 360905.573613, - "transferSize": 859 - }, - "children": {} - }, - "53431.6": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "startTime": 360903.338392, - "endTime": 360903.570907, - "responseReceivedTime": 360903.570548, - "transferSize": 1146 - }, - "children": {} - }, - "53431.7": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200", - "startTime": 360903.339044, - "endTime": 360903.706811, - "responseReceivedTime": 360903.706456, - "transferSize": 770 - }, - "children": {} - }, - "53431.8": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync", - "startTime": 360903.339465, - "endTime": 360903.764529, - "responseReceivedTime": 360903.764156, - "transferSize": 767 - }, - "children": {} - }, - "53431.9": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", - "startTime": 360903.339704, - "endTime": 360906.367565, - "responseReceivedTime": 360906.367106, - "transferSize": 859 - }, - "children": {} - }, - "53431.10": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.js", - "startTime": 360903.340348, - "endTime": 360903.749569, - "responseReceivedTime": 360903.735216, - "transferSize": 1630 - }, - "children": {} - }, - "53431.18": { - "request": { - "url": "http://localhost:10200/zone.js", - "startTime": 360903.349782, - "endTime": 360904.20042, - "responseReceivedTime": 360903.863811, - "transferSize": 71654 - }, - "children": {} - }, - "53431.19": { - "request": { - "url": "http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js", - "startTime": 360903.350326, - "endTime": 360903.743462, - "responseReceivedTime": 360903.577709, - "transferSize": 31671 - }, - "children": {} - }, - "53431.22": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "startTime": 360906.043217, - "endTime": 360906.260271, - "responseReceivedTime": 360906.259792, - "transferSize": 859 - }, - "children": {} - } - } - } - }, - "longestChain": { - "duration": 3229.7290000133216, - "length": 2, - "transferSize": 859 - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "critical-request-chains", - "category": "Performance", - "description": "Critical Request Chains", - "helpText": "The Critical Request Chains below show you what resources are required for first render of this page. Improve page load by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/critical-request-chains)." - }, - { - "score": true, - "displayValue": "0", - "rawValue": true, - "extendedInfo": { - "formatter": "user-timings", - "value": [] - }, - "scoringMode": "binary", - "informative": true, - "name": "user-timings", - "category": "Performance", - "description": "User Timing marks and measures", - "helpText": "Consider instrumenting your app with the User Timing API to create custom, real-world measurements of key user experiences. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/user-timing)." - } - ] - } - ] - } - ] + } } \ No newline at end of file diff --git a/lighthouse-core/test/index-test.js b/lighthouse-core/test/index-test.js index ab6208415a77..336c09df64ef 100644 --- a/lighthouse-core/test/index-test.js +++ b/lighthouse-core/test/index-test.js @@ -89,8 +89,7 @@ describe('Module Tests', function() { }], audits: [ 'fluff' - ], - aggregations: [] + ] }) .then(() => { throw new Error('Should not have resolved'); @@ -99,7 +98,7 @@ describe('Module Tests', function() { }); }); - it('should return formatted audit results when given no aggregations', function() { + it('should return formatted audit results when given no categories', function() { const exampleUrl = 'https://example.com/'; return lighthouse(exampleUrl, { output: 'json' @@ -117,8 +116,8 @@ describe('Module Tests', function() { assert.ok(results.generatedTime); assert.equal(results.url, exampleUrl); assert.equal(results.initialUrl, exampleUrl); - assert.ok(Array.isArray(results.aggregations)); - assert.equal(results.aggregations.length, 0); + assert.ok(Array.isArray(results.reportCategories)); + assert.equal(results.reportCategories.length, 0); assert.ok(results.audits.viewport); assert.ok(results.timing); assert.equal(typeof results.timing.total, 'number'); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 509fe98f5733..071a5122a34e 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -7130,3354 +7130,6 @@ "description": "These audits are required by the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist) but are not automatically checked by Lighthouse. They do not affect your score but please verify them manually." } }, - "aggregations": [ - { - "name": "Progressive Web App", - "description": "These audits validate the aspects of a Progressive Web App. They are a subset¹ of the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).", - "categorizable": false, - "scored": true, - "total": 0.36363636363636365, - "score": [ - { - "name": "Progressive Web App", - "description": "These audits validate the aspects of a Progressive Web App. They are a subset¹ of the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).", - "overall": 0.36363636363636365, - "subItems": [ - { - "score": false, - "displayValue": "", - "rawValue": false, - "scoringMode": "binary", - "name": "service-worker", - "category": "Offline", - "description": "Registers a Service Worker", - "helpText": "The service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/registered-service-worker)." - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "scoringMode": "binary", - "name": "works-offline", - "category": "Offline", - "description": "Responds with a 200 when offline", - "helpText": "If you're building a Progressive Web App, consider using a service worker so that your app can work offline. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-200-when-offline)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "scoringMode": "binary", - "name": "without-javascript", - "category": "JavaScript", - "description": "Contains some content when JavaScript is not available", - "helpText": "Your app should display some content when JavaScript is disabled, even if it's just a warning to the user that JavaScript is required to use the app. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/no-js)." - }, - { - "score": false, - "displayValue": "1 insecure request found", - "rawValue": false, - "extendedInfo": { - "formatter": "url-list", - "value": [ - { - "url": "ajax.googleapis.com/…3.2.1/jquery.min.js" - } - ] - }, - "scoringMode": "binary", - "name": "is-on-https", - "category": "Security", - "description": "Uses HTTPS", - "helpText": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).", - "details": { - "type": "list", - "header": { - "type": "text", - "text": "Insecure URLs:" - }, - "items": [ - { - "type": "text", - "text": "ajax.googleapis.com/…3.2.1/jquery.min.js" - } - ] - } - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "scoringMode": "binary", - "name": "redirects-http", - "category": "Security", - "description": "Redirects HTTP traffic to HTTPS", - "helpText": "If you've already set up HTTPS, make sure that you redirect all HTTP traffic to HTTPS. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-redirects-to-https)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "null", - "value": { - "areLatenciesAll3G": true, - "allRequestLatencies": [ - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "latency": "151.95" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2000&async=true", - "latency": "155.11" - }, - null, - { - "url": "http://localhost:3000/dobetterweb/unknown404.css?delay=200", - "latency": "156.48" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2200", - "latency": "163.39" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "latency": "170.86" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_partial_a.html?delay=200", - "latency": "177.25" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_partial_b.html?delay=200&isasync", - "latency": "183.26" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=3000&async=true", - "latency": "156.48" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "latency": "156.72" - }, - { - "url": "http://localhost:3000/zone.js", - "latency": "164.22" - }, - { - "url": "http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js", - "latency": "837.82" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "latency": "153.81" - }, - { - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "latency": "157.18" - }, - { - "url": "http://localhost:3000/zone.js", - "latency": "155.07" - }, - { - "url": "http://localhost:3000/favicon.ico", - "latency": "154.19" - } - ], - "isFast": true, - "timeToFirstInteractive": 1735.8040000166893 - } - }, - "scoringMode": "binary", - "name": "load-fast-enough-for-pwa", - "category": "PWA", - "description": "Page load is fast enough on 3G", - "helpText": "Satisfied if the Time To Interactive duration is shorter than 10 seconds, as defined by the [PWA Baseline Checklist](https://developers.google.com/web/progressive-web-apps/checklist). Network throttling is required (specifically: RTT latencies >= 150 RTT are expected)." - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "debugString": "Failures: No manifest was fetched, Site does not register a Service Worker, Manifest start_url is not cached by a Service Worker.", - "extendedInfo": { - "value": { - "failures": [ - "No manifest was fetched", - "Site does not register a Service Worker", - "Manifest start_url is not cached by a Service Worker" - ], - "manifestValues": { - "isParseFailure": true, - "parseFailureReason": "No manifest was fetched", - "allChecks": [] - } - }, - "formatter": "null" - }, - "scoringMode": "binary", - "name": "webapp-install-banner", - "category": "PWA", - "description": "User can be prompted to Install the Web App", - "helpText": "While users can manually add your site to their homescreen, the [prompt (aka app install banner)](https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/) will proactively prompt the user to install the app if the various requirements are met and the user has moderate engagement with your site." - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "debugString": "Failures: No manifest was fetched.", - "extendedInfo": { - "value": { - "failures": [ - "No manifest was fetched" - ], - "manifestValues": { - "isParseFailure": true, - "parseFailureReason": "No manifest was fetched", - "allChecks": [] - } - }, - "formatter": "null" - }, - "scoringMode": "binary", - "name": "splash-screen", - "category": "PWA", - "description": "Configured for a custom splash screen", - "helpText": "A default splash screen will be constructed for your app, but satisfying these requirements guarantee a high-quality [splash screen](https://developers.google.com/web/updates/2015/10/splashscreen) that transitions the user from tapping the home screen icon to your app's first paint" - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "debugString": "Failures: No manifest was fetched, No `` tag found.", - "extendedInfo": { - "value": { - "failures": [ - "No manifest was fetched", - "No `` tag found" - ], - "manifestValues": { - "isParseFailure": true, - "parseFailureReason": "No manifest was fetched", - "allChecks": [] - }, - "themeColor": null - }, - "formatter": "null" - }, - "scoringMode": "binary", - "name": "themed-omnibox", - "category": "PWA", - "description": "Address bar matches brand colors", - "helpText": "The browser address bar can be themed to match your site. A `theme-color` [meta tag](https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android) will upgrade the address bar when a user browses the site, and the [manifest theme-color](https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color) will apply the same theme site-wide once it's been added to homescreen." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "debugString": "", - "scoringMode": "binary", - "name": "viewport", - "category": "Mobile Friendly", - "description": "Has a `` tag with `width` or `initial-scale`", - "helpText": "Add a viewport meta tag to optimize your app for mobile screens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/has-viewport-meta-tag)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "debugString": "", - "scoringMode": "binary", - "name": "content-width", - "category": "Mobile Friendly", - "description": "Content is sized correctly for the viewport", - "helpText": "If the width of your app's content doesn't match the width of the viewport, your app might not be optimized for mobile screens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/content-sized-correctly-for-viewport)." - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "scoringMode": "binary", - "informative": true, - "manual": true, - "name": "pwa-cross-browser", - "category": "PWA", - "description": "Site works cross-browser", - "helpText": "To reach the most number of users, sites should work across every major browser." - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "scoringMode": "binary", - "informative": true, - "manual": true, - "name": "pwa-page-transitions", - "category": "PWA", - "description": "Page transitions don't feel like they block on the network", - "helpText": "Transitions should feel snappy as you tap around, even on a slow network, a key to perceived performance." - }, - { - "score": false, - "displayValue": "", - "rawValue": false, - "scoringMode": "binary", - "informative": true, - "manual": true, - "name": "pwa-each-page-has-url", - "category": "PWA", - "description": "Each page has a URL", - "helpText": "Ensure individual pages are deep linkable via the URLs and that URLs are unique for the purpose of shareability on social media." - } - ] - } - ] - }, - { - "name": "Performance", - "description": "These encapsulate your app's performance.", - "categorizable": false, - "scored": false, - "total": 0.9827272727272727, - "score": [ - { - "name": "Performance", - "description": "These encapsulate your app's performance.", - "overall": 0.9827272727272727, - "subItems": [ - { - "score": 98, - "displayValue": "1422.4ms", - "rawValue": 1422.4, - "optimalValue": "< 1,600 ms", - "extendedInfo": { - "value": { - "timestamps": { - "navStart": 482337143550, - "fCP": 482338565967, - "fMP": 482338565978 - }, - "timings": { - "navStart": 0, - "fCP": 1422.417, - "fMP": 1422.428 - } - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "first-meaningful-paint", - "category": "Performance", - "description": "First meaningful paint", - "helpText": "First meaningful paint measures when the primary content of a page is visible. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint)." - }, - { - "score": 97, - "displayValue": "1433", - "rawValue": 1433, - "optimalValue": "< 1,250", - "extendedInfo": { - "formatter": "speedline", - "value": { - "timings": { - "firstVisualChange": 1433, - "visuallyComplete": 1433, - "speedIndex": 1433.2870000004768, - "perceptualSpeedIndex": 1433.2870000004768 - }, - "timestamps": { - "firstVisualChange": 482338576550, - "visuallyComplete": 482338576550, - "speedIndex": 482338576837, - "perceptualSpeedIndex": 482338576837 - }, - "frames": [ - { - "timestamp": 482337143.55, - "progress": 0 - }, - { - "timestamp": 482338576.837, - "progress": 100 - }, - { - "timestamp": 482338585.176, - "progress": 100 - }, - { - "timestamp": 482338897.487, - "progress": 100 - } - ] - } - }, - "scoringMode": "numeric", - "name": "speed-index-metric", - "category": "Performance", - "description": "Perceptual Speed Index", - "helpText": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index)." - }, - { - "score": 100, - "displayValue": "16ms", - "rawValue": 16, - "optimalValue": "< 50 ms", - "extendedInfo": { - "value": [ - { - "percentile": 0.5, - "time": 16 - }, - { - "percentile": 0.75, - "time": 16 - }, - { - "percentile": 0.9, - "time": 16 - }, - { - "percentile": 0.99, - "time": 82.478669999472 - }, - { - "percentile": 1, - "time": 156.88399999999183 - } - ], - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "estimated-input-latency", - "category": "Performance", - "description": "Estimated Input Latency", - "helpText": "The score above is an estimate of how long your app takes to respond to user input, in milliseconds. There is a 90% probability that a user encounters this amount of latency, or less. 10% of the time a user can expect additional latency. If your score is higher than Lighthouse's target score, users may perceive your app as laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency)." - }, - { - "score": 97, - "displayValue": "1683.3ms", - "rawValue": 1683.3, - "optimalValue": "< 5,000 ms", - "extendedInfo": { - "value": { - "timings": { - "onLoad": 1729.5439999699593, - "fMP": 1422.428, - "visuallyReady": 1433.287, - "timeToInteractive": 1683.287, - "timeToInteractiveB": 1672.4279999732971, - "timeToInteractiveC": 1422.4279999732971, - "endOfTrace": 8858.657000005245 - }, - "timestamps": { - "onLoad": 482338873094, - "fMP": 482338565978, - "visuallyReady": 482338576837, - "timeToInteractive": 482338826837, - "timeToInteractiveB": 482338815978, - "timeToInteractiveC": 482338565978, - "endOfTrace": 482346002207 - }, - "latencies": { - "timeToInteractive": [ - { - "estLatency": 106.88399999999996, - "startTime": "1433.3" - }, - { - "estLatency": 106.88399999999996, - "startTime": "1483.3" - }, - { - "estLatency": 106.88400000000001, - "startTime": "1533.3" - }, - { - "estLatency": 106.88400000000001, - "startTime": "1583.3" - }, - { - "estLatency": 68.51700001621248, - "startTime": "1633.3" - }, - { - "estLatency": 19.120666672070815, - "startTime": "1683.3" - } - ], - "timeToInteractiveB": [ - { - "estLatency": 106.88400000000013, - "startTime": "1422.4" - }, - { - "estLatency": 106.88399999999996, - "startTime": "1472.4" - }, - { - "estLatency": 106.88399999999996, - "startTime": "1522.4" - }, - { - "estLatency": 106.88400000000001, - "startTime": "1572.4" - }, - { - "estLatency": 79.3760000433922, - "startTime": "1622.4" - }, - { - "estLatency": 29.376000043392196, - "startTime": "1672.4" - } - ], - "timeToInteractiveC": [ - { - "estLatency": 16, - "startTime": "1422.4" - } - ] - }, - "expectedLatencyAtTTI": 19.121 - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "time-to-interactive", - "category": "Performance", - "description": "Time To Interactive (alpha)", - "helpText": "Time to Interactive identifies the time at which your app appears to be ready enough to interact with. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/time-to-interactive)." - }, - { - "score": 99, - "displayValue": "1,740ms", - "rawValue": 1735.8040000166893, - "extendedInfo": { - "value": { - "timeInMs": 1735.8040000166893, - "timestamp": 482338879354.00006 - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "first-interactive", - "category": "Performance", - "description": "First Interactive (beta)", - "helpText": "The first point at which necessary scripts of the page have loaded and the CPU is idle enough to handle most user input." - }, - { - "score": 99, - "displayValue": "1,740ms", - "rawValue": 1735.8040000610351, - "extendedInfo": { - "value": { - "cpuQuietPeriod": { - "start": 482338879.35400003, - "end": 482346002.207 - }, - "networkQuietPeriod": { - "start": 482338404.903, - "end": 482346002.207 - }, - "cpuQuietPeriods": [ - { - "start": 482338879.35400003, - "end": 482346002.207 - } - ], - "networkQuietPeriods": [ - { - "start": 482338404.903, - "end": 482346002.207 - } - ], - "timestamp": 482338879354.00006, - "timeInMs": 1735.8040000610351 - }, - "formatter": "null" - }, - "scoringMode": "numeric", - "name": "consistently-interactive", - "category": "Performance", - "description": "Consistently Interactive (beta)", - "helpText": "The point at which most network resources have finished loading and the CPU is idle for a prolonged period." - }, - { - "score": 90, - "displayValue": "3 resources delayed first paint by 180ms", - "rawValue": 180, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 180, - "results": [ - { - "url": "/dobetterweb/unknown404.css?delay=200", - "totalKb": "0 KB", - "totalMs": "161ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=2200", - "totalKb": "1 KB", - "totalMs": "165ms" - }, - { - "url": "/dobetterweb/dbw_partial_a.html?delay=200", - "totalKb": "1 KB", - "totalMs": "180ms" - } - ], - "tableHeadings": { - "url": "URL", - "totalKb": "Size (KB)", - "totalMs": "Delayed Paint By (ms)" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "link-blocking-first-paint", - "category": "Performance", - "description": "Reduce render-blocking stylesheets", - "helpText": "Link elements are blocking the first paint of your page. Consider inlining critical links and deferring non-critical ones. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Size (KB)" - }, - { - "type": "text", - "itemType": "text", - "text": "Delayed Paint By (ms)" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/dobetterweb/unknown404.css?delay=200" - }, - { - "type": "text", - "text": "0 KB" - }, - { - "type": "text", - "text": "161ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=2200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "165ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_partial_a.html?delay=200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "180ms" - } - ] - ] - } - }, - { - "score": 65, - "displayValue": "1 resource delayed first paint by 318ms", - "rawValue": 318, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 318, - "results": [ - { - "url": "/dobetterweb/dbw_tester.js", - "totalKb": "2 KB", - "totalMs": "318ms" - } - ], - "tableHeadings": { - "url": "URL", - "totalKb": "Size (KB)", - "totalMs": "Delayed Paint By (ms)" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "script-blocking-first-paint", - "category": "Performance", - "description": "Reduce render-blocking scripts", - "helpText": "Script elements are blocking the first paint of your page. Consider inlining critical scripts and deferring non-critical ones. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Size (KB)" - }, - { - "type": "text", - "itemType": "text", - "text": "Delayed Paint By (ms)" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.js" - }, - { - "type": "text", - "text": "2 KB" - }, - { - "type": "text", - "text": "318ms" - } - ] - ] - } - }, - { - "score": 100, - "displayValue": "", - "rawValue": 0, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 0, - "wastedKb": 0, - "results": [], - "tableHeadings": { - "preview": "", - "url": "URL", - "totalKb": "Original", - "webpSavings": "Savings as WebP", - "jpegSavings": "Savings as JPEG" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "uses-optimized-images", - "category": "Images", - "description": "Optimize images", - "helpText": "Images should be optimized to save network bytes. The following images could have smaller file sizes when compressed with [WebP](https://developers.google.com/speed/webp/) or JPEG at 80 quality. [Learn more about image optimization](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "thumbnail", - "text": "" - }, - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Original" - }, - { - "type": "text", - "itemType": "text", - "text": "Savings as WebP" - }, - { - "type": "text", - "itemType": "text", - "text": "Savings as JPEG" - } - ], - "items": [] - } - }, - { - "score": 90, - "displayValue": "Potential savings of 7 KB (~30ms)", - "rawValue": 30, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 30, - "wastedKb": 7, - "results": [ - { - "url": "/dobetterweb/dbw_tester.html", - "totalBytes": 10390, - "wastedBytes": 6935, - "wastedPercent": 66.74687199230029, - "potentialSavings": "7 KB (67%)", - "wastedKb": "7 KB", - "wastedMs": "30ms", - "totalKb": "10 KB", - "totalMs": "40ms" - } - ], - "tableHeadings": { - "url": "Uncompressed resource URL", - "totalKb": "Original", - "potentialSavings": "GZIP Savings" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "uses-request-compression", - "category": "Performance", - "description": "Enable text compression", - "helpText": "Text-based responses should be served with compression (gzip, deflate or brotli) to minimize total network bytes. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "Uncompressed resource URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Original" - }, - { - "type": "text", - "itemType": "text", - "text": "GZIP Savings" - } - ], - "items": [ - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": "10 KB" - }, - { - "type": "text", - "text": "7 KB (67%)" - } - ] - ] - } - }, - { - "score": 100, - "displayValue": "", - "rawValue": 0, - "extendedInfo": { - "formatter": "table", - "value": { - "wastedMs": 0, - "wastedKb": 0, - "results": [], - "tableHeadings": { - "preview": "", - "url": "URL", - "totalKb": "Original", - "potentialSavings": "Potential Savings" - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "uses-responsive-images", - "category": "Images", - "description": "Properly size images", - "helpText": "Image sizes served should be based on the device display size to save network bytes. Learn more about [responsive images](https://developers.google.com/web/fundamentals/design-and-ui/media/images) and [client hints](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "thumbnail", - "text": "" - }, - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Original" - }, - { - "type": "text", - "itemType": "text", - "text": "Potential Savings" - } - ], - "items": [] - } - }, - { - "score": 100, - "displayValue": "Total size was 51 KB", - "rawValue": 52569, - "optimalValue": "< 1,600 KB", - "extendedInfo": { - "formatter": "table", - "value": { - "results": [ - { - "url": "…3.2.1/jquery.min.js", - "totalBytes": 30874, - "totalKb": "30 KB", - "totalMs": "130ms" - }, - { - "url": "/dobetterweb/dbw_tester.html", - "totalBytes": 10664, - "totalKb": "10 KB", - "totalMs": "40ms" - }, - { - "url": "/dobetterweb/dbw_tester.js", - "totalBytes": 1749, - "totalKb": "2 KB", - "totalMs": "10ms" - }, - { - "url": "/favicon.ico", - "totalBytes": 1616, - "totalKb": "2 KB", - "totalMs": "10ms" - }, - { - "url": "/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "totalBytes": 1273, - "totalKb": "1 KB", - "totalMs": "10ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=2200", - "totalBytes": 984, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "totalBytes": 984, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=3000&async=true", - "totalBytes": 984, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_tester.css?delay=2000&async=true", - "totalBytes": 984, - "totalKb": "1 KB", - "totalMs": "0ms" - }, - { - "url": "/dobetterweb/dbw_partial_a.html?delay=200", - "totalBytes": 920, - "totalKb": "1 KB", - "totalMs": "0ms" - } - ], - "tableHeadings": { - "url": "URL", - "totalKb": "Total Size", - "totalMs": "Transfer Time" - } - } - }, - "scoringMode": "numeric", - "name": "total-byte-weight", - "category": "Network", - "description": "Avoids enormous network payloads", - "helpText": "Network transfer size [costs users real dollars](https://whatdoesmysitecost.com/) and is [highly correlated](http://httparchive.org/interesting.php#onLoad) with long load times. Try to find ways to reduce the size of required files.", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "url", - "text": "URL" - }, - { - "type": "text", - "itemType": "text", - "text": "Total Size" - }, - { - "type": "text", - "itemType": "text", - "text": "Transfer Time" - } - ], - "items": [ - [ - { - "type": "url", - "text": "…3.2.1/jquery.min.js" - }, - { - "type": "text", - "text": "30 KB" - }, - { - "type": "text", - "text": "130ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": "10 KB" - }, - { - "type": "text", - "text": "40ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.js" - }, - { - "type": "text", - "text": "2 KB" - }, - { - "type": "text", - "text": "10ms" - } - ], - [ - { - "type": "url", - "text": "/favicon.ico" - }, - { - "type": "text", - "text": "2 KB" - }, - { - "type": "text", - "text": "10ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_disabled.css?delay=200&isdisabled" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "10ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=2200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?scriptActivated&delay=200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=3000&async=true" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_tester.css?delay=2000&async=true" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ], - [ - { - "type": "url", - "text": "/dobetterweb/dbw_partial_a.html?delay=200" - }, - { - "type": "text", - "text": "1 KB" - }, - { - "type": "text", - "text": "0ms" - } - ] - ] - } - }, - { - "score": 100, - "displayValue": "35 nodes", - "rawValue": 35, - "optimalValue": "< 1,500 nodes", - "extendedInfo": { - "formatter": "cards", - "value": [ - { - "title": "Total DOM Nodes", - "value": "35", - "target": "< 1,500 nodes" - }, - { - "title": "DOM Depth", - "value": "4", - "snippet": "html >\n body >\n div >\n h2", - "target": "< 32" - }, - { - "title": "Maximum Children", - "value": "22", - "snippet": "Element with most children:\nhead", - "target": "< 60 nodes" - } - ] - }, - "scoringMode": "numeric", - "name": "dom-size", - "category": "Performance", - "description": "Avoids an excessive DOM size", - "helpText": "Browser engineers recommend pages contain fewer than ~1,500 DOM nodes. The sweet spot is a tree depth < 32 elements and fewer than 60 children/parent element. A large DOM can increase memory, cause longer [style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/fundamentals/performance/rendering/).", - "details": { - "type": "cards", - "header": { - "type": "text", - "text": "View details" - }, - "items": [ - { - "title": "Total DOM Nodes", - "value": "35", - "target": "< 1,500 nodes" - }, - { - "title": "DOM Depth", - "value": "4", - "snippet": "html >\n body >\n div >\n h2", - "target": "< 32" - }, - { - "title": "Maximum Children", - "value": "22", - "snippet": "Element with most children:\nhead", - "target": "< 60 nodes" - } - ] - } - }, - { - "score": false, - "displayValue": "13", - "rawValue": false, - "optimalValue": 0, - "extendedInfo": { - "formatter": "critical-request-chains", - "value": { - "chains": { - "68289.1": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "startTime": 482337.146085, - "endTime": 482337.352812, - "responseReceivedTime": 482337.302538, - "transferSize": 10664 - }, - "children": { - "68289.2": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2000&async=true", - "startTime": 482337.376297, - "endTime": 482337.532551, - "responseReceivedTime": 482337.531899, - "transferSize": 984 - }, - "children": {} - }, - "68289.3": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", - "startTime": 482337.379707, - "endTime": 482337.39337, - "responseReceivedTime": -1, - "transferSize": 0 - }, - "children": {} - }, - "68289.4": { - "request": { - "url": "http://localhost:3000/dobetterweb/unknown404.css?delay=200", - "startTime": 482337.380844, - "endTime": 482337.541672, - "responseReceivedTime": 482337.538673, - "transferSize": 133 - }, - "children": {} - }, - "68289.5": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2200", - "startTime": 482337.381144, - "endTime": 482337.546151, - "responseReceivedTime": 482337.545695, - "transferSize": 984 - }, - "children": {} - }, - "68289.6": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "startTime": 482337.381999, - "endTime": 482337.55391, - "responseReceivedTime": 482337.55344, - "transferSize": 1273 - }, - "children": {} - }, - "68289.7": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_partial_a.html?delay=200", - "startTime": 482337.382781, - "endTime": 482337.56081, - "responseReceivedTime": 482337.560413, - "transferSize": 920 - }, - "children": {} - }, - "68289.8": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_partial_b.html?delay=200&isasync", - "startTime": 482337.383554, - "endTime": 482337.567984, - "responseReceivedTime": 482337.567541, - "transferSize": 917 - }, - "children": {} - }, - "68289.9": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=3000&async=true", - "startTime": 482337.384843, - "endTime": 482337.689976, - "responseReceivedTime": 482337.689377, - "transferSize": 984 - }, - "children": {} - }, - "68289.10": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "startTime": 482337.385661, - "endTime": 482337.70328, - "responseReceivedTime": 482337.69617, - "transferSize": 1749 - }, - "children": {} - }, - "68289.11": { - "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482337.386495, - "endTime": 482338.489729, - "responseReceivedTime": 482337.710627, - "transferSize": 133 - }, - "children": {} - }, - "68289.12": { - "request": { - "url": "http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js", - "startTime": 482337.386939, - "endTime": 482338.369126, - "responseReceivedTime": 482338.22519, - "transferSize": 30874 - }, - "children": {} - }, - "68289.22": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "startTime": 482338.24998, - "endTime": 482338.404903, - "responseReceivedTime": 482338.404314, - "transferSize": 984 - }, - "children": {} - }, - "68289.24": { - "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482338.577149, - "endTime": 482338.735018, - "responseReceivedTime": 482338.733016, - "transferSize": 133 - }, - "children": {} - } - } - } - }, - "longestChain": { - "duration": 1588.9329999918118, - "length": 2, - "transferSize": 133 - } - } - }, - "scoringMode": "binary", - "informative": true, - "name": "critical-request-chains", - "category": "Performance", - "description": "Critical Request Chains", - "helpText": "The Critical Request Chains below show you what resources are required for first render of this page. Improve page load by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/critical-request-chains).", - "details": { - "type": "criticalrequestchain", - "header": { - "type": "text", - "text": "View critical network waterfall:" - }, - "chains": { - "68289.1": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "startTime": 482337.146085, - "endTime": 482337.352812, - "responseReceivedTime": 482337.302538, - "transferSize": 10664 - }, - "children": { - "68289.2": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2000&async=true", - "startTime": 482337.376297, - "endTime": 482337.532551, - "responseReceivedTime": 482337.531899, - "transferSize": 984 - }, - "children": {} - }, - "68289.3": { - "request": { - "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", - "startTime": 482337.379707, - "endTime": 482337.39337, - "responseReceivedTime": -1, - "transferSize": 0 - }, - "children": {} - }, - "68289.4": { - "request": { - "url": "http://localhost:3000/dobetterweb/unknown404.css?delay=200", - "startTime": 482337.380844, - "endTime": 482337.541672, - "responseReceivedTime": 482337.538673, - "transferSize": 133 - }, - "children": {} - }, - "68289.5": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2200", - "startTime": 482337.381144, - "endTime": 482337.546151, - "responseReceivedTime": 482337.545695, - "transferSize": 984 - }, - "children": {} - }, - "68289.6": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "startTime": 482337.381999, - "endTime": 482337.55391, - "responseReceivedTime": 482337.55344, - "transferSize": 1273 - }, - "children": {} - }, - "68289.7": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_partial_a.html?delay=200", - "startTime": 482337.382781, - "endTime": 482337.56081, - "responseReceivedTime": 482337.560413, - "transferSize": 920 - }, - "children": {} - }, - "68289.8": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_partial_b.html?delay=200&isasync", - "startTime": 482337.383554, - "endTime": 482337.567984, - "responseReceivedTime": 482337.567541, - "transferSize": 917 - }, - "children": {} - }, - "68289.9": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=3000&async=true", - "startTime": 482337.384843, - "endTime": 482337.689976, - "responseReceivedTime": 482337.689377, - "transferSize": 984 - }, - "children": {} - }, - "68289.10": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "startTime": 482337.385661, - "endTime": 482337.70328, - "responseReceivedTime": 482337.69617, - "transferSize": 1749 - }, - "children": {} - }, - "68289.11": { - "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482337.386495, - "endTime": 482338.489729, - "responseReceivedTime": 482337.710627, - "transferSize": 133 - }, - "children": {} - }, - "68289.12": { - "request": { - "url": "http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js", - "startTime": 482337.386939, - "endTime": 482338.369126, - "responseReceivedTime": 482338.22519, - "transferSize": 30874 - }, - "children": {} - }, - "68289.22": { - "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "startTime": 482338.24998, - "endTime": 482338.404903, - "responseReceivedTime": 482338.404314, - "transferSize": 984 - }, - "children": {} - }, - "68289.24": { - "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482338.577149, - "endTime": 482338.735018, - "responseReceivedTime": 482338.733016, - "transferSize": 133 - }, - "children": {} - } - } - } - }, - "longestChain": { - "duration": 1588.9329999918118, - "length": 2, - "transferSize": 133 - } - } - }, - { - "score": true, - "displayValue": "0", - "rawValue": true, - "extendedInfo": { - "formatter": "user-timings", - "value": [] - }, - "scoringMode": "binary", - "informative": true, - "name": "user-timings", - "category": "Performance", - "description": "User Timing marks and measures", - "helpText": "Consider instrumenting your app with the User Timing API to create custom, real-world measurements of key user experiences. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/user-timing).", - "details": { - "type": "table", - "header": "View Details", - "itemHeaders": [ - { - "type": "text", - "itemType": "text", - "text": "Name" - }, - { - "type": "text", - "itemType": "text", - "text": "Type" - }, - { - "type": "text", - "itemType": "text", - "text": "Time" - } - ], - "items": [] - } - } - ] - } - ] - }, - { - "name": "Accessibility", - "description": "These checks highlight opportunities to [improve the accessibility of your app](https://developers.google.com/web/fundamentals/accessibility).", - "categorizable": false, - "scored": false, - "total": 0.942857142857143, - "score": [ - { - "name": "Accessibility", - "description": "These checks highlight opportunities to [improve the accessibility of your app](https://developers.google.com/web/fundamentals/accessibility).", - "overall": 0.942857142857143, - "subItems": [ - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "accesskeys", - "category": "Accessibility", - "description": "`[accesskey]` values are unique.", - "helpText": "`accesskey` attributes allow the user to quickly activate or focus part of the page.Using the same `accesskey` more than once could lead to a confusing experience." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-allowed-attr", - "category": "Accessibility", - "description": "`[aria-*]` attributes match their roles.", - "helpText": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/aria-allowed-attributes)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-required-attr", - "category": "Accessibility", - "description": "`[role]`s have all required `[aria-*]` attributes.", - "helpText": "Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/required-aria-attributes)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-required-children", - "category": "Accessibility", - "description": "`[role]`s that require child `[role]`s contain them.", - "helpText": "Some ARIA parent roles require specific roles on their children to perform their accessibility function." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-required-parent", - "category": "Accessibility", - "description": "`[role]`s are contained by their required parent element.", - "helpText": "Some ARIA roles require specific roles on their parent element to perform their accessibility function." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-roles", - "category": "Accessibility", - "description": "`[role]` values are valid.", - "helpText": "ARIA roles require specific values to perform their accessibility function." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-valid-attr-value", - "category": "Accessibility", - "description": "`[aria-*]` attributes have valid values.", - "helpText": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/valid-aria-values)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "aria-valid-attr", - "category": "Accessibility", - "description": "`[aria-*]` attributes are valid and not misspelled.", - "helpText": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/valid-aria-attributes)." - }, - { - "score": true, - "displayValue": "", - "rawValue": true, - "extendedInfo": { - "formatter": "accessibility" - }, - "scoringMode": "binary", - "name": "audio-caption", - "category": "Accessibility", - "description": "`