diff --git a/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js b/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js index 9d23ae867716..edfc18cc4635 100644 --- a/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js +++ b/lighthouse-cli/test/smokehouse/dobetterweb/dbw-expectations.js @@ -179,9 +179,7 @@ module.exports = [ details: { items: { 0: { - 2: { - text: /^480 x 57/, - }, + displayedAspectRatio: /^480 x 57/, }, length: 1, }, diff --git a/lighthouse-core/audits/audit.js b/lighthouse-core/audits/audit.js index 90ecd29de703..ae179e191697 100644 --- a/lighthouse-core/audits/audit.js +++ b/lighthouse-core/audits/audit.js @@ -70,54 +70,27 @@ class Audit { }); } - /** - * Table cells will use the type specified in headings[x].itemType. However a custom type - * can be provided: results[x].propName = {type: 'code', text: '...'} - * @param {!Audit.Headings} headings - * @param {!Array>} results - * @return {!Array} - */ - static makeTableRows(headings, results) { - const tableRows = results.map(item => { - return headings.map(heading => { - const value = item[heading.key]; - if (typeof value === 'object' && value && value.type) return value; - - return { - type: heading.itemType, - text: value, - }; - }); - }); - return tableRows; - } - - /** - * @param {!Audit.Headings} headings - * @return {!Array} - */ - static makeTableHeaders(headings) { - return headings.map(heading => ({ - type: 'text', - itemKey: heading.key, - itemType: heading.itemType, - text: heading.text, - })); - } - /** * @param {!Audit.Headings} headings * @param {!Array>} results + * @param {!DetailsRenderer.DetailsSummary} summary * @return {!DetailsRenderer.DetailsJSON} */ - static makeTableDetails(headings, results) { - const tableHeaders = Audit.makeTableHeaders(headings); - const tableRows = Audit.makeTableRows(headings, results); + static makeTableDetails(headings, results, summary) { + if (results.length === 0) { + return { + type: 'table', + headings: [], + items: [], + summary, + }; + } + return { type: 'table', - header: 'View Details', - itemHeaders: tableHeaders, - items: tableRows, + headings: headings, + items: results, + summary, }; } @@ -172,7 +145,6 @@ module.exports = Audit; * @typedef {Object} Audit.Heading * @property {string} key * @property {string} itemType - * @property {string} itemKey * @property {string} text */ diff --git a/lighthouse-core/audits/bootup-time.js b/lighthouse-core/audits/bootup-time.js index d98aa32c417e..16e8aaf5de0a 100644 --- a/lighthouse-core/audits/bootup-time.js +++ b/lighthouse-core/audits/bootup-time.js @@ -95,13 +95,14 @@ class BootupTime extends Audit { .filter(result => result.sum >= THRESHOLD_IN_MS) .sort((a, b) => b.sum - a.sum); - const tableDetails = BootupTime.makeTableDetails(headings, results); + const summary = {wastedMs: totalBootupTime}; + const details = BootupTime.makeTableDetails(headings, results, summary); return { score: totalBootupTime < 2000, rawValue: totalBootupTime, displayValue: Util.formatMilliseconds(totalBootupTime), - details: tableDetails, + details, extendedInfo: { value: extendedInfo, }, diff --git a/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js b/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js index c7085f361156..e37b4b208e57 100644 --- a/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +++ b/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js @@ -28,31 +28,14 @@ class UnusedBytes extends Audit { else return 0; } - /** - * @param {number} bytes - * @return {string} - */ - static bytesDetails(bytes) { - return { - type: 'bytes', - value: bytes, - displayUnit: 'kb', - granularity: 1, - }; - } - /** * @param {number} bytes * @param {number} networkThroughput measured in bytes/second * @return {string} */ - static bytesToMsDetails(bytes, networkThroughput) { + static bytesToMs(bytes, networkThroughput) { const milliseconds = bytes / networkThroughput * 1000; - return { - type: 'ms', - value: milliseconds, - granularity: 10, - }; + return milliseconds; } /** @@ -109,10 +92,8 @@ class UnusedBytes extends Audit { const debugString = result.debugString; const results = result.results .map(item => { - item.wastedKb = this.bytesDetails(item.wastedBytes); - item.wastedMs = this.bytesToMsDetails(item.wastedBytes, networkThroughput); - item.totalKb = this.bytesDetails(item.totalBytes); - item.totalMs = this.bytesToMsDetails(item.totalBytes, networkThroughput); + item.wastedMs = this.bytesToMs(item.wastedBytes, networkThroughput); + item.totalMs = this.bytesToMs(item.totalBytes, networkThroughput); return item; }) .sort((itemA, itemB) => itemB.wastedBytes - itemA.wastedBytes); @@ -126,7 +107,11 @@ class UnusedBytes extends Audit { displayValue = `Potential savings of ${wastedBytes} bytes`; } - const tableDetails = Audit.makeTableDetails(result.headings, results); + const summary = { + wastedMs, + wastedBytes, + }; + const details = Audit.makeTableDetails(result.headings, results, summary); return { debugString, @@ -140,7 +125,7 @@ class UnusedBytes extends Audit { results, }, }, - details: tableDetails, + details, }; } diff --git a/lighthouse-core/audits/byte-efficiency/offscreen-images.js b/lighthouse-core/audits/byte-efficiency/offscreen-images.js index 337e1052e5ae..a874a7edb892 100644 --- a/lighthouse-core/audits/byte-efficiency/offscreen-images.js +++ b/lighthouse-core/audits/byte-efficiency/offscreen-images.js @@ -28,7 +28,8 @@ class OffscreenImages extends ByteEfficiencyAudit { name: 'offscreen-images', description: 'Offscreen images', informative: true, - helpText: 'Consider lazy-loading offscreen and hidden images to improve page load speed ' + + helpText: + 'Consider lazy-loading offscreen and hidden images to improve page load speed ' + 'and time to interactive. ' + '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/offscreen-images).', requiredArtifacts: ['ImageUsage', 'ViewportDimensions', 'traces', 'devtoolsLogs'], @@ -72,11 +73,6 @@ class OffscreenImages extends ByteEfficiencyAudit { return { url, - preview: { - type: 'thumbnail', - url: image.networkRecord.url, - mimeType: image.networkRecord.mimeType, - }, requestStartTime: image.networkRecord.startTime, totalBytes, wastedBytes, @@ -107,9 +103,9 @@ class OffscreenImages extends ByteEfficiencyAudit { } // If an image was used more than once, warn only about its least wasteful usage - const existing = results.get(processed.preview.url); + const existing = results.get(processed.url); if (!existing || existing.wastedBytes > processed.wastedBytes) { - results.set(processed.preview.url, processed); + results.set(processed.url, processed); } return results; @@ -118,17 +114,24 @@ class OffscreenImages extends ByteEfficiencyAudit { return artifacts.requestFirstInteractive(trace).then(firstInteractive => { const ttiTimestamp = firstInteractive.timestamp / 1000000; const results = Array.from(resultsMap.values()).filter(item => { - const isWasteful = item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES && - item.wastedPercent > IGNORE_THRESHOLD_IN_PERCENT; + const isWasteful = + item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES && + item.wastedPercent > IGNORE_THRESHOLD_IN_PERCENT; const loadedEarly = item.requestStartTime < ttiTimestamp; return isWasteful && loadedEarly; }); const headings = [ - {key: 'preview', itemType: 'thumbnail', text: ''}, + {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + { + key: 'wastedBytes', + itemType: 'bytes', + displayUnit: 'kb', + granularity: 1, + text: 'Potential Savings', + }, ]; return { diff --git a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js index cf90b7bf6b39..407f77e0fa03 100644 --- a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js +++ b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js @@ -6,6 +6,7 @@ 'use strict'; const ByteEfficiencyAudit = require('./byte-efficiency-audit'); +const Util = require('../../report/v2/renderer/util'); // Parameters for log-normal CDF scoring. See https://www.desmos.com/calculator/gpmjeykbwr // ~75th and ~90th percentiles http://httparchive.org/interesting.php?a=All&l=Feb%201%202017&s=All#bytesTotal @@ -22,9 +23,9 @@ class TotalByteWeight extends ByteEfficiencyAudit { description: 'Avoids enormous network payloads', failureDescription: 'Has enormous network payloads', helpText: - 'Large network payloads cost users real money and are highly correlated with ' + - 'long load times. [Learn ' + - 'more](https://developers.google.com/web/tools/lighthouse/audits/network-payloads).', + 'Large network payloads cost users real money and are highly correlated with ' + + 'long load times. [Learn ' + + 'more](https://developers.google.com/web/tools/lighthouse/audits/network-payloads).', scoringMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC, requiredArtifacts: ['devtoolsLogs'], }; @@ -50,8 +51,7 @@ class TotalByteWeight extends ByteEfficiencyAudit { const result = { url: record.url, totalBytes: record.transferSize, - totalKb: ByteEfficiencyAudit.bytesDetails(record.transferSize), - totalMs: ByteEfficiencyAudit.bytesToMsDetails(record.transferSize, networkThroughput), + totalMs: ByteEfficiencyAudit.bytesToMs(record.transferSize, networkThroughput), }; totalBytes += result.totalBytes; @@ -60,7 +60,6 @@ class TotalByteWeight extends ByteEfficiencyAudit { const totalCompletedRequests = results.length; results = results.sort((itemA, itemB) => itemB.totalBytes - itemA.totalBytes).slice(0, 10); - // Use the CDF of a log-normal distribution for scoring. // <= 1600KB: score≈100 // 4000KB: score=50 @@ -73,8 +72,14 @@ class TotalByteWeight extends ByteEfficiencyAudit { const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Total Size'}, - {key: 'totalMs', itemType: 'text', text: 'Transfer Time'}, + { + key: 'totalBytes', + itemType: 'bytes', + displayUnit: 'kb', + granularity: 1, + text: 'Total Size', + }, + {key: 'totalMs', itemType: 'ms', text: 'Transfer Time'}, ]; const tableDetails = ByteEfficiencyAudit.makeTableDetails(headings, results); @@ -82,7 +87,7 @@ class TotalByteWeight extends ByteEfficiencyAudit { return { score, rawValue: totalBytes, - displayValue: `Total size was ${Math.round(totalBytes / 1024)} KB`, + displayValue: `Total size was ${Util.formatBytesToKB(totalBytes, 1)}`, extendedInfo: { value: { results, diff --git a/lighthouse-core/audits/byte-efficiency/unminified-css.js b/lighthouse-core/audits/byte-efficiency/unminified-css.js index 35dfff8d2061..220de0646a8f 100644 --- a/lighthouse-core/audits/byte-efficiency/unminified-css.js +++ b/lighthouse-core/audits/byte-efficiency/unminified-css.js @@ -103,7 +103,7 @@ class UnminifiedCSS extends ByteEfficiencyAudit { let url = stylesheet.header.sourceURL; if (!url || url === pageUrl) { const contentPreview = UnusedCSSRules.determineContentPreview(stylesheet.content); - url = {type: 'code', text: contentPreview}; + url = {type: 'code', value: contentPreview}; } const totalBytes = ByteEfficiencyAudit.estimateTransferSize(networkRecord, content.length, @@ -144,8 +144,9 @@ class UnminifiedCSS extends ByteEfficiencyAudit { results, headings: [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ], }; } diff --git a/lighthouse-core/audits/byte-efficiency/unminified-javascript.js b/lighthouse-core/audits/byte-efficiency/unminified-javascript.js index 07703761185b..9a71397d81c4 100644 --- a/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +++ b/lighthouse-core/audits/byte-efficiency/unminified-javascript.js @@ -95,8 +95,9 @@ class UnminifiedJavaScript extends ByteEfficiencyAudit { debugString, headings: [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ], }; } diff --git a/lighthouse-core/audits/byte-efficiency/unused-css-rules.js b/lighthouse-core/audits/byte-efficiency/unused-css-rules.js index f78770f1ad1d..68ed1033c0a4 100644 --- a/lighthouse-core/audits/byte-efficiency/unused-css-rules.js +++ b/lighthouse-core/audits/byte-efficiency/unused-css-rules.js @@ -139,7 +139,7 @@ class UnusedCSSRules extends ByteEfficiencyAudit { let url = stylesheetInfo.header.sourceURL; if (!url || url === pageUrl) { const contentPreview = UnusedCSSRules.determineContentPreview(stylesheetInfo.content); - url = {type: 'code', text: contentPreview}; + url = {type: 'code', value: contentPreview}; } const usage = UnusedCSSRules.computeUsage(stylesheetInfo); @@ -166,8 +166,9 @@ class UnusedCSSRules extends ByteEfficiencyAudit { const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ]; return { diff --git a/lighthouse-core/audits/byte-efficiency/unused-javascript.js b/lighthouse-core/audits/byte-efficiency/unused-javascript.js index d9572d39b122..cf6fd07a39b9 100644 --- a/lighthouse-core/audits/byte-efficiency/unused-javascript.js +++ b/lighthouse-core/audits/byte-efficiency/unused-javascript.js @@ -111,8 +111,9 @@ class UnusedJavaScript extends ByteEfficiencyAudit { results, headings: [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ], }; } diff --git a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js index 9a40d9cdb318..dd4618006442 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js +++ b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js @@ -7,7 +7,7 @@ const assert = require('assert'); const parseCacheControl = require('parse-cache-control'); -const ByteEfficiencyAudit = require('./byte-efficiency-audit'); +const Audit = require('../audit'); const WebInspector = require('../../lib/web-inspector'); const URL = require('../../lib/url-shim'); @@ -18,7 +18,7 @@ const IGNORE_THRESHOLD_IN_PERCENT = 0.925; const SCORING_POINT_OF_DIMINISHING_RETURNS = 4; // 4 KB const SCORING_MEDIAN = 768; // 768 KB -class CacheHeaders extends ByteEfficiencyAudit { +class CacheHeaders extends Audit { /** * @return {!AuditMeta} */ @@ -31,7 +31,7 @@ class CacheHeaders extends ByteEfficiencyAudit { helpText: 'A long cache lifetime can speed up repeat visits to your page. ' + '[Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control).', - scoringMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC, + scoringMode: Audit.SCORING_MODES.NUMERIC, requiredArtifacts: ['devtoolsLogs'], }; } @@ -157,7 +157,7 @@ class CacheHeaders extends ByteEfficiencyAudit { * @return {!AuditResult} */ static audit(artifacts) { - const devtoolsLogs = artifacts.devtoolsLogs[ByteEfficiencyAudit.DEFAULT_PASS]; + const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; return artifacts.requestNetworkRecords(devtoolsLogs).then(records => { const results = []; let queryStringCount = 0; @@ -186,13 +186,7 @@ class CacheHeaders extends ByteEfficiencyAudit { const url = URL.elideDataURI(record._url); const totalBytes = record._transferSize; - const totalKb = ByteEfficiencyAudit.bytesDetails(totalBytes); const wastedBytes = (1 - cacheHitProbability) * totalBytes; - const cacheLifetimeDisplay = { - type: 'ms', - value: cacheLifetimeInSeconds, - displayUnit: 'duration', - }; totalWastedBytes += wastedBytes; if (url.includes('?')) queryStringCount++; @@ -201,9 +195,7 @@ class CacheHeaders extends ByteEfficiencyAudit { url, cacheControl, cacheLifetimeInSeconds, - cacheLifetimeDisplay, cacheHitProbability, - totalKb, totalBytes, wastedBytes, }); @@ -217,7 +209,7 @@ class CacheHeaders extends ByteEfficiencyAudit { // <= 4KB: score≈100 // 768KB: score=50 // >= 4600KB: score≈5 - const score = ByteEfficiencyAudit.computeLogNormalScore( + const score = Audit.computeLogNormalScore( totalWastedBytes / 1024, SCORING_POINT_OF_DIMINISHING_RETURNS, SCORING_MEDIAN @@ -225,11 +217,13 @@ class CacheHeaders extends ByteEfficiencyAudit { const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'cacheLifetimeDisplay', itemType: 'text', text: 'Cache TTL'}, - {key: 'totalKb', itemType: 'text', text: 'Size (KB)'}, + {key: 'cacheLifetimeInSeconds', itemType: 'ms', text: 'Cache TTL', displayUnit: 'duration'}, + {key: 'totalBytes', itemType: 'bytes', text: 'Size (KB)', displayUnit: 'kb', + granularity: 1}, ]; - const tableDetails = ByteEfficiencyAudit.makeTableDetails(headings, results); + const summary = {wastedBytes: totalWastedBytes}; + const details = Audit.makeTableDetails(headings, results, summary); return { score, @@ -241,7 +235,7 @@ class CacheHeaders extends ByteEfficiencyAudit { queryStringCount, }, }, - details: tableDetails, + details, }; }); } diff --git a/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js b/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js index 3d5359c23626..2aff63451945 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js @@ -65,7 +65,6 @@ class UsesOptimizedImages extends ByteEfficiencyAudit { url, fromProtocol: image.fromProtocol, isCrossOrigin: !image.isSameOrigin, - preview: {url: image.url, mimeType: image.mimeType, type: 'thumbnail'}, totalBytes: image.originalSize, wastedBytes: jpegSavings.bytes, }); @@ -78,10 +77,11 @@ class UsesOptimizedImages extends ByteEfficiencyAudit { } const headings = [ - {key: 'preview', itemType: 'thumbnail', text: ''}, + {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ]; return { diff --git a/lighthouse-core/audits/byte-efficiency/uses-request-compression.js b/lighthouse-core/audits/byte-efficiency/uses-request-compression.js index c48371f7aeee..60bb718a3a56 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-request-compression.js +++ b/lighthouse-core/audits/byte-efficiency/uses-request-compression.js @@ -71,8 +71,9 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit { const headings = [ {key: 'url', itemType: 'url', text: 'Uncompressed resource URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'GZIP Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'GZIP Savings'}, ]; return { diff --git a/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js b/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js index 2883be8408e9..cd6c0ddabb2d 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js @@ -62,11 +62,6 @@ class UsesResponsiveImages extends ByteEfficiencyAudit { return { url, - preview: { - type: 'thumbnail', - url: image.networkRecord.url, - mimeType: image.networkRecord.mimeType, - }, totalBytes, wastedBytes, wastedPercent: 100 * wastedRatio, @@ -100,9 +95,9 @@ class UsesResponsiveImages extends ByteEfficiencyAudit { } // Don't warn about an image that was later used appropriately - const existing = resultsMap.get(processed.preview.url); + const existing = resultsMap.get(processed.url); if (!existing || existing.wastedBytes > processed.wastedBytes) { - resultsMap.set(processed.preview.url, processed); + resultsMap.set(processed.url, processed); } }); @@ -110,10 +105,11 @@ class UsesResponsiveImages extends ByteEfficiencyAudit { .filter(item => item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES); const headings = [ - {key: 'preview', itemType: 'thumbnail', text: ''}, + {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ]; return { diff --git a/lighthouse-core/audits/byte-efficiency/uses-webp-images.js b/lighthouse-core/audits/byte-efficiency/uses-webp-images.js index 97f53caec286..ad5b6a8d991d 100644 --- a/lighthouse-core/audits/byte-efficiency/uses-webp-images.js +++ b/lighthouse-core/audits/byte-efficiency/uses-webp-images.js @@ -54,7 +54,6 @@ class UsesWebPImages extends ByteEfficiencyAudit { url, fromProtocol: image.fromProtocol, isCrossOrigin: !image.isSameOrigin, - preview: {url: image.url, mimeType: image.mimeType, type: 'thumbnail'}, totalBytes: image.originalSize, wastedBytes: webpSavings.bytes, }); @@ -67,10 +66,11 @@ class UsesWebPImages extends ByteEfficiencyAudit { } const headings = [ - {key: 'preview', itemType: 'thumbnail', text: ''}, + {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Original'}, - {key: 'wastedKb', itemType: 'text', text: 'Potential Savings'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: 'Original'}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, + text: 'Potential Savings'}, ]; return { diff --git a/lighthouse-core/audits/deprecations.js b/lighthouse-core/audits/deprecations.js index 63c77d0d5c01..99df0d6d190e 100644 --- a/lighthouse-core/audits/deprecations.js +++ b/lighthouse-core/audits/deprecations.js @@ -38,16 +38,15 @@ class Deprecations extends Audit { const deprecations = entries.filter(log => log.entry.source === 'deprecation').map(log => { return { - type: 'code', - text: log.entry.text, - url: log.entry.url, + value: log.entry.text, + url: log.entry.url || '', source: log.entry.source, lineNumber: log.entry.lineNumber, }; }); const headings = [ - {key: 'text', itemType: 'code', text: 'Deprecation / Warning'}, + {key: 'value', itemType: 'code', text: 'Deprecation / Warning'}, {key: 'url', itemType: 'url', text: 'URL'}, {key: 'lineNumber', itemType: 'text', text: 'Line'}, ]; diff --git a/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js b/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js index 9b7907004f02..79317351efe9 100644 --- a/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js +++ b/lighthouse-core/audits/dobetterweb/link-blocking-first-paint.js @@ -65,15 +65,11 @@ class LinkBlockingFirstPaintAudit extends Audit { return { url: item.tag.url, - totalKb: ByteEfficiencyAudit.bytesDetails(item.transferSize), - totalMs: { - type: 'ms', - value: (item.endTime - startTime) * 1000, - granularity: 1, - }, + totalBytes: item.transferSize, + wastedMs: (item.endTime - startTime) * 1000, }; }) - .sort((a, b) => b.totalMs.value - a.totalMs.value); + .sort((a, b) => b.wastedMs - a.wastedMs); const rawDelayTime = Math.round((endTime - startTime) * 1000); const delayTime = Util.formatMilliseconds(rawDelayTime, 1); @@ -86,11 +82,13 @@ class LinkBlockingFirstPaintAudit extends Audit { const headings = [ {key: 'url', itemType: 'url', text: 'URL'}, - {key: 'totalKb', itemType: 'text', text: 'Size (KB)'}, - {key: 'totalMs', itemType: 'text', text: 'Delayed Paint By (ms)'}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 0.01, + text: 'Size (KB)'}, + {key: 'wastedMs', itemType: 'ms', text: 'Delayed Paint By (ms)', granularity: 1}, ]; - const tableDetails = Audit.makeTableDetails(headings, results); + const summary = {wastedMs: rawDelayTime}; + const details = Audit.makeTableDetails(headings, results, summary); return { displayValue, @@ -98,11 +96,10 @@ class LinkBlockingFirstPaintAudit extends Audit { rawValue: rawDelayTime, extendedInfo: { value: { - wastedMs: delayTime, results, }, }, - details: tableDetails, + details, }; } diff --git a/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js b/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js index 2a8070fd4bc8..a41cd01413c3 100644 --- a/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js +++ b/lighthouse-core/audits/dobetterweb/no-vulnerable-libraries.js @@ -160,7 +160,7 @@ class NoVulnerableLibrariesAudit extends Audit { {key: 'vulnCount', itemType: 'text', text: 'Vulnerability Count'}, {key: 'highestSeverity', itemType: 'text', text: 'Highest Severity'}, ]; - const details = Audit.makeTableDetails(headings, finalVulns); + const details = Audit.makeTableDetails(headings, finalVulns, {}); return { rawValue: totalVulns === 0, diff --git a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js index db9e7a91a0a3..1affd909acc7 100644 --- a/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js +++ b/lighthouse-core/audits/dobetterweb/password-inputs-can-be-pasted-into.js @@ -38,11 +38,11 @@ class PasswordInputsCanBePastedIntoAudit extends Audit { type: 'list', header: { type: 'text', - text: 'Password inputs that prevent pasting into', + value: 'Password inputs that prevent pasting into', }, items: passwordInputsWithPreventedPaste.map(input => ({ type: 'text', - text: input.snippet, + value: input.snippet, })), }, }; diff --git a/lighthouse-core/audits/font-display.js b/lighthouse-core/audits/font-display.js index 9a8a7bd0bf70..f3bf95047772 100644 --- a/lighthouse-core/audits/font-display.js +++ b/lighthouse-core/audits/font-display.js @@ -6,7 +6,6 @@ 'use strict'; const Audit = require('./audit'); -const Util = require('../report/v2/renderer/util'); const WebInspector = require('../lib/web-inspector'); const allowedFontFaceDisplays = ['block', 'fallback', 'optional', 'swap']; @@ -59,13 +58,13 @@ class FontDisplay extends Audit { return { url: record.url, - wastedTime: Util.formatMilliseconds(wastedTime, 1), + wastedTime, }; }); const headings = [ {key: 'url', itemType: 'url', text: 'Font URL'}, - {key: 'wastedTime', itemType: 'text', text: 'Font download time'}, + {key: 'wastedTime', itemType: 'ms', granularity: 1, text: 'Font download time'}, ]; const details = Audit.makeTableDetails(headings, results); diff --git a/lighthouse-core/audits/image-aspect-ratio.js b/lighthouse-core/audits/image-aspect-ratio.js index 73a73bfda820..c32cea09c7b9 100644 --- a/lighthouse-core/audits/image-aspect-ratio.js +++ b/lighthouse-core/audits/image-aspect-ratio.js @@ -47,11 +47,6 @@ class ImageAspectRatio extends Audit { return { url, - preview: { - type: 'thumbnail', - url: image.networkRecord.url, - mimeType: image.networkRecord.mimeType, - }, displayedAspectRatio: `${image.width} x ${image.height} (${displayedAspectRatio.toFixed(2)})`, actualAspectRatio: `${image.naturalWidth} x ${image.naturalHeight} @@ -89,7 +84,7 @@ class ImageAspectRatio extends Audit { }); const headings = [ - {key: 'preview', itemType: 'thumbnail', text: ''}, + {key: 'url', itemType: 'thumbnail', text: ''}, {key: 'url', itemType: 'url', text: 'URL'}, {key: 'displayedAspectRatio', itemType: 'text', text: 'Aspect Ratio (Displayed)'}, {key: 'actualAspectRatio', itemType: 'text', text: 'Aspect Ratio (Actual)'}, diff --git a/lighthouse-core/audits/is-on-https.js b/lighthouse-core/audits/is-on-https.js index 095747999507..0cf89ed81f9c 100644 --- a/lighthouse-core/audits/is-on-https.js +++ b/lighthouse-core/audits/is-on-https.js @@ -67,7 +67,7 @@ class HTTPS extends Audit { details: { type: 'list', header: {type: 'text', text: 'Insecure URLs:'}, - items: insecureRecords.map(record => ({type: 'url', text: record.url})), + items: insecureRecords.map(record => ({type: 'url', value: record.url})), }, }; }); diff --git a/lighthouse-core/audits/redirects.js b/lighthouse-core/audits/redirects.js index d55650aaecfb..aca682228d8a 100644 --- a/lighthouse-core/audits/redirects.js +++ b/lighthouse-core/audits/redirects.js @@ -43,7 +43,7 @@ class Redirects extends Audit { if (redirectRequests.length > 1) { pageRedirects.push({ url: `(Initial: ${redirectRequests[0].url})`, - wastedMs: 'n/a', + wastedMs: 0, }); } @@ -56,15 +56,16 @@ class Redirects extends Audit { pageRedirects.push({ url: redirectedRequest.url, - wastedMs: {type: 'ms', value: wastedMs, granularity: 1}, + wastedMs, }); } const headings = [ {key: 'url', itemType: 'text', text: 'Redirected URL'}, - {key: 'wastedMs', itemType: 'text', text: 'Time for Redirect'}, + {key: 'wastedMs', itemType: 'ms', text: 'Time for Redirect', granularity: 1}, ]; - const details = Audit.makeTableDetails(headings, pageRedirects); + const summary = {wastedMs: totalWastedMs}; + const details = Audit.makeTableDetails(headings, pageRedirects, summary); return { // We award a passing grade if you only have 1 redirect diff --git a/lighthouse-core/audits/seo/font-size.js b/lighthouse-core/audits/seo/font-size.js index a22e35db343a..06c2f76f7d8b 100644 --- a/lighthouse-core/audits/seo/font-size.js +++ b/lighthouse-core/audits/seo/font-size.js @@ -243,7 +243,7 @@ class FontSize extends Audit { tableData.push({ source: 'Add\'l illegible text', - selector: null, + selector: '', coverage: `${percentageOfUnanalyzedFailingText.toFixed(2)}%`, fontSize: '< 12px', }); @@ -252,7 +252,7 @@ class FontSize extends Audit { if (percentageOfPassingText > 0) { tableData.push({ source: 'Legible text', - selector: null, + selector: '', coverage: `${percentageOfPassingText.toFixed(2)}%`, fontSize: '≥ 12px', }); @@ -260,7 +260,7 @@ class FontSize extends Audit { const details = Audit.makeTableDetails(headings, tableData); const passed = percentageOfPassingText >= MINIMAL_PERCENTAGE_OF_LEGIBLE_TEXT; - let debugString = null; + let debugString; if (!passed) { const percentageOfFailingText = parseFloat((100 - percentageOfPassingText).toFixed(2)); diff --git a/lighthouse-core/audits/seo/link-text.js b/lighthouse-core/audits/seo/link-text.js index 3f099d8d3b10..17918f0efa39 100644 --- a/lighthouse-core/audits/seo/link-text.js +++ b/lighthouse-core/audits/seo/link-text.js @@ -57,7 +57,7 @@ class LinkText extends Audit { {key: 'text', itemType: 'text', text: 'Link Text'}, ]; - const details = Audit.makeTableDetails(headings, failingLinks); + const details = Audit.makeTableDetails(headings, failingLinks, {}); let displayValue; if (failingLinks.length) { diff --git a/lighthouse-core/audits/time-to-first-byte.js b/lighthouse-core/audits/time-to-first-byte.js index 9a3edf73846d..1b9273ce5e6c 100644 --- a/lighthouse-core/audits/time-to-first-byte.js +++ b/lighthouse-core/audits/time-to-first-byte.js @@ -55,6 +55,11 @@ class TTFBMetric extends Audit { rawValue: ttfb, score: passed, displayValue, + details: { + summary: { + wastedMs: ttfb - TTFB_THRESHOLD, + }, + }, extendedInfo: { value: { wastedMs: ttfb - TTFB_THRESHOLD, diff --git a/lighthouse-core/audits/user-timings.js b/lighthouse-core/audits/user-timings.js index 31b3c1d94eee..1240991ed330 100644 --- a/lighthouse-core/audits/user-timings.js +++ b/lighthouse-core/audits/user-timings.js @@ -6,7 +6,6 @@ 'use strict'; const Audit = require('./audit'); -const Util = require('../report/v2/renderer/util'); class UserTimings extends Audit { /** @@ -115,13 +114,12 @@ class UserTimings extends Audit { return { name: item.name, timingType: item.isMark ? 'Mark' : 'Measure', - time: Util.formatMilliseconds(time, 0.001), - timeAsNumber: time, + time, }; }).sort((itemA, itemB) => { if (itemA.timingType === itemB.timingType) { // If both items are the same type, sort in ascending order by time - return itemA.timeAsNumber - itemB.timeAsNumber; + return itemA.time - itemB.time; } else if (itemA.timingType === 'Measure') { // Put measures before marks return -1; @@ -133,7 +131,7 @@ class UserTimings extends Audit { const headings = [ {key: 'name', itemType: 'text', text: 'Name'}, {key: 'timingType', itemType: 'text', text: 'Type'}, - {key: 'time', itemType: 'text', text: 'Time'}, + {key: 'time', itemType: 'ms', granularity: 0.01, text: 'Time'}, ]; const details = Audit.makeTableDetails(headings, tableRows); diff --git a/lighthouse-core/audits/uses-rel-preload.js b/lighthouse-core/audits/uses-rel-preload.js index 72f8269b3e1e..815c7293583e 100644 --- a/lighthouse-core/audits/uses-rel-preload.js +++ b/lighthouse-core/audits/uses-rel-preload.js @@ -92,7 +92,8 @@ class UsesRelPreloadAudit extends Audit { {key: 'url', itemType: 'url', text: 'URL'}, {key: 'wastedMs', itemType: 'text', text: 'Potential Savings'}, ]; - const details = Audit.makeTableDetails(headings, results); + const summary = {wastedMs: maxWasted}; + const details = Audit.makeTableDetails(headings, results, summary); return { score: UnusedBytes.scoreForWastedMs(maxWasted), diff --git a/lighthouse-core/report/v2/renderer/category-renderer.js b/lighthouse-core/report/v2/renderer/category-renderer.js index 7dde283fe563..84487efc7633 100644 --- a/lighthouse-core/report/v2/renderer/category-renderer.js +++ b/lighthouse-core/report/v2/renderer/category-renderer.js @@ -45,7 +45,7 @@ class CategoryRenderer { // Append audit details to header section so the entire audit is within a
. const header = /** @type {!HTMLDetailsElement} */ (this.dom.find('.lh-score__header', tmpl)); - if (audit.result.details) { + if (audit.result.details && audit.result.details.type) { header.appendChild(this.detailsRenderer.render(audit.result.details)); } @@ -200,25 +200,24 @@ class CategoryRenderer { * @param {!Element} element Parent container to add the manual audits to. */ _renderManualAudits(manualAudits, groupDefinitions, element) { - const auditsGroupedByGroup = /** @type {!Object>} */ ({}); + // While we could support rendering multiple groups of manual audits, it doesn't + // seem desirable for UX or renderer complexity. So we'll throw. + const groupsIds = new Set(manualAudits.map(a => a.group)); + /* eslint-disable no-console */ + console.assert(groupsIds.size <= 1, 'More than 1 manual audit group found.'); + console.assert(!groupsIds.has(undefined), 'Some manual audits don\'t belong to a group'); + /* eslint-enable no-console */ + if (!groupsIds.size) return; + + const groupId = /** @type {string} */ (Array.from(groupsIds)[0]); + const auditGroupElem = this.renderAuditGroup(groupDefinitions[groupId], {expandable: true}); + auditGroupElem.classList.add('lh-audit-group--manual'); + manualAudits.forEach(audit => { - const group = auditsGroupedByGroup[audit.group] || []; - group.push(audit); - auditsGroupedByGroup[audit.group] = group; + auditGroupElem.appendChild(this.renderAudit(audit)); }); - Object.keys(auditsGroupedByGroup).forEach(groupId => { - const group = groupDefinitions[groupId]; - const auditGroupElem = this.renderAuditGroup(group, {expandable: true}); - auditGroupElem.classList.add('lh-audit-group--manual'); - - auditsGroupedByGroup[groupId].forEach(audit => { - auditGroupElem.appendChild(this.renderAudit(audit)); - }); - - element.appendChild(auditGroupElem); - }); + element.appendChild(auditGroupElem); } /** diff --git a/lighthouse-core/report/v2/renderer/details-renderer.js b/lighthouse-core/report/v2/renderer/details-renderer.js index 22e594ed8426..bbc714102136 100644 --- a/lighthouse-core/report/v2/renderer/details-renderer.js +++ b/lighthouse-core/report/v2/renderer/details-renderer.js @@ -32,9 +32,9 @@ class DetailsRenderer { render(details) { switch (details.type) { case 'text': - return this._renderText(details); + return this._renderText(/** @type {!DetailsRenderer.StringDetailsJSON} */ (details)); case 'url': - return this._renderTextURL(details); + return this._renderTextURL(/** @type {!DetailsRenderer.StringDetailsJSON} */ (details)); case 'bytes': return this._renderBytes(/** @type {!DetailsRenderer.NumericUnitDetailsJSON} */ (details)); case 'ms': @@ -59,8 +59,9 @@ class DetailsRenderer { /** @type {!CriticalRequestChainRenderer.CRCDetailsJSON} */ (details)); case 'list': return this._renderList(/** @type {!DetailsRenderer.ListDetailsJSON} */ (details)); - default: + default: { throw new Error(`Unknown type: ${details.type}`); + } } } @@ -70,8 +71,8 @@ class DetailsRenderer { */ _renderBytes(details) { // TODO: handle displayUnit once we have something other than 'kb' - const text = Util.formatBytesToKB(details.value, details.granularity); - return this._renderText({type: 'text', text}); + const value = Util.formatBytesToKB(details.value, details.granularity); + return this._renderText({type: 'text', value}); } /** @@ -79,20 +80,20 @@ class DetailsRenderer { * @return {!Element} */ _renderMilliseconds(details) { - let text = Util.formatMilliseconds(details.value, details.granularity); + let value = Util.formatMilliseconds(details.value, details.granularity); if (details.displayUnit === 'duration') { - text = Util.formatDuration(details.value); + value = Util.formatDuration(details.value); } - return this._renderText({type: 'text', text}); + return this._renderText({type: 'text', value}); } /** - * @param {!DetailsRenderer.DetailsJSON} text + * @param {!DetailsRenderer.StringDetailsJSON} text * @return {!Element} */ _renderTextURL(text) { - const url = text.text || ''; + const url = text.value; let displayedPath; let displayedHost; @@ -111,13 +112,13 @@ class DetailsRenderer { const element = this._dom.createElement('div', 'lh-text__url'); element.appendChild(this._renderText({ - text: displayedPath, + value: displayedPath, type: 'text', })); if (displayedHost) { const hostElem = this._renderText({ - text: displayedHost, + value: displayedHost, type: 'text', }); hostElem.classList.add('lh-text__url-host'); @@ -136,8 +137,11 @@ class DetailsRenderer { const allowedProtocols = ['https:', 'http:']; const url = new URL(details.url); if (!allowedProtocols.includes(url.protocol)) { - // Fall back to text if protocol not allowed. - return this._renderText(details); + // Fall back to just the link text if protocol not allowed. + return this._renderText({ + type: 'text', + value: details.text, + }); } const a = /** @type {!HTMLAnchorElement} */ (this._dom.createElement('a')); @@ -150,30 +154,26 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.DetailsJSON} text + * @param {!DetailsRenderer.StringDetailsJSON} text * @return {!Element} */ _renderText(text) { const element = this._dom.createElement('div', 'lh-text'); - element.textContent = text.text; + element.textContent = text.value; return element; } /** * Create small thumbnail with scaled down image asset. * If the supplied details doesn't have an image/* mimeType, then an empty span is returned. - * @param {!DetailsRenderer.ThumbnailDetails} value + * @param {!DetailsRenderer.ThumbnailDetails} details * @return {!Element} */ - _renderThumbnail(value) { - if (/^image/.test(value.mimeType) === false) { - return this._dom.createElement('span'); - } - + _renderThumbnail(details) { const element = this._dom.createElement('img', 'lh-thumbnail'); - element.src = value.url; + element.src = details.value; + element.title = details.value; element.alt = ''; - element.title = value.url; return element; } @@ -186,11 +186,6 @@ class DetailsRenderer { const element = this._dom.createElement('details', 'lh-details'); element.open = true; - if (list.header) { - const summary = this._dom.createElement('summary', 'lh-list__header'); - summary.textContent = list.header.text; - element.appendChild(summary); - } const itemsElem = this._dom.createChildOf(element, 'div', 'lh-list__items'); for (const item of list.items) { @@ -209,26 +204,48 @@ class DetailsRenderer { const element = this._dom.createElement('details', 'lh-details'); element.open = true; - if (details.header) { - element.appendChild(this._dom.createElement('summary')).textContent = details.header; - } + element.appendChild(this._dom.createElement('summary')).textContent = 'View Details'; const tableElem = this._dom.createChildOf(element, 'table', 'lh-table'); const theadElem = this._dom.createChildOf(tableElem, 'thead'); const theadTrElem = this._dom.createChildOf(theadElem, 'tr'); - for (const heading of details.itemHeaders) { + for (const heading of details.headings) { const itemType = heading.itemType || 'text'; const classes = `lh-table-column--${itemType}`; - this._dom.createChildOf(theadTrElem, 'th', classes).appendChild(this.render(heading)); + this._dom.createChildOf(theadTrElem, 'th', classes).appendChild(this.render({ + type: 'text', + value: heading.text || '', + })); } const tbodyElem = this._dom.createChildOf(tableElem, 'tbody'); for (const row of details.items) { const rowElem = this._dom.createChildOf(tbodyElem, 'tr'); - for (const columnItem of row) { - const classes = `lh-table-column--${columnItem.type}`; - this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(columnItem)); + for (const heading of details.headings) { + const value = /** @type {number|string|!DetailsRenderer.DetailsJSON} */ (row[heading.key]); + + if (typeof value === 'undefined') { + this._dom.createChildOf(rowElem, 'td', 'lh-table-column--empty'); + continue; + } + // handle nested types like code blocks in table rows. + if (value.type) { + const valueAsDetails = /** @type {!DetailsRenderer.DetailsJSON} */ (value); + const classes = `lh-table-column--${valueAsDetails.type}`; + this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(valueAsDetails)); + continue; + } + + // build new details item to render + const item = { + value: /** @type {number|string} */ (value), + type: heading.itemType, + displayUnit: heading.displayUnit, + granularity: heading.granularity, + }; + const classes = `lh-table-column--${value.type || heading.itemType}`; + this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(item)); } } return element; @@ -243,7 +260,6 @@ class DetailsRenderer { const element = this._dom.createElement('span', 'lh-node'); element.textContent = item.snippet; element.title = item.selector; - if (item.text) element.setAttribute('data-text', item.text); if (item.path) element.setAttribute('data-path', item.path); if (item.selector) element.setAttribute('data-selector', item.selector); if (item.snippet) element.setAttribute('data-snippet', item.snippet); @@ -315,7 +331,7 @@ class DetailsRenderer { */ _renderCode(details) { const pre = this._dom.createElement('pre', 'lh-code'); - pre.textContent = details.text; + pre.textContent = details.value; return pre; } } @@ -326,10 +342,14 @@ if (typeof module !== 'undefined' && module.exports) { self.DetailsRenderer = DetailsRenderer; } +// TODO, what's the diff between DetailsJSON and NumericUnitDetailsJSON? /** * @typedef {{ * type: string, - * text: (string|undefined) + * value: (string|number|undefined), + * summary: (DetailsRenderer.OpportunitySummary|undefined), + * granularity: (number|undefined), + * displayUnit: (string|undefined) * }} */ DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions @@ -338,11 +358,23 @@ DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions * @typedef {{ * type: string, * header: ({text: string}|undefined), - * items: !Array<{type: string, text: (string|undefined)}> + * items: !Array * }} */ DetailsRenderer.ListDetailsJSON; // eslint-disable-line no-unused-expressions + +/** + * @typedef {{ + * type: string, + * value: string, + * granularity: (number|undefined), + * displayUnit: (string|undefined), + * }} + */ +DetailsRenderer.StringDetailsJSON; // eslint-disable-line no-unused-expressions + + /** * @typedef {{ * type: string, @@ -356,7 +388,6 @@ DetailsRenderer.NumericUnitDetailsJSON; // eslint-disable-line no-unused-express /** * @typedef {{ * type: string, - * text: (string|undefined), * path: (string|undefined), * selector: (string|undefined), * snippet:(string|undefined) @@ -374,46 +405,34 @@ DetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * type: string, - * itemType: (string|undefined), - * itemKey: (string|undefined), - * text: (string|undefined) - * }} - */ -DetailsRenderer.TableHeaderJSON; // eslint-disable-line no-unused-expressions - -/** - * @typedef {{ - * type: string, + * itemType: string, + * key: string, * text: (string|undefined), - * path: (string|undefined), - * selector: (string|undefined), - * snippet:(string|undefined) + * granularity: (number|undefined), + * displayUnit: (string|undefined), * }} */ -DetailsRenderer.NodeDetailsJSON; // eslint-disable-line no-unused-expressions +DetailsRenderer.TableHeaderJSON; // eslint-disable-line no-unused-expressions /** @typedef {{ * type: string, - * header: ({text: string}|undefined), - * items: !Array>, - * itemHeaders: !Array + * items: !Array, + * headings: !Array * }} */ DetailsRenderer.TableDetailsJSON; // eslint-disable-line no-unused-expressions /** @typedef {{ * type: string, - * url: ({text: string}|undefined), - * mimeType: ({text: string}|undefined) + * value: (string|undefined), * }} */ DetailsRenderer.ThumbnailDetails; // eslint-disable-line no-unused-expressions /** @typedef {{ * type: string, - * url: string, - * text: string + * text: string, + * url: string * }} */ DetailsRenderer.LinkDetailsJSON; // eslint-disable-line no-unused-expressions @@ -425,3 +444,11 @@ DetailsRenderer.LinkDetailsJSON; // eslint-disable-line no-unused-expressions * }} */ DetailsRenderer.FilmstripDetails; // eslint-disable-line no-unused-expressions + + +/** @typedef {{ + * wastedMs: (number|undefined), + * wastedBytes: (number|undefined), + * }} + */ +DetailsRenderer.OpportunitySummary; // eslint-disable-line no-unused-expressions diff --git a/lighthouse-core/report/v2/renderer/performance-category-renderer.js b/lighthouse-core/report/v2/renderer/performance-category-renderer.js index bba7006060ad..91f8f6abcb57 100644 --- a/lighthouse-core/report/v2/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/v2/renderer/performance-category-renderer.js @@ -45,8 +45,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer { * @return {!Element} */ _renderPerfHintAudit(audit, scale) { - const extendedInfo = /** @type {!PerformanceCategoryRenderer.PerfHintExtendedInfo} - */ (audit.result.extendedInfo); const tooltipAttrs = {title: audit.result.displayValue}; const element = this.dom.createElement('details', [ @@ -62,9 +60,21 @@ class PerformanceCategoryRenderer extends CategoryRenderer { this.dom.createChildOf(summary, 'div', 'lh-toggle-arrow', {title: 'See resources'}); - if (!extendedInfo || typeof audit.result.rawValue !== 'number') { + if (audit.result.error) { const debugStrEl = this.dom.createChildOf(summary, 'div', 'lh-debug'); - debugStrEl.textContent = audit.result.debugString || 'Report error: no extended information'; + debugStrEl.textContent = audit.result.debugString || 'Audit error'; + return element; + } + + const details = audit.result.details; + const summaryInfo = /** @type {!DetailsRenderer.OpportunitySummary} + */ (details && details.summary); + // eslint-disable-next-line no-console + console.assert(summaryInfo, 'Missing `summary` for perf-hint audit'); + // eslint-disable-next-line no-console + console.assert(typeof summaryInfo.wastedMs === 'number', + 'Missing numeric `summary.wastedMs` for perf-hint audit'); + if (!summaryInfo || !summaryInfo.wastedMs) { return element; } @@ -72,15 +82,15 @@ class PerformanceCategoryRenderer extends CategoryRenderer { tooltipAttrs); const sparklineEl = this.dom.createChildOf(sparklineContainerEl, 'div', 'lh-sparkline'); const sparklineBarEl = this.dom.createChildOf(sparklineEl, 'div', 'lh-sparkline__bar'); - sparklineBarEl.style.width = audit.result.rawValue / scale * 100 + '%'; + sparklineBarEl.style.width = summaryInfo.wastedMs / scale * 100 + '%'; const statsEl = this.dom.createChildOf(summary, 'div', 'lh-perf-hint__stats', tooltipAttrs); const statsMsEl = this.dom.createChildOf(statsEl, 'div', 'lh-perf-hint__primary-stat'); - statsMsEl.textContent = Util.formatMilliseconds(audit.result.rawValue); + statsMsEl.textContent = Util.formatMilliseconds(summaryInfo.wastedMs); - if (extendedInfo.value.wastedKb) { + if (summaryInfo.wastedBytes) { const statsKbEl = this.dom.createChildOf(statsEl, 'div', 'lh-perf-hint__secondary-stat'); - statsKbEl.textContent = Util.formatNumber(extendedInfo.value.wastedKb) + ' KB'; + statsKbEl.textContent = Util.formatBytesToKB(summaryInfo.wastedBytes); } const descriptionEl = this.dom.createChildOf(element, 'div', 'lh-perf-hint__description'); @@ -91,8 +101,9 @@ class PerformanceCategoryRenderer extends CategoryRenderer { debugStrEl.textContent = audit.result.debugString; } - if (audit.result.details) { - element.appendChild(this.detailsRenderer.render(audit.result.details)); + // If there's no `type`, then we only used details for `summary` + if (details.type) { + element.appendChild(this.detailsRenderer.render(details)); } return element; @@ -179,13 +190,3 @@ if (typeof module !== 'undefined' && module.exports) { } else { self.PerformanceCategoryRenderer = PerformanceCategoryRenderer; } - -/** - * @typedef {{ - * value: { - * wastedMs: (number|undefined), - * wastedKb: (number|undefined), - * } - * }} - */ -PerformanceCategoryRenderer.PerfHintExtendedInfo; // eslint-disable-line no-unused-expressions diff --git a/lighthouse-core/report/v2/renderer/report-renderer.js b/lighthouse-core/report/v2/renderer/report-renderer.js index d59d240c86fe..a54f84b7a08d 100644 --- a/lighthouse-core/report/v2/renderer/report-renderer.js +++ b/lighthouse-core/report/v2/renderer/report-renderer.js @@ -184,27 +184,32 @@ if (typeof module !== 'undefined' && module.exports) { /** * @typedef {{ * id: string, - * weight: number, * score: number, - * group: string, - * result: { - * rawValue: (number|undefined), - * description: string, - * informative: boolean, - * manual: boolean, - * notApplicable: boolean, - * debugString: string, - * displayValue: string, - * helpText: string, - * score: (number|boolean), - * scoringMode: string, - * extendedInfo: Object, - * details: (!DetailsRenderer.DetailsJSON|undefined) - * } + * weight: number, + * group: (string|undefined), + * result: (ReportRenderer.AuditResultJSON|undefined) * }} */ ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions +/** + * @typedef {{ + * rawValue: (number|boolean|undefined), + * description: string, + * informative: (boolean|undefined), + * manual: (boolean|undefined), + * notApplicable: (boolean|undefined), + * debugString: (string|undefined), + * displayValue: string, + * helpText: string, + * scoringMode: string, + * extendedInfo: Object, + * error: boolean, + * details: (!DetailsRenderer.DetailsJSON|undefined), + * }} + */ +ReportRenderer.AuditResultJSON; // eslint-disable-line no-unused-expressions + /** * @typedef {{ * name: string, @@ -234,7 +239,7 @@ ReportRenderer.GroupJSON; // eslint-disable-line no-unused-expressions * initialUrl: string, * url: string, * runWarnings: (!Array|undefined), - * artifacts: {traces: !Object}, + * audits: !Object, * reportCategories: !Array, * reportGroups: !Object, * runtimeConfig: { diff --git a/lighthouse-core/report/v2/report-styles.css b/lighthouse-core/report/v2/report-styles.css index 1338d0f26260..bdb85b941fc6 100644 --- a/lighthouse-core/report/v2/report-styles.css +++ b/lighthouse-core/report/v2/report-styles.css @@ -752,6 +752,7 @@ span.lh-node:hover { .lh-code { white-space: normal; margin-top: 0; + font-size: 85%; } .lh-run-warnings { diff --git a/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js b/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js index 4e8a9429039e..4be5e24fd3b7 100644 --- a/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js @@ -12,8 +12,8 @@ const assert = require('assert'); describe('Byte efficiency base audit', () => { const baseHeadings = [ - {key: 'totalKb', itemType: 'text', text: ''}, - {key: 'wastedKb', itemType: 'text', text: ''}, + {key: 'totalBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: ''}, + {key: 'wastedBytes', itemType: 'bytes', displayUnit: 'kb', granularity: 1, text: ''}, {key: 'wastedMs', itemType: 'text', text: ''}, ]; @@ -107,10 +107,10 @@ describe('Byte efficiency base audit', () => { ], }, 1000); - assert.equal(result.details.items[0][1].value, 2048); - assert.equal(result.details.items[0][0].value, 4096); - assert.equal(result.details.items[1][1].value, 1986); - assert.equal(result.details.items[1][0].value, 5436); + assert.equal(result.details.items[0].wastedBytes, 2048); + assert.equal(result.details.items[0].totalBytes, 4096); + assert.equal(result.details.items[1].wastedBytes, 1986); + assert.equal(result.details.items[1].totalBytes, 5436); }); it('should populate Ms', () => { @@ -123,9 +123,9 @@ describe('Byte efficiency base audit', () => { ], }, 1000); - assert.equal(result.details.items[0][2].value, 350); - assert.equal(result.details.items[1][2].value, 326); - assert.equal(result.details.items[2][2].value, 251); + assert.equal(result.details.items[0].wastedMs, 350); + assert.equal(result.details.items[1].wastedMs, 326); + assert.equal(result.details.items[2].wastedMs, 251); }); it('should sort on wastedBytes', () => { @@ -138,9 +138,9 @@ describe('Byte efficiency base audit', () => { ], }, 1000); - assert.equal(result.details.items[0][2].value, 450); - assert.equal(result.details.items[1][2].value, 400); - assert.equal(result.details.items[2][2].value, 350); + assert.equal(result.details.items[0].wastedMs, 450); + assert.equal(result.details.items[1].wastedMs, 400); + assert.equal(result.details.items[2].wastedMs, 350); }); it('should create a display value', () => { diff --git a/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js b/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js index ae16894c886a..27e3ad020140 100644 --- a/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/total-byte-weight-test.js @@ -45,7 +45,7 @@ describe('Total byte weight audit', () => { const results = result.details.items; assert.strictEqual(results.length, 3); assert.strictEqual(result.extendedInfo.value.totalCompletedRequests, 3); - assert.strictEqual(results[0][1].value, 71680, 'results are sorted'); + assert.strictEqual(results[0].totalBytes, 71680, 'results are sorted'); }); }); diff --git a/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js b/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js index c05ed451107f..27b22d78e62d 100644 --- a/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js @@ -102,14 +102,14 @@ describe('Best Practices: unused css rules audit', () => { }; }); - it('correctly computes wastedKb', () => { + it('correctly computes wastedBytes', () => { assert.equal(map({usedRules: []}).wastedPercent, 100); assert.equal(map({usedRules: [{startOffset: 0, endOffset: 3}]}).wastedPercent, 40); assert.equal(map({usedRules: [{startOffset: 0, endOffset: 5}]}).wastedPercent, 0); }); it('correctly computes url', () => { - const expectedPreview = {type: 'code', text: 'dummy'}; + const expectedPreview = {type: 'code', value: 'dummy'}; assert.deepEqual(map({header: {sourceURL: ''}}).url, expectedPreview); assert.deepEqual(map({header: {sourceURL: 'a'}}, 'http://g.co/a').url, expectedPreview); assert.equal(map({header: {sourceURL: 'foobar'}}).url, 'http://g.co/foobar'); diff --git a/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js b/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js index b10a0ae208c3..6669fd031291 100644 --- a/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js +++ b/lighthouse-core/test/audits/byte-efficiency/uses-long-cache-ttl-test.js @@ -74,10 +74,9 @@ describe('Cache headers audit', () => { ]; return CacheHeadersAudit.audit(artifacts).then(result => { - const items = result.extendedInfo.value.results; + const items = result.details.items; assert.equal(items.length, 3); assert.equal(items[0].cacheLifetimeInSeconds, 3600); - assert.equal(items[0].cacheLifetimeDisplay.value, 3600); assert.equal(items[0].cacheHitProbability, .2); assert.equal(Math.round(items[0].wastedBytes), 80000); assert.equal(items[1].cacheLifetimeInSeconds, 3600); diff --git a/lighthouse-core/test/audits/deprecations-test.js b/lighthouse-core/test/audits/deprecations-test.js index f51d8107ce11..eafb4d0d79be 100644 --- a/lighthouse-core/test/audits/deprecations-test.js +++ b/lighthouse-core/test/audits/deprecations-test.js @@ -34,9 +34,8 @@ describe('Console deprecations audit', () => { assert.equal(auditResult.rawValue, false); assert.equal(auditResult.displayValue, '1 warning found'); assert.equal(auditResult.details.items.length, 1); - assert.equal(auditResult.details.items[0][1].type, 'url'); - assert.equal(auditResult.details.items[0][1].text, undefined); - assert.equal(auditResult.details.items[0][2].text, undefined); + assert.equal(auditResult.details.items[0].url, ''); + assert.equal(auditResult.details.items[0].lineNumber, undefined); }); it('fails when deprecation messages are found', () => { @@ -71,7 +70,7 @@ describe('Console deprecations audit', () => { assert.equal(auditResult.rawValue, false); assert.equal(auditResult.displayValue, '2 warnings found'); assert.equal(auditResult.details.items.length, 2); - assert.equal(auditResult.details.items[0][1].text, URL); - assert.equal(auditResult.details.items[0][2].text, 123); + assert.equal(auditResult.details.items[0].url, URL); + assert.equal(auditResult.details.items[0].lineNumber, 123); }); }); diff --git a/lighthouse-core/test/audits/dobetterweb/link-blocking-first-paint-test.js b/lighthouse-core/test/audits/dobetterweb/link-blocking-first-paint-test.js index dd597438824f..89a153c0f06e 100644 --- a/lighthouse-core/test/audits/dobetterweb/link-blocking-first-paint-test.js +++ b/lighthouse-core/test/audits/dobetterweb/link-blocking-first-paint-test.js @@ -68,11 +68,11 @@ describe('Link Block First Paint audit', () => { assert.equal(auditResult.displayValue, `2 resources delayed first paint by 500${NBSP}ms`); const results = auditResult.details.items; assert.equal(results.length, 2); - assert.ok(results[0][0].text.includes('css/style.css'), 'has a url'); - assert.equal(results[0][1].value, 100); - assert.equal(results[1][1].value, 100); - assert.equal(results[0][2].value, 500); - assert.equal(Math.round(results[1][2].value), 200); + assert.ok(results[0].url.includes('css/style.css'), 'has a url'); + assert.equal(results[0].totalBytes, 100); + assert.equal(results[1].totalBytes, 100); + assert.equal(results[0].wastedMs, 500); + assert.equal(Math.round(results[1].wastedMs), 200); }); }); diff --git a/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js b/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js index 3e7c1d32082e..5fcae3bc53f8 100644 --- a/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js +++ b/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js @@ -32,7 +32,7 @@ describe('Page does not use mutation events', () => { assert.equal(auditResult.rawValue, false); assert.equal(auditResult.details.items.length, 4); - const itemHeaders = auditResult.details.itemHeaders; + const itemHeaders = auditResult.details.headings; assert.deepEqual(Object.keys(itemHeaders).map(key => itemHeaders[key].text), ['URL', 'Event', 'Line', 'Col', 'Snippet'], 'table headings are correct and in order'); @@ -64,7 +64,7 @@ describe('Page does not use mutation events', () => { URL: {finalUrl: URL}, }); assert.equal(auditResult.rawValue, false); - assert.equal(auditResult.details.items[0][0].text, 'eval():54:21'); + assert.equal(auditResult.details.items[0].url, 'eval():54:21'); assert.equal(auditResult.details.items.length, 1); }); }); diff --git a/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js b/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js index 0e7d60d594d3..f24784291fe6 100644 --- a/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js +++ b/lighthouse-core/test/audits/dobetterweb/no-vulnerable-libraries-test.js @@ -37,9 +37,10 @@ describe('Avoids front-end JavaScript libraries with known vulnerabilities', () assert.equal(auditResult.rawValue, false); assert.equal(auditResult.details.items.length, 1); assert.equal(auditResult.extendedInfo.jsLibs.length, 3); - assert.equal(auditResult.details.items[0][2].text, 'High'); - assert.equal(auditResult.details.items[0][0].text, 'angular@1.1.4'); - assert.equal(auditResult.details.items[0][0].url, 'https://snyk.io/vuln/npm:angular?lh@1.1.4'); + assert.equal(auditResult.details.items[0].highestSeverity, 'High'); + assert.equal(auditResult.details.items[0].detectedLib.type, 'link'); + assert.equal(auditResult.details.items[0].detectedLib.text, 'angular@1.1.4'); + assert.equal(auditResult.details.items[0].detectedLib.url, 'https://snyk.io/vuln/npm:angular?lh@1.1.4'); }); it('handles ill-specified versions', () => { @@ -53,7 +54,8 @@ describe('Avoids front-end JavaScript libraries with known vulnerabilities', () assert.equal(auditResult.rawValue, false); assert.equal(auditResult.details.items.length, 1); - assert.equal(auditResult.details.items[0][0].text, 'jquery@1.8.0'); + assert.equal(auditResult.details.items[0].detectedLib.type, 'link'); + assert.equal(auditResult.details.items[0].detectedLib.text, 'jquery@1.8.0'); }); it('passes when no JS libraries with known vulnerabilities are detected', () => { diff --git a/lighthouse-core/test/audits/dobetterweb/script-blocking-first-paint-test.js b/lighthouse-core/test/audits/dobetterweb/script-blocking-first-paint-test.js index 54922fe7da23..c496abdb6b21 100644 --- a/lighthouse-core/test/audits/dobetterweb/script-blocking-first-paint-test.js +++ b/lighthouse-core/test/audits/dobetterweb/script-blocking-first-paint-test.js @@ -53,9 +53,9 @@ describe('Script Block First Paint audit', () => { assert.equal(auditResult.displayValue, `2 resources delayed first paint by 150${NBSP}ms`); const results = auditResult.details.items; assert.equal(results.length, 2); - assert.ok(results[0][0].text.includes('js/app.js'), 'has a url'); - assert.equal(Math.round(results[0][2].value), 150); - assert.equal(Math.round(results[1][2].value), 50); + assert.ok(results[0].url.includes('js/app.js'), 'has a url'); + assert.equal(Math.round(results[0].wastedMs), 150); + assert.equal(Math.round(results[1].wastedMs), 50); }); }); diff --git a/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js b/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js index 0cd8ae048aa4..756376309640 100644 --- a/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js +++ b/lighthouse-core/test/audits/dobetterweb/uses-http2-test.js @@ -24,7 +24,7 @@ describe('Resources are fetched over http/2', () => { assert.equal(auditResult.rawValue, false); assert.ok(auditResult.displayValue.match('4 requests were not')); assert.equal(auditResult.details.items.length, 4); - const headers = auditResult.details.itemHeaders; + const headers = auditResult.details.headings; assert.equal(headers[0].text, 'URL', 'table headings are correct and in order'); assert.equal(headers[1].text, 'Protocol', 'table headings are correct and in order'); }); diff --git a/lighthouse-core/test/audits/errors-in-console-test.js b/lighthouse-core/test/audits/errors-in-console-test.js index c41b0c2dba20..e855b3a3a479 100644 --- a/lighthouse-core/test/audits/errors-in-console-test.js +++ b/lighthouse-core/test/audits/errors-in-console-test.js @@ -84,21 +84,15 @@ describe('Console error logs audit', () => { assert.equal(auditResult.rawValue, 3); assert.equal(auditResult.score, false); assert.equal(auditResult.details.items.length, 3); - assert.equal(auditResult.details.items[0][0].type, 'url'); - assert.equal(auditResult.details.items[0][0].text, 'http://www.example.com/favicon.ico'); - assert.equal(auditResult.details.items[0][1].type, 'code'); - assert.equal(auditResult.details.items[0][1].text, + assert.equal(auditResult.details.items[0].url, 'http://www.example.com/favicon.ico'); + assert.equal(auditResult.details.items[0].description, 'The server responded with a status of 404 (Not Found)'); - assert.equal(auditResult.details.items[1][0].type, 'url'); - assert.equal(auditResult.details.items[1][0].text, 'http://www.example.com/wsconnect.ws'); - assert.equal(auditResult.details.items[1][1].type, 'code'); - assert.equal(auditResult.details.items[1][1].text, + assert.equal(auditResult.details.items[1].url, 'http://www.example.com/wsconnect.ws'); + assert.equal(auditResult.details.items[1].description, 'WebSocket connection failed: Unexpected response code: 500'); - assert.equal(auditResult.details.items[2][0].type, 'url'); - assert.equal(auditResult.details.items[2][0].text, + assert.equal(auditResult.details.items[2].url, 'http://example.com/fancybox.js'); - assert.equal(auditResult.details.items[2][1].type, 'code'); - assert.equal(auditResult.details.items[2][1].text, + assert.equal(auditResult.details.items[2].description, 'TypeError: Cannot read property \'msie\' of undefined'); }); @@ -117,9 +111,9 @@ describe('Console error logs audit', () => { assert.equal(auditResult.score, false); assert.equal(auditResult.details.items.length, 1); // url is undefined - assert.strictEqual(auditResult.details.items[0][0].text, undefined); + assert.strictEqual(auditResult.details.items[0].url, undefined); // text is undefined - assert.strictEqual(auditResult.details.items[0][1].text, undefined); + assert.strictEqual(auditResult.details.items[0].description, undefined); }); // Checks bug #4188 @@ -147,8 +141,8 @@ describe('Console error logs audit', () => { assert.equal(auditResult.rawValue, 1); assert.equal(auditResult.score, false); assert.equal(auditResult.details.items.length, 1); - assert.strictEqual(auditResult.details.items[0][0].text, 'http://example.com/fancybox.js'); - assert.strictEqual(auditResult.details.items[0][1].text, + assert.strictEqual(auditResult.details.items[0].url, 'http://example.com/fancybox.js'); + assert.strictEqual(auditResult.details.items[0].description, 'TypeError: Cannot read property \'msie\' of undefined'); }); }); diff --git a/lighthouse-core/test/audits/font-display-test.js b/lighthouse-core/test/audits/font-display-test.js index 29790271312e..64fd3ba3474b 100644 --- a/lighthouse-core/test/audits/font-display-test.js +++ b/lighthouse-core/test/audits/font-display-test.js @@ -59,11 +59,10 @@ describe('Performance: Font Display audit', () => { _resourceType: WebInspector.resourceTypes.Font, }, ], webFonts)).then(result => { - const items = [[{ - type: 'url', - text: openSansFontBold.src[0], - }, - {type: 'text', text: '2,000 ms'}]]; + const items = [{ + url: openSansFontBold.src[0], + wastedTime: 2000, + }]; assert.strictEqual(result.rawValue, false); assert.deepEqual(result.details.items, items); }); diff --git a/lighthouse-core/test/audits/seo/anchor-text-test.js b/lighthouse-core/test/audits/seo/anchor-text-test.js index a61caf70bfb4..e8dcfe460ec9 100644 --- a/lighthouse-core/test/audits/seo/anchor-text-test.js +++ b/lighthouse-core/test/audits/seo/anchor-text-test.js @@ -27,8 +27,8 @@ describe('SEO: link text audit', () => { const auditResult = LinkTextAudit.audit(artifacts); assert.equal(auditResult.rawValue, false); assert.equal(auditResult.details.items.length, 1); - assert.equal(auditResult.details.items[0][0].text, invalidLink.href); - assert.equal(auditResult.details.items[0][1].text, invalidLink.text); + assert.equal(auditResult.details.items[0].href, invalidLink.href); + assert.equal(auditResult.details.items[0].text, invalidLink.text); }); it('ignores links pointing to the main document', () => { diff --git a/lighthouse-core/test/audits/seo/font-size-test.js b/lighthouse-core/test/audits/seo/font-size-test.js index f9bfe7acc8a7..110392f5f604 100644 --- a/lighthouse-core/test/audits/seo/font-size-test.js +++ b/lighthouse-core/test/audits/seo/font-size-test.js @@ -122,7 +122,7 @@ describe('SEO: Font size audit', () => { assert.equal(auditResult.rawValue, false); assert.equal(auditResult.details.items.length, 2); - assert.equal(auditResult.details.items[0][2].text, '57.14%'); + assert.equal(auditResult.details.items[0].coverage, '57.14%'); }); it('adds a category for failing text that wasn\'t analyzed', () => { @@ -142,8 +142,8 @@ describe('SEO: Font size audit', () => { const auditResult = FontSizeAudit.audit(artifacts); assert.equal(auditResult.rawValue, false); assert.equal(auditResult.details.items.length, 3); - assert.equal(auditResult.details.items[1][0].text, 'Add\'l illegible text'); - assert.equal(auditResult.details.items[1][2].text, '40.00%'); + assert.equal(auditResult.details.items[1].source, 'Add\'l illegible text'); + assert.equal(auditResult.details.items[1].coverage, '40.00%'); }); it('informs user if audit haven\'t covered all text on the page', () => { diff --git a/lighthouse-core/test/audits/user-timing-test.js b/lighthouse-core/test/audits/user-timing-test.js index 8821e91d9ff4..0848bdc8200d 100644 --- a/lighthouse-core/test/audits/user-timing-test.js +++ b/lighthouse-core/test/audits/user-timing-test.js @@ -12,8 +12,6 @@ const traceEvents = require('../fixtures/traces/trace-user-timings.json'); const Runner = require('../../runner.js'); const computedArtifacts = Runner.instantiateComputedArtifacts(); -const NBSP = '\xa0'; - function generateArtifactsWithTrace(trace) { return Object.assign({ traces: { @@ -44,9 +42,9 @@ describe('Performance: user-timings audit', () => { assert.equal(Math.floor(auditResult.extendedInfo.value[1].endTime), 1000); assert.equal(Math.floor(auditResult.extendedInfo.value[1].duration), 1000); - assert.equal(auditResult.details.items[0][0].text, 'measure_test'); - assert.equal(auditResult.details.items[0][1].text, 'Measure'); - assert.equal(auditResult.details.items[0][2].text, `1,000.965${NBSP}ms`); + assert.equal(auditResult.details.items[0].name, 'measure_test'); + assert.equal(auditResult.details.items[0].timingType, 'Measure'); + assert.equal(auditResult.details.items[0].time, 1000.965); }); }); diff --git a/lighthouse-core/test/report/v2/renderer/category-renderer-test.js b/lighthouse-core/test/report/v2/renderer/category-renderer-test.js index afc4b134fdf9..4d75462d30db 100644 --- a/lighthouse-core/test/report/v2/renderer/category-renderer-test.js +++ b/lighthouse-core/test/report/v2/renderer/category-renderer-test.js @@ -42,7 +42,10 @@ describe('CategoryRenderer', () => { }); it('renders an audit', () => { - const audit = sampleResults.reportCategories[0].audits[0]; + const audit = sampleResults.reportCategories + .find(c => c.id === 'pwa').audits + .find(a => a.id === 'works-offline'); + const auditDOM = renderer.renderAudit(audit); const title = auditDOM.querySelector('.lh-score__title'); @@ -79,25 +82,34 @@ describe('CategoryRenderer', () => { }); it('renders a category', () => { - const category = sampleResults.reportCategories[0]; + const category = sampleResults.reportCategories.find(c => c.id === 'pwa'); const categoryDOM = renderer.render(category, sampleResults.reportGroups); const score = categoryDOM.querySelector('.lh-score'); const value = categoryDOM.querySelector('.lh-score > .lh-score__value'); const title = score.querySelector('.lh-score__title'); - const description = score.querySelector('.lh-score__description'); assert.deepEqual(score, score.firstElementChild, 'first child is a score'); assert.ok(value.classList.contains('lh-score__value--numeric'), 'category score is numeric'); assert.equal(value.textContent, Math.round(category.score), 'category score is rounded'); assert.equal(title.textContent, category.name, 'title is set'); - assert.ok(description.querySelector('a'), 'description contains converted markdown links'); const audits = categoryDOM.querySelectorAll('.lh-audit'); assert.equal(audits.length, category.audits.length, 'renders correct number of audits'); }); + it('handles markdown in category descriptions a category', () => { + const category = sampleResults.reportCategories.find(c => c.id === 'pwa'); + const prevDesc = category.description; + category.description += ' [link text](http://example.com).'; + const categoryDOM = renderer.render(category, sampleResults.reportGroups); + + const description = categoryDOM.querySelector('.lh-score .lh-score__description'); + assert.ok(description.querySelector('a'), 'description contains converted markdown links'); + category.description = prevDesc; + }); + it('renders audits with debugString as failed', () => { const auditResult = { description: 'Audit', @@ -130,8 +142,14 @@ describe('CategoryRenderer', () => { const a11yCategory = sampleResults.reportCategories.find(cat => cat.id === 'accessibility'); const categoryDOM = renderer.render(a11yCategory, sampleResults.reportGroups); assert.ok(categoryDOM.querySelector('.lh-audit-group--notapplicable .lh-audit-group__summary')); - assert.equal(categoryDOM.querySelectorAll('.lh-score--informative').length, 1, - 'score shows informative and dash icon'); + + const notApplicableCount = a11yCategory.audits.reduce((sum, audit) => + sum += audit.result.notApplicable ? 1 : 0, 0); + assert.equal( + categoryDOM.querySelectorAll('.lh-audit-group--notapplicable .lh-score--informative').length, + notApplicableCount, + 'score shows informative and dash icon' + ); const perfCategory = sampleResults.reportCategories.find(cat => cat.id === 'performance'); const categoryDOM2 = renderer.render(perfCategory, sampleResults.reportGroups); @@ -157,7 +175,7 @@ describe('CategoryRenderer', () => { }); // TODO waiting for decision regarding this header - xit('renders the failed audits grouped by group', () => { + it.skip('renders the failed audits grouped by group', () => { const categoryDOM = renderer.render(category, sampleResults.reportGroups); const failedAudits = category.audits.filter(audit => { return audit.score !== 100 && !audit.result.notApplicable; @@ -171,9 +189,9 @@ describe('CategoryRenderer', () => { it('renders the passed audits grouped by group', () => { const categoryDOM = renderer.render(category, sampleResults.reportGroups); - const passedAudits = category.audits.filter(audit => audit.score === 100); + const passedAudits = category.audits.filter(audit => + !audit.result.notApplicable && audit.score === 100); const passedAuditTags = new Set(passedAudits.map(audit => audit.group)); - const passedAuditGroups = categoryDOM.querySelectorAll('.lh-passed-audits .lh-audit-group'); assert.equal(passedAuditGroups.length, passedAuditTags.size); }); @@ -187,7 +205,7 @@ describe('CategoryRenderer', () => { describe('grouping passed/failed/manual', () => { it('separates audits in the DOM', () => { - const category = sampleResults.reportCategories[0]; + const category = sampleResults.reportCategories.find(c => c.id === 'pwa'); const elem = renderer.render(category, sampleResults.reportGroups); const passedAudits = elem.querySelectorAll('.lh-passed-audits > .lh-audit'); const failedAudits = elem.querySelectorAll('.lh-failed-audits > .lh-audit'); @@ -199,7 +217,8 @@ describe('CategoryRenderer', () => { }); it('doesnt create a passed section if there were 0 passed', () => { - const category = JSON.parse(JSON.stringify(sampleResults.reportCategories[0])); + const origCategory = sampleResults.reportCategories.find(c => c.id === 'pwa'); + const category = JSON.parse(JSON.stringify(origCategory)); category.audits.forEach(audit => audit.score = 0); const elem = renderer.render(category, sampleResults.reportGroups); const passedAudits = elem.querySelectorAll('.lh-passed-audits > .lh-audit'); diff --git a/lighthouse-core/test/report/v2/renderer/details-renderer-test.js b/lighthouse-core/test/report/v2/renderer/details-renderer-test.js index 995361a13dce..732127cd5bbe 100644 --- a/lighthouse-core/test/report/v2/renderer/details-renderer-test.js +++ b/lighthouse-core/test/report/v2/renderer/details-renderer-test.js @@ -35,35 +35,18 @@ describe('DetailsRenderer', () => { describe('render', () => { it('renders text', () => { - const el = renderer.render({type: 'text', text: 'My text content'}); + const el = renderer.render({type: 'text', value: 'My text content'}); assert.equal(el.textContent, 'My text content'); assert.ok(el.classList.contains('lh-text'), 'adds classes'); }); - it('renders lists with headers', () => { - const el = renderer.render({ - type: 'list', - header: {type: 'text', text: 'My Header'}, - items: [ - {type: 'text', text: 'content 1'}, - {type: 'text', text: 'content 2'}, - ], - }); - - const header = el.querySelector('.lh-list__header'); - assert.equal(header.textContent, 'My Header', 'did not render header'); - - const items = el.querySelector('.lh-list__items'); - assert.equal(items.children.length, 2, 'did not render children'); - }); - it('renders lists without headers', () => { const el = renderer.render({ type: 'list', items: [ - {type: 'text', text: 'content 1'}, - {type: 'text', text: 'content 2'}, - {type: 'text', text: 'content 3'}, + {type: 'text', value: 'content 1'}, + {type: 'text', value: 'content 2'}, + {type: 'text', value: 'content 3'}, ], }); @@ -76,7 +59,7 @@ describe('DetailsRenderer', () => { it('renders cards', () => { const list = { - header: {type: 'text', text: 'View details'}, + header: {type: 'text', value: 'View details'}, items: [ {title: 'Total DOM Nodes', value: 3500, target: '1,500 nodes'}, {title: 'DOM Depth', value: 10, snippet: 'snippet'}, @@ -85,8 +68,6 @@ describe('DetailsRenderer', () => { }; const details = renderer._renderCards(list); - assert.ok(details.classList.contains('lh-details')); - assert.equal(details.querySelector('summary').textContent, 'View details'); const cards = details.querySelectorAll('.lh-scorecards > .lh-scorecard'); assert.ok(cards.length, list.items.length, `renders ${list.items.length} cards`); @@ -105,7 +86,7 @@ describe('DetailsRenderer', () => { it('renders code', () => { const el = renderer.render({ type: 'code', - text: 'code snippet', + value: 'code snippet', lineNumber: 123, source: 'deprecation', url: 'https://example.com/feature', @@ -119,7 +100,7 @@ describe('DetailsRenderer', () => { it('renders thumbnails', () => { const el = renderer.render({ type: 'thumbnail', - url: 'http://example.com/my-image.jpg', + value: 'http://example.com/my-image.jpg', mimeType: 'image/jpeg', }); @@ -154,23 +135,22 @@ describe('DetailsRenderer', () => { it('renders tables', () => { const el = renderer.render({ type: 'table', - header: 'View Items', - itemHeaders: [ - {type: 'text', text: 'First'}, - {type: 'text', text: 'Second'}, - {type: 'text', text: 'Preview', itemType: 'thumbnail'}, + headings: [ + {text: 'First', key: 'a', itemType: 'text'}, + {text: 'Second', key: 'b', itemType: 'text'}, + {text: 'Preview', key: 'c', itemType: 'thumbnail'}, ], items: [ - [ - {type: 'text', text: 'value A.1'}, - {type: 'text', text: 'value A.2'}, - {type: 'thumbnail', url: 'http://example.com/image.jpg', mimeType: 'image/jpeg'}, - ], - [ - {type: 'text', text: 'value B.1'}, - {type: 'text', text: 'value B.2'}, - {type: 'thumbnail', url: 'unknown'}, - ], + { + a: 'value A.1', + b: 'value A.2', + c: {type: 'thumbnail', value: 'http://example.com/image.jpg'}, + }, + { + a: 'value B.1', + b: 'value B.2', + c: {type: 'thumbnail', value: 'unknown'}, + }, ], }); @@ -219,7 +199,7 @@ describe('DetailsRenderer', () => { const displayUrlText = '/(example.com)'; const el = renderer.render({ type: 'url', - text: urlText, + value: urlText, }); assert.equal(el.localName, 'div'); diff --git a/lighthouse-core/test/report/v2/renderer/performance-category-renderer-test.js b/lighthouse-core/test/report/v2/renderer/performance-category-renderer-test.js index 63ce6254b05f..abc7f6a7d6b3 100644 --- a/lighthouse-core/test/report/v2/renderer/performance-category-renderer-test.js +++ b/lighthouse-core/test/report/v2/renderer/performance-category-renderer-test.js @@ -21,7 +21,7 @@ const sampleResults = require('../../../results/sample_v2.json'); const TEMPLATE_FILE = fs.readFileSync(__dirname + '/../../../../report/v2/templates.html', 'utf8'); -describe('CategoryRenderer', () => { +describe('PerfCategoryRenderer', () => { let renderer; before(() => { @@ -97,32 +97,53 @@ describe('CategoryRenderer', () => { const auditWithDebug = { score: 0, group: 'perf-hint', - result: {rawValue: 100, debugString: 'Yikes!', description: 'Bug'}, + result: { + rawValue: 100, debugString: 'Yikes!', description: 'Bug', + helpText: '', + details: {summary: {wastedMs: 3223}}, + }, }; - const fakeAudits = category.audits.concat(auditWithDebug); - const fakeCategory = Object.assign({}, category, {audits: fakeAudits}); + const fakeCategory = Object.assign({}, category, {audits: [auditWithDebug]}); const categoryDOM = renderer.render(fakeCategory, sampleResults.reportGroups); const debugEl = categoryDOM.querySelector('.lh-perf-hint .lh-debug'); assert.ok(debugEl, 'did not render debug'); }); - it('renders the performance hints with no extended info', () => { - const buggyAudit = { + it('renders errored performance hint with a debug string', () => { + const auditWithDebug = { score: 0, group: 'perf-hint', - result: {debugString: 'Yikes!', description: 'Bug'}, + result: { + error: true, score: 0, + rawValue: 100, debugString: 'Yikes!!', description: 'Bug #2', + + }, }; - const fakeAudits = category.audits.concat(buggyAudit); - const fakeCategory = Object.assign({}, category, {audits: fakeAudits}); + const fakeCategory = Object.assign({}, category, {audits: [auditWithDebug]}); const categoryDOM = renderer.render(fakeCategory, sampleResults.reportGroups); const debugEl = categoryDOM.querySelector('.lh-perf-hint .lh-debug'); assert.ok(debugEl, 'did not render debug'); }); + it('throws if a performance hint is missing summary.wastedMs', () => { + const auditWithDebug = { + score: 0, + group: 'perf-hint', + result: { + rawValue: 100, description: 'Bug', helpText: '', + }, + }; + + const fakeCategory = Object.assign({}, category, {audits: [auditWithDebug]}); + assert.throws(_ => { + renderer.render(fakeCategory, sampleResults.reportGroups); + }); + }); + it('renders the failing diagnostics', () => { const categoryDOM = renderer.render(category, sampleResults.reportGroups); const diagnosticSection = categoryDOM.querySelectorAll('.lh-category > .lh-audit-group')[2]; @@ -137,8 +158,8 @@ describe('CategoryRenderer', () => { const categoryDOM = renderer.render(category, sampleResults.reportGroups); const passedSection = categoryDOM.querySelector('.lh-category > .lh-passed-audits'); - const passedAudits = category.audits.filter(audit => audit.group !== 'perf-metric' && - audit.score === 100); + const passedAudits = category.audits.filter(audit => + audit.group && audit.group !== 'perf-metric' && audit.score === 100); const passedElements = passedSection.querySelectorAll('.lh-audit'); assert.equal(passedElements.length, passedAudits.length); }); diff --git a/lighthouse-core/test/report/v2/renderer/report-renderer-test.js b/lighthouse-core/test/report/v2/renderer/report-renderer-test.js index bb9beaa903ec..e4f18c8ffc22 100644 --- a/lighthouse-core/test/report/v2/renderer/report-renderer-test.js +++ b/lighthouse-core/test/report/v2/renderer/report-renderer-test.js @@ -117,7 +117,8 @@ describe('ReportRenderer V2', () => { it('renders a left nav', () => { const header = renderer._renderReportNav(sampleResults); - assert.equal(header.querySelectorAll('.lh-leftnav__item').length, 4); + const categoryCount = sampleResults.reportCategories.length; + assert.equal(header.querySelectorAll('.lh-leftnav__item').length, categoryCount); const categories = header.querySelectorAll('.leftnav-item__category'); const scores = header.querySelectorAll('.leftnav-item__score'); diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index b448ffe683ef..55346dfd2f20 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -1,9 +1,10 @@ { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36(KHTML, like Gecko) Chrome/59.0.3033.0 Mobile Safari/537.36", - "lighthouseVersion": "2.0.0-alpha.4", - "generatedTime": "2017-05-14T18:40:45.644Z", - "initialUrl": "http://localhost:3000/dobetterweb/dbw_tester.html", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3358.0 Safari/537.36", + "lighthouseVersion": "2.9.1", + "generatedTime": "2018-03-07T22:58:39.810Z", + "initialUrl": "http://localhost:10200/dobetterweb/dbw_tester.html", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "runWarnings": [], "audits": { "is-on-https": { "score": false, @@ -12,14 +13,13 @@ "extendedInfo": { "value": [ { - "url": "ajax.googleapis.com/…3.2.1/jquery.min.js" + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" } ] }, "scoringMode": "binary", "name": "is-on-https", - "category": "Security", - "description": "Uses HTTPS", + "description": "Does not use 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", @@ -29,8 +29,8 @@ }, "items": [ { - "type": "text", - "text": "ajax.googleapis.com/…3.2.1/jquery.min.js" + "type": "url", + "value": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" } ] } @@ -41,8 +41,7 @@ "rawValue": false, "scoringMode": "binary", "name": "redirects-http", - "category": "Security", - "description": "Redirects HTTP traffic to HTTPS", + "description": "Does not redirect 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)." }, "service-worker": { @@ -51,8 +50,7 @@ "rawValue": false, "scoringMode": "binary", "name": "service-worker", - "category": "Offline", - "description": "Registers a Service Worker", + "description": "Does not register 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)." }, "works-offline": { @@ -61,8 +59,7 @@ "rawValue": false, "scoringMode": "binary", "name": "works-offline", - "category": "Offline", - "description": "Responds with a 200 when offline", + "description": "Does not respond 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)." }, "viewport": { @@ -72,7 +69,6 @@ "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)." }, @@ -82,32 +78,34 @@ "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)." }, "first-meaningful-paint": { - "score": 98, - "displayValue": "1422.4ms", - "rawValue": 1422.4, - "optimalValue": "< 1,600 ms", + "score": 51, + "displayValue": "3,970 ms", + "rawValue": 3969.1, "extendedInfo": { "value": { "timestamps": { - "navStart": 482337143550, - "fCP": 482338565967, - "fMP": 482338565978 + "navStart": 185603319912, + "fCP": 185607289047, + "fMP": 185607289048, + "onLoad": 185608244374, + "endOfTrace": 185613601189 }, "timings": { "navStart": 0, - "fCP": 1422.417, - "fMP": 1422.428 - } + "fCP": 3969.135, + "fMP": 3969.136, + "onLoad": 4924.462, + "endOfTrace": 10281.277 + }, + "fmpFellBack": false } }, "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)." }, @@ -118,113 +116,58 @@ "extendedInfo": { "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" - }, + "firstRequestLatencies": [ { - "url": "http://localhost:3000/zone.js", - "latency": "155.07" + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "latency": "570.56" }, { - "url": "http://localhost:3000/favicon.ico", - "latency": "154.19" + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "latency": "564.12" } ], "isFast": true, - "timeToFirstInteractive": 1735.8040000166893 + "timeToFirstInteractive": 4927.278 } }, "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)." + "helpText": "A fast page load over a 3G network ensures a good mobile user experience. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g)." }, "speed-index-metric": { - "score": 97, - "displayValue": "1433", - "rawValue": 1433, - "optimalValue": "< 1,250", + "score": 60, + "displayValue": "4,565", + "rawValue": 4565, "extendedInfo": { "value": { "timings": { - "firstVisualChange": 1433, - "visuallyComplete": 1433, - "speedIndex": 1433.2870000004768, - "perceptualSpeedIndex": 1433.2870000004768 + "firstVisualChange": 3969, + "visuallyReady": 4791.470999985933, + "visuallyComplete": 4791, + "perceptualSpeedIndex": 4565.36406660484 }, "timestamps": { - "firstVisualChange": 482338576550, - "visuallyComplete": 482338576550, - "speedIndex": 482338576837, - "perceptualSpeedIndex": 482338576837 + "firstVisualChange": 185607288912, + "visuallyReady": 185608111383, + "visuallyComplete": 185608110912, + "perceptualSpeedIndex": 185607885276.0666 }, "frames": [ { - "timestamp": 482337143.55, + "timestamp": 185603319.912, "progress": 0 }, { - "timestamp": 482338576.837, - "progress": 100 + "timestamp": 185607289.343, + "progress": 20.35111741380658 }, { - "timestamp": 482338585.176, - "progress": 100 + "timestamp": 185607891.18, + "progress": 47.059476427932246 }, { - "timestamp": 482338897.487, + "timestamp": 185608111.383, "progress": 100 } ] @@ -232,15 +175,79 @@ }, "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)." }, + "screenshot-thumbnails": { + "score": 100, + "displayValue": "true", + "rawValue": true, + "scoringMode": "binary", + "informative": true, + "name": "screenshot-thumbnails", + "description": "Screenshot Thumbnails", + "helpText": "This is what the load of your site looked like.", + "details": { + "type": "filmstrip", + "scale": 4927.278, + "items": [ + { + "timing": 493, + "timestamp": 185603812639.80002, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 985, + "timestamp": 185604305367.6, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 1478, + "timestamp": 185604798095.4, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 1971, + "timestamp": 185605290823.19998, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 2464, + "timestamp": 185605783551, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 2956, + "timestamp": 185606276278.80002, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 3449, + "timestamp": 185606769006.6, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 3942, + "timestamp": 185607261734.4, + "data": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIANUAeAMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AP1ToAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD//Z" + }, + { + "timing": 4435, + "timestamp": 185607754462.19998, + "data": "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BcA634e/BXxh8PvH95cW2neCm8I6rFpcr2kQmSTRJbRDGYbOIxFJE2BQjl4yrMzFDnZQBy2jfs1ePdG8N6Bp4n8OvPpGh+KNNjLXc5jlfUbhZrVyBbj5RtAkGeAfl30AdX47+Bvijxl8XtE8YIuhWUNrqeg3lygupWldbFdSMuP3OC2+/UKDjKxkkqTtoA7/AOAHgTUvhf8ABXwb4S1h7STU9G06OymewkaSBig27kJVTg46FQR0oA7+gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA//9k=" + }, + { + "timing": 4927, + "timestamp": 185608247190, + "data": "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" + } + ] + } + }, "estimated-input-latency": { "score": 100, - "displayValue": "16ms", + "displayValue": "16 ms", "rawValue": 16, - "optimalValue": "< 50 ms", "extendedInfo": { "value": [ { @@ -257,165 +264,139 @@ }, { "percentile": 0.99, - "time": 82.478669999472 + "time": 92.83829500000138 }, { "percentile": 1, - "time": 156.88399999999183 + "time": 138.53700000000208 } ] }, "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)." + "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 latency is higher than 50 ms, users may perceive your app as laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency)." + }, + "errors-in-console": { + "score": false, + "displayValue": "5", + "rawValue": 5, + "scoringMode": "binary", + "name": "errors-in-console", + "description": "Browser errors were logged to the console", + "helpText": "Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns.", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "description", + "itemType": "code", + "text": "Description" + } + ], + "items": [ + { + "source": "other", + "description": "Application Cache Error event: Manifest fetch failed (404) http://localhost:10200/dobetterweb/clock.appcache", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html" + }, + { + "source": "network", + "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200" + }, + { + "source": "network", + "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", + "url": "http://localhost:10200/favicon.ico" + }, + { + "source": "network", + "description": "Failed to load resource: the server responded with a status of 404 (Not Found)", + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200" + }, + { + "source": "Runtime.exception", + "description": "Error: An error\n at http://localhost:10200/dobetterweb/dbw_tester.html:42:38", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html" + } + ] + } + }, + "time-to-first-byte": { + "score": true, + "displayValue": "", + "rawValue": 570.5630000000001, + "extendedInfo": { + "value": { + "wastedMs": -29.436999999999898 + } + }, + "scoringMode": "binary", + "informative": true, + "name": "time-to-first-byte", + "description": "Keep server response times low (TTFB)", + "helpText": "Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/chrome-devtools/network-performance/issues).", + "details": { + "summary": { + "wastedMs": -29.436999999999898 + } + } }, "first-interactive": { - "score": 99, - "displayValue": "1,740ms", - "rawValue": 1735.8040000166893, + "score": 82, + "displayValue": "4,930 ms", + "rawValue": 4927.278, "extendedInfo": { "value": { - "timeInMs": 1735.8040000166893, - "timestamp": 482338879354.00006 + "timeInMs": 4927.278, + "timestamp": 185608247190 } }, "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." + "helpText": "First Interactive marks the time at which the page is minimally interactive. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-interactive)." }, "consistently-interactive": { - "score": 99, - "displayValue": "1,740ms", - "rawValue": 1735.8040000610351, + "score": 82, + "displayValue": "4,930 ms", + "rawValue": 4927.278, "extendedInfo": { "value": { "cpuQuietPeriod": { - "start": 482338879.35400003, - "end": 482346002.207 + "start": 185608247.19, + "end": 185613601.189 }, "networkQuietPeriod": { - "start": 482338404.903, - "end": 482346002.207 + "start": 185607537.382, + "end": 185613601.189 }, "cpuQuietPeriods": [ { - "start": 482338879.35400003, - "end": 482346002.207 + "start": 185608247.19, + "end": 185613601.189 } ], "networkQuietPeriods": [ { - "start": 482338404.903, - "end": 482346002.207 + "start": 185607537.382, + "end": 185613601.189 } ], - "timestamp": 482338879354.00006, - "timeInMs": 1735.8040000610351 + "timestamp": 185608247190, + "timeInMs": 4927.278 } }, "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." - }, - "time-to-interactive": { - "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 - } - }, - "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)." + "helpText": "Consistently Interactive marks the time at which the page is fully interactive. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/consistently-interactive)." }, "user-timings": { "score": true, @@ -427,29 +408,11 @@ "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" - } - ], + "headings": [], "items": [] } }, @@ -457,165 +420,173 @@ "score": false, "displayValue": "13", "rawValue": false, - "optimalValue": 0, "extendedInfo": { "value": { "chains": { - "68289.1": { + "F3B687683512E0F003DD41EB23E2091A": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "startTime": 482337.146085, - "endTime": 482337.352812, - "responseReceivedTime": 482337.302538, - "transferSize": 10664 + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "startTime": 185603.321221, + "endTime": 185603.961376, + "responseReceivedTime": 185603.89718499998, + "transferSize": 12640 }, "children": { - "68289.2": { + "75994.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 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", + "startTime": 185603.951516, + "endTime": 185605.956256, + "responseReceivedTime": 185605.955291, + "transferSize": 821 }, "children": {} }, - "68289.3": { + "75994.3": { "request": { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", - "startTime": 482337.379707, - "endTime": 482337.39337, - "responseReceivedTime": -1, - "transferSize": 0 + "startTime": 185603.956717, + "endTime": 185604.52588, + "responseReceivedTime": 185604.52470399998, + "transferSize": 821 }, "children": {} }, - "68289.4": { + "75994.4": { "request": { - "url": "http://localhost:3000/dobetterweb/unknown404.css?delay=200", - "startTime": 482337.380844, - "endTime": 482337.541672, - "responseReceivedTime": 482337.538673, - "transferSize": 133 + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200", + "startTime": 185603.957861, + "endTime": 185604.534512, + "responseReceivedTime": 185604.532778, + "transferSize": 139 }, "children": {} }, - "68289.5": { + "75994.5": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2200", - "startTime": 482337.381144, - "endTime": 482337.546151, - "responseReceivedTime": 482337.545695, - "transferSize": 984 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", + "startTime": 185603.959225, + "endTime": 185606.170588, + "responseReceivedTime": 185606.169761, + "transferSize": 821 }, "children": {} }, - "68289.6": { + "75994.6": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "startTime": 482337.381999, - "endTime": 482337.55391, - "responseReceivedTime": 482337.55344, - "transferSize": 1273 + "url": "http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled", + "startTime": 185603.960011, + "endTime": 185604.541262, + "responseReceivedTime": 185604.54052399998, + "transferSize": 1108 }, "children": {} }, - "68289.7": { + "75994.7": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_partial_a.html?delay=200", - "startTime": 482337.382781, - "endTime": 482337.56081, - "responseReceivedTime": 482337.560413, - "transferSize": 920 + "url": "http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200", + "startTime": 185603.961819, + "endTime": 185604.549739, + "responseReceivedTime": 185604.54903999998, + "transferSize": 736 }, "children": {} }, - "68289.8": { + "75994.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 + "url": "http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync", + "startTime": 185603.962566, + "endTime": 185605.097653, + "responseReceivedTime": 185605.096858, + "transferSize": 733 }, "children": {} }, - "68289.9": { + "75994.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 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", + "startTime": 185603.964089, + "endTime": 185607.537382, + "responseReceivedTime": 185607.53660999998, + "transferSize": 821 }, "children": {} }, - "68289.10": { + "75994.10": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "startTime": 482337.385661, - "endTime": 482337.70328, - "responseReceivedTime": 482337.69617, - "transferSize": 1749 + "url": "http://localhost:10200/dobetterweb/dbw_tester.js", + "startTime": 185603.965303, + "endTime": 185605.113307, + "responseReceivedTime": 185605.104776, + "transferSize": 1703 }, "children": {} }, - "68289.11": { + "75994.11": { "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482337.386495, - "endTime": 482338.489729, - "responseReceivedTime": 482337.710627, - "transferSize": 133 + "url": "http://localhost:10200/dobetterweb/empty_module.js?delay=500", + "startTime": 185603.96675, + "endTime": 185604.557407, + "responseReceivedTime": 185604.556719, + "transferSize": 144 }, "children": {} }, - "68289.12": { + "75994.21": { "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 + "url": "http://localhost:10200/zone.js", + "startTime": 185606.170955, + "endTime": 185607.28227, + "responseReceivedTime": 185606.742005, + "transferSize": 71654 }, "children": {} }, - "68289.22": { + "75994.22": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "startTime": 482338.24998, - "endTime": 482338.404903, - "responseReceivedTime": 482338.404314, - "transferSize": 984 + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "startTime": 185607.195975, + "endTime": 185608.117509, + "responseReceivedTime": 185607.822806, + "transferSize": 30174 }, "children": {} }, - "68289.24": { + "75994.28": { "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482338.577149, - "endTime": 482338.735018, - "responseReceivedTime": 482338.733016, - "transferSize": 133 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", + "startTime": 185606.245562, + "endTime": 185607.285454, + "responseReceivedTime": 185606.82147599998, + "transferSize": 821 }, "children": {} } } + }, + "75994.33": { + "request": { + "url": "http://localhost:10200/favicon.ico", + "startTime": 185608.288594, + "endTime": 185608.857719, + "responseReceivedTime": 185608.85586200003, + "transferSize": 221 + }, + "children": {} } }, "longestChain": { - "duration": 1588.9329999918118, - "length": 2, - "transferSize": 133 + "duration": 5536.498000001302, + "length": 1, + "transferSize": 221 } } }, "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).", + "helpText": "The Critical Request Chains below show you what resources are issued with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/critical-request-chains).", "details": { "type": "criticalrequestchain", "header": { @@ -623,152 +594,184 @@ "text": "View critical network waterfall:" }, "chains": { - "68289.1": { + "F3B687683512E0F003DD41EB23E2091A": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "startTime": 482337.146085, - "endTime": 482337.352812, - "responseReceivedTime": 482337.302538, - "transferSize": 10664 + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "startTime": 185603.321221, + "endTime": 185603.961376, + "responseReceivedTime": 185603.89718499998, + "transferSize": 12640 }, "children": { - "68289.2": { + "75994.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 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", + "startTime": 185603.951516, + "endTime": 185605.956256, + "responseReceivedTime": 185605.955291, + "transferSize": 821 }, "children": {} }, - "68289.3": { + "75994.3": { "request": { "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", - "startTime": 482337.379707, - "endTime": 482337.39337, - "responseReceivedTime": -1, - "transferSize": 0 + "startTime": 185603.956717, + "endTime": 185604.52588, + "responseReceivedTime": 185604.52470399998, + "transferSize": 821 }, "children": {} }, - "68289.4": { + "75994.4": { "request": { - "url": "http://localhost:3000/dobetterweb/unknown404.css?delay=200", - "startTime": 482337.380844, - "endTime": 482337.541672, - "responseReceivedTime": 482337.538673, - "transferSize": 133 + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200", + "startTime": 185603.957861, + "endTime": 185604.534512, + "responseReceivedTime": 185604.532778, + "transferSize": 139 }, "children": {} }, - "68289.5": { + "75994.5": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?delay=2200", - "startTime": 482337.381144, - "endTime": 482337.546151, - "responseReceivedTime": 482337.545695, - "transferSize": 984 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", + "startTime": 185603.959225, + "endTime": 185606.170588, + "responseReceivedTime": 185606.169761, + "transferSize": 821 }, "children": {} }, - "68289.6": { + "75994.6": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_disabled.css?delay=200&isdisabled", - "startTime": 482337.381999, - "endTime": 482337.55391, - "responseReceivedTime": 482337.55344, - "transferSize": 1273 + "url": "http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled", + "startTime": 185603.960011, + "endTime": 185604.541262, + "responseReceivedTime": 185604.54052399998, + "transferSize": 1108 }, "children": {} }, - "68289.7": { + "75994.7": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_partial_a.html?delay=200", - "startTime": 482337.382781, - "endTime": 482337.56081, - "responseReceivedTime": 482337.560413, - "transferSize": 920 + "url": "http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200", + "startTime": 185603.961819, + "endTime": 185604.549739, + "responseReceivedTime": 185604.54903999998, + "transferSize": 736 }, "children": {} }, - "68289.8": { + "75994.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 + "url": "http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync", + "startTime": 185603.962566, + "endTime": 185605.097653, + "responseReceivedTime": 185605.096858, + "transferSize": 733 }, "children": {} }, - "68289.9": { + "75994.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 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", + "startTime": 185603.964089, + "endTime": 185607.537382, + "responseReceivedTime": 185607.53660999998, + "transferSize": 821 }, "children": {} }, - "68289.10": { + "75994.10": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "startTime": 482337.385661, - "endTime": 482337.70328, - "responseReceivedTime": 482337.69617, - "transferSize": 1749 + "url": "http://localhost:10200/dobetterweb/dbw_tester.js", + "startTime": 185603.965303, + "endTime": 185605.113307, + "responseReceivedTime": 185605.104776, + "transferSize": 1703 }, "children": {} }, - "68289.11": { + "75994.11": { "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482337.386495, - "endTime": 482338.489729, - "responseReceivedTime": 482337.710627, - "transferSize": 133 + "url": "http://localhost:10200/dobetterweb/empty_module.js?delay=500", + "startTime": 185603.96675, + "endTime": 185604.557407, + "responseReceivedTime": 185604.556719, + "transferSize": 144 }, "children": {} }, - "68289.12": { + "75994.21": { "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 + "url": "http://localhost:10200/zone.js", + "startTime": 185606.170955, + "endTime": 185607.28227, + "responseReceivedTime": 185606.742005, + "transferSize": 71654 }, "children": {} }, - "68289.22": { + "75994.22": { "request": { - "url": "http://localhost:3000/dobetterweb/dbw_tester.css?scriptActivated&delay=200", - "startTime": 482338.24998, - "endTime": 482338.404903, - "responseReceivedTime": 482338.404314, - "transferSize": 984 + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "startTime": 185607.195975, + "endTime": 185608.117509, + "responseReceivedTime": 185607.822806, + "transferSize": 30174 }, "children": {} }, - "68289.24": { + "75994.28": { "request": { - "url": "http://localhost:3000/zone.js", - "startTime": 482338.577149, - "endTime": 482338.735018, - "responseReceivedTime": 482338.733016, - "transferSize": 133 + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", + "startTime": 185606.245562, + "endTime": 185607.285454, + "responseReceivedTime": 185606.82147599998, + "transferSize": 821 }, "children": {} } } + }, + "75994.33": { + "request": { + "url": "http://localhost:10200/favicon.ico", + "startTime": 185608.288594, + "endTime": 185608.857719, + "responseReceivedTime": 185608.85586200003, + "transferSize": 221 + }, + "children": {} } }, "longestChain": { - "duration": 1588.9329999918118, - "length": 2, - "transferSize": 133 + "duration": 5536.498000001302, + "length": 1, + "transferSize": 221 + } + } + }, + "redirects": { + "score": 100, + "displayValue": "0 ms", + "rawValue": 0, + "extendedInfo": { + "value": { + "wastedMs": 0 + } + }, + "scoringMode": "binary", + "name": "redirects", + "description": "Avoids page redirects", + "helpText": "Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/speed/docs/insights/AvoidRedirects).", + "details": { + "type": "table", + "headings": [], + "items": [], + "summary": { + "wastedMs": 0 } } }, @@ -776,13 +779,17 @@ "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.", + "debugString": "Failures: No manifest was fetched, Site does not register a service worker, Service worker does not successfully serve the manifest's start_url, No start URL to fetch: No usable web app manifest found on page http://localhost:10200/dobetterweb/dbw_tester.html.", "extendedInfo": { "value": { + "warnings": [ + "No start URL to fetch: No usable web app manifest found on page http://localhost:10200/dobetterweb/dbw_tester.html" + ], "failures": [ "No manifest was fetched", - "Site does not register a Service Worker", - "Manifest start_url is not cached by a Service Worker" + "Site does not register a service worker", + "Service worker does not successfully serve the manifest's start_url", + "No start URL to fetch: No usable web app manifest found on page http://localhost:10200/dobetterweb/dbw_tester.html" ], "manifestValues": { "isParseFailure": true, @@ -793,9 +800,8 @@ }, "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." + "description": "User will not be prompted to Install the Web App", + "helpText": "Browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/install-prompt)." }, "splash-screen": { "score": false, @@ -816,9 +822,8 @@ }, "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" + "description": "Is not configured for a custom splash screen", + "helpText": "A themed splash screen ensures a high-quality experience when users launch your app from their homescreens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/custom-splash-screen)." }, "themed-omnibox": { "score": false, @@ -841,9 +846,8 @@ }, "scoringMode": "binary", "name": "themed-omnibox", - "category": "PWA", - "description": "Address bar has been themed", - "helpText": "The browser address bar can be themed to match your site. Adding the `theme-color` [meta tag](https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android) to a web page will colorize the address bar when a user browses the site, while adding the `theme_color` [attribute](https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color) to your web app's manifest will colorize the address bar once the app has been added to homescreen." + "description": "Address bar does not match brand colors", + "helpText": "The browser address bar can be themed to match your site. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/address-bar)." }, "manifest-short-name-length": { "score": false, @@ -851,9 +855,8 @@ "rawValue": false, "scoringMode": "binary", "name": "manifest-short-name-length", - "category": "Manifest", - "description": "Manifest's `short_name` won't be truncated when displayed on homescreen", - "helpText": "Make your app's `short_name` less than 12 characters to ensure that it's not truncated on homescreens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/manifest-short_name-is-not-truncated)." + "description": "Manifest's `short_name` will be truncated when displayed on homescreen", + "helpText": "Make your app's `short_name` fewer than 12 characters to ensure that it's not truncated on homescreens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/manifest-short_name-is-not-truncated)." }, "content-width": { "score": true, @@ -862,206 +865,651 @@ "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)." }, + "image-aspect-ratio": { + "score": false, + "displayValue": "", + "rawValue": false, + "scoringMode": "binary", + "name": "image-aspect-ratio", + "description": "Displays images with incorrect aspect ratio", + "helpText": "Image display dimensions should match natural aspect ratio.", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "thumbnail", + "text": "" + }, + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "displayedAspectRatio", + "itemType": "text", + "text": "Aspect Ratio (Displayed)" + }, + { + "key": "actualAspectRatio", + "itemType": "text", + "text": "Aspect Ratio (Actual)" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "displayedAspectRatio": "480 x 57\n (8.42)", + "actualAspectRatio": "480 x 318\n (1.51)", + "doRatiosMatch": false + } + ] + } + }, "deprecations": { "score": false, - "displayValue": "4 warnings found", + "displayValue": "3 warnings found", "rawValue": false, "extendedInfo": { "value": [ { - "label": "line: 45", - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "code": "Calling Element.createShadowRoot() for an element which already hosts a shadow root is deprecated. See https://www.chromestatus.com/features/4668884095336448 for more details.", - "source": "deprecation", - "level": "warning", - "text": "Calling Element.createShadowRoot() for an element which already hosts a shadow root is deprecated. See https://www.chromestatus.com/features/4668884095336448 for more details.", - "timestamp": 1494787235458.94, - "lineNumber": 45, - "stackTrace": { - "callFrames": [ - { - "functionName": "", - "scriptId": "32", - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "lineNumber": 45, - "columnNumber": 6 - }, - { - "functionName": "", - "scriptId": "32", - "url": "http://localhost:3000/dobetterweb/dbw_tester.js", - "lineNumber": 48, - "columnNumber": 2 - } - ] - } - }, - { - "label": "line: 294", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "code": "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.", + "value": "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", "source": "deprecation", - "level": "warning", - "text": "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.", - "timestamp": 1494787236069.11, - "lineNumber": 294, - "stackTrace": { - "callFrames": [ - { - "functionName": "deprecationsTest", - "scriptId": "35", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "lineNumber": 294, - "columnNumber": 6 - }, - { - "functionName": "", - "scriptId": "35", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "lineNumber": 314, - "columnNumber": 2 - } - ] - } + "lineNumber": 322 }, { - "label": "line: 297", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "code": "'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead.", + "value": "'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead.", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", "source": "deprecation", - "level": "warning", - "text": "'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead.", - "timestamp": 1494787236231.84, - "lineNumber": 297, - "stackTrace": { - "callFrames": [ - { - "functionName": "deprecationsTest", - "scriptId": "35", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "lineNumber": 297, - "columnNumber": 8 - }, - { - "functionName": "", - "scriptId": "35", - "url": "http://localhost:3000/dobetterweb/dbw_tester.html", - "lineNumber": 314, - "columnNumber": 2 - } - ] - } + "lineNumber": 325 }, { - "label": "line: ???", - "url": "Unable to determine URL", - "code": "/deep/ combinator is deprecated and will be a no-op in M60, around August 2017. See https://www.chromestatus.com/features/4964279606312960 for more details.", - "source": "deprecation", - "level": "warning", - "text": "/deep/ combinator is deprecated and will be a no-op in M60, around August 2017. See https://www.chromestatus.com/features/4964279606312960 for more details.", - "timestamp": 1494787236238.11 + "value": "/deep/ combinator is no longer supported in CSS dynamic profile.It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.", + "url": "", + "source": "deprecation" } ] }, "scoringMode": "binary", "name": "deprecations", - "category": "Deprecations", - "description": "Avoids deprecated APIs", + "description": "Uses deprecated API's", "helpText": "Deprecated APIs will eventually be removed from the browser. [Learn more](https://www.chromestatus.com/features#deprecated).", "details": { "type": "table", - "header": "View Details", - "itemHeaders": [ + "headings": [ { - "type": "text", + "key": "value", "itemType": "code", "text": "Deprecation / Warning" }, { - "type": "text", + "key": "url", "itemType": "url", "text": "URL" }, { - "type": "text", + "key": "lineNumber", "itemType": "text", "text": "Line" } ], "items": [ - [ - { - "type": "code", - "text": "Calling Element.createShadowRoot() for an element which already hosts a shadow root is deprecated. See https://www.chromestatus.com/features/4668884095336448 for more details." - }, - { - "type": "url", - "text": "/dobetterweb/dbw_tester.js" - }, - { - "type": "text", - "text": 45 - } - ], - [ - { - "type": "code", - "text": "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/." - }, - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": 294 - } - ], - [ - { - "type": "code", - "text": "'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead." - }, - { - "type": "url", - "text": "/dobetterweb/dbw_tester.html" - }, - { - "type": "text", - "text": 297 - } - ], - [ - { - "type": "code", - "text": "/deep/ combinator is deprecated and will be a no-op in M60, around August 2017. See https://www.chromestatus.com/features/4964279606312960 for more details." - }, - { - "type": "url", - "text": "" - }, - { - "type": "text" - } - ] + { + "value": "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "source": "deprecation", + "lineNumber": 322 + }, + { + "value": "'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead.", + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "source": "deprecation", + "lineNumber": 325 + }, + { + "value": "/deep/ combinator is no longer supported in CSS dynamic profile.It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.", + "url": "", + "source": "deprecation" + } ] } }, - "pwa-page-transitions": { - "score": false, + "mainthread-work-breakdown": { + "score": true, + "displayValue": "1,360 ms", + "rawValue": 1359.7759999930859, + "extendedInfo": { + "value": { + "Evaluate Script": 1121.2470000088215, + "Layout": 89.00499999523163, + "Parse HTML": 63.04200002551079, + "Recalculate Style": 32.57899996638298, + "Minor GC": 19.94599997997284, + "Compile Script": 7.7519999742507935, + "Run Microtasks": 6.680999994277954, + "DOM GC": 6.252999991178513, + "Paint": 4.94200000166893, + "Update Layer Tree": 4.238999992609024, + "Composite Layers": 2.732000023126602, + "Parse Stylesheet": 1.3200000524520874, + "XHR Ready State Change": 0.026999980211257935, + "XHR Load": 0.011000007390975952 + } + }, + "scoringMode": "binary", + "informative": true, + "name": "mainthread-work-breakdown", + "description": "Main thread work breakdown", + "helpText": "Consider reducing the time spent parsing, compiling and executing JS.You may find delivering smaller JS payloads helps with this.", + "details": { + "type": "table", + "headings": [ + { + "key": "group", + "itemType": "text", + "text": "Category" + }, + { + "key": "category", + "itemType": "text", + "text": "Work" + }, + { + "key": "duration", + "itemType": "text", + "text": "Time spent" + } + ], + "items": [ + { + "category": "Evaluate Script", + "group": "Script Evaluation", + "duration": "1,121 ms" + }, + { + "category": "Run Microtasks", + "group": "Script Evaluation", + "duration": "7 ms" + }, + { + "category": "XHR Ready State Change", + "group": "Script Evaluation", + "duration": "0 ms" + }, + { + "category": "XHR Load", + "group": "Script Evaluation", + "duration": "0 ms" + }, + { + "category": "Layout", + "group": "Style & Layout", + "duration": "89 ms" + }, + { + "category": "Recalculate Style", + "group": "Style & Layout", + "duration": "33 ms" + }, + { + "category": "Parse HTML", + "group": "Parsing HTML & CSS", + "duration": "63 ms" + }, + { + "category": "Parse Stylesheet", + "group": "Parsing HTML & CSS", + "duration": "1 ms" + }, + { + "category": "Minor GC", + "group": "Garbage collection", + "duration": "20 ms" + }, + { + "category": "DOM GC", + "group": "Garbage collection", + "duration": "6 ms" + }, + { + "category": "Compile Script", + "group": "Script Parsing & Compile", + "duration": "8 ms" + }, + { + "category": "Update Layer Tree", + "group": "Compositing", + "duration": "4 ms" + }, + { + "category": "Composite Layers", + "group": "Compositing", + "duration": "3 ms" + }, + { + "category": "Paint", + "group": "Paint", + "duration": "5 ms" + } + ] + } + }, + "bootup-time": { + "score": true, + "displayValue": "1,300 ms", + "rawValue": 1302.3579999804497, + "extendedInfo": { + "value": { + "http://localhost:10200/dobetterweb/dbw_tester.html": { + "Script Evaluation": 954.5010000169277, + "Style & Layout": 116.7219999730587, + "Parsing HTML & CSS": 63.04200002551079, + "Script Parsing & Compile": 2.776999980211258 + }, + "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js": { + "Script Evaluation": 80.03200000524521, + "Script Parsing & Compile": 1.2049999833106995, + "Style & Layout": 0.03799998760223389 + }, + "http://localhost:10200/zone.js": { + "Script Evaluation": 76.56400001049042, + "Script Parsing & Compile": 1.6739999949932098 + }, + "http://localhost:10200/dobetterweb/dbw_tester.js": { + "Script Evaluation": 3.79899999499321, + "Script Parsing & Compile": 2.0040000081062317 + } + } + }, + "scoringMode": "binary", + "name": "bootup-time", + "description": "JavaScript boot-up time", + "helpText": "Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn more](https://developers.google.com/web/lighthouse/audits/bootup).", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "scripting", + "itemType": "text", + "text": "Script Evaluation" + }, + { + "key": "scriptParseCompile", + "itemType": "text", + "text": "Script Parsing & Compile" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "sum": 957.277999997139, + "scripting": "955 ms", + "scriptParseCompile": "3 ms" + }, + { + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "sum": 81.23699998855591, + "scripting": "80 ms", + "scriptParseCompile": "1 ms" + }, + { + "url": "http://localhost:10200/zone.js", + "sum": 78.23800000548363, + "scripting": "77 ms", + "scriptParseCompile": "2 ms" + } + ], + "summary": { + "wastedMs": 1302.3579999804497 + } + } + }, + "uses-rel-preload": { + "score": 100, + "displayValue": "0 ms", + "rawValue": 0, + "extendedInfo": { + "value": [] + }, + "scoringMode": "numeric", + "informative": true, + "name": "uses-rel-preload", + "description": "Preload key requests", + "helpText": "Consider using to prioritize fetching late-discovered resources sooner [Learn more](https://developers.google.com/web/updates/2016/03/link-rel-preload).", + "details": { + "type": "table", + "headings": [], + "items": [], + "summary": { + "wastedMs": 0 + } + } + }, + "font-display": { + "score": true, "displayValue": "", - "rawValue": false, + "rawValue": true, + "scoringMode": "binary", + "name": "font-display", + "description": "All text remains visible during webfont loads", + "helpText": "Leverage the font-display CSS feature to ensure text is user-visible while webfonts are loading. [Learn more](https://developers.google.com/web/updates/2016/02/font-display).", + "details": { + "type": "table", + "headings": [], + "items": [] + } + }, + "network-requests": { + "score": 100, + "displayValue": "18", + "rawValue": 18, + "extendedInfo": { + "value": [ + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "startTime": 0, + "endTime": 640.1550000009593, + "transferSize": 12640, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", + "startTime": 630.2950000099372, + "endTime": 2635.035000013886, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", + "startTime": 635.496000002604, + "endTime": 1204.6590000099968, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200", + "startTime": 636.6400000115391, + "endTime": 1213.2910000218544, + "transferSize": 139, + "statusCode": 404 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", + "startTime": 638.0040000076406, + "endTime": 2849.3670000170823, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled", + "startTime": 638.7899999972433, + "endTime": 1220.04100002232, + "transferSize": 1108, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200", + "startTime": 640.5979999981355, + "endTime": 1228.5180000180844, + "transferSize": 736, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync", + "startTime": 641.3450000109151, + "endTime": 1776.4320000133011, + "transferSize": 733, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", + "startTime": 642.8679999953602, + "endTime": 4216.161000018474, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.js", + "startTime": 644.0820000134408, + "endTime": 1792.0860000012908, + "transferSize": 1703, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/empty_module.js?delay=500", + "startTime": 645.529000001261, + "endTime": 1236.1859999946319, + "transferSize": 144, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "startTime": 3951.6250000160653, + "endTime": 4779.641000000993, + "transferSize": 24741, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/zone.js", + "startTime": 2849.7340000176337, + "endTime": 3961.049000005005, + "transferSize": 71654, + "statusCode": 200 + }, + { + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "startTime": 3874.7540000185836, + "endTime": 4796.288000012282, + "transferSize": 30174, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", + "startTime": 2924.34100000537, + "endTime": 3964.233000006061, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "startTime": 3066.252999997232, + "endTime": 3772.7560000203084, + "transferSize": 12640, + "statusCode": 200 + }, + { + "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", + "startTime": 3829.6360000094865, + "endTime": 3968.59800000675, + "transferSize": 0, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/favicon.ico", + "startTime": 4967.373000021325, + "endTime": 5536.498000001302, + "transferSize": 221, + "statusCode": 404 + } + ] + }, "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." + "name": "network-requests", + "description": "Network Requests", + "helpText": "Lists the network requests that were made during page load.", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "itemType": "url", + "text": "URL" + }, + { + "key": "startTime", + "itemType": "ms", + "granularity": 1, + "text": "Start Time" + }, + { + "key": "endTime", + "itemType": "ms", + "granularity": 1, + "text": "End Time" + }, + { + "key": "transferSize", + "itemType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "text": "Transfer Size" + }, + { + "key": "statusCode", + "itemType": "text", + "text": "Status Code" + } + ], + "items": [ + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "startTime": 0, + "endTime": 640.1550000009593, + "transferSize": 12640, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", + "startTime": 630.2950000099372, + "endTime": 2635.035000013886, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", + "startTime": 635.496000002604, + "endTime": 1204.6590000099968, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/unknown404.css?delay=200", + "startTime": 636.6400000115391, + "endTime": 1213.2910000218544, + "transferSize": 139, + "statusCode": 404 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", + "startTime": 638.0040000076406, + "endTime": 2849.3670000170823, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_disabled.css?delay=200&isdisabled", + "startTime": 638.7899999972433, + "endTime": 1220.04100002232, + "transferSize": 1108, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_partial_a.html?delay=200", + "startTime": 640.5979999981355, + "endTime": 1228.5180000180844, + "transferSize": 736, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_partial_b.html?delay=200&isasync", + "startTime": 641.3450000109151, + "endTime": 1776.4320000133011, + "transferSize": 733, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", + "startTime": 642.8679999953602, + "endTime": 4216.161000018474, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.js", + "startTime": 644.0820000134408, + "endTime": 1792.0860000012908, + "transferSize": 1703, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/empty_module.js?delay=500", + "startTime": 645.529000001261, + "endTime": 1236.1859999946319, + "transferSize": 144, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/lighthouse-480x318.jpg", + "startTime": 3951.6250000160653, + "endTime": 4779.641000000993, + "transferSize": 24741, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/zone.js", + "startTime": 2849.7340000176337, + "endTime": 3961.049000005005, + "transferSize": 71654, + "statusCode": 200 + }, + { + "url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js", + "startTime": 3874.7540000185836, + "endTime": 4796.288000012282, + "transferSize": 30174, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", + "startTime": 2924.34100000537, + "endTime": 3964.233000006061, + "transferSize": 821, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/dobetterweb/dbw_tester.html", + "startTime": 3066.252999997232, + "endTime": 3772.7560000203084, + "transferSize": 12640, + "statusCode": 200 + }, + { + "url": "blob:http://localhost:10200/ae0eac03-ab9b-4a6a-b299-f5212153e277", + "startTime": 3829.6360000094865, + "endTime": 3968.59800000675, + "transferSize": 0, + "statusCode": 200 + }, + { + "url": "http://localhost:10200/favicon.ico", + "startTime": 4967.373000021325, + "endTime": 5536.498000001302, + "transferSize": 221, + "statusCode": 404 + } + ] + } }, "pwa-cross-browser": { "score": false, @@ -1071,9 +1519,19 @@ "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." + "helpText": "To reach the most number of users, sites should work across every major browser. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist#site-works-cross-browser)." + }, + "pwa-page-transitions": { + "score": false, + "displayValue": "", + "rawValue": false, + "scoringMode": "binary", + "informative": true, + "manual": true, + "name": "pwa-page-transitions", + "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. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist#page-transitions-dont-feel-like-they-block-on-the-network)." }, "pwa-each-page-has-url": { "score": false, @@ -1083,119 +1541,118 @@ "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." + "helpText": "Ensure individual pages are deep linkable via the URLs and that URLs are unique for the purpose of shareability on social media. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist#each-page-has-a-url)." }, "accesskeys": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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." + "description": "`[accesskey]` values are not unique", + "helpText": "Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more](https://dequeuniversity.com/rules/axe/2.2/accesskeys?application=lighthouse)." }, "aria-allowed-attr": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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)." + "description": "`[aria-*]` attributes do not match their roles", + "helpText": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-allowed-attr?application=lighthouse)." }, "aria-required-attr": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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)." + "description": "`[role]`s do not 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://dequeuniversity.com/rules/axe/2.2/aria-required-attr?application=lighthouse)." }, "aria-required-children": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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." + "description": "Elements with `[role]` that require specific children `[role]`s, are missing.", + "helpText": "Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-children?application=lighthouse)." }, "aria-required-parent": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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." + "description": "`[role]`s are not contained by their required parent element", + "helpText": "Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-parent?application=lighthouse)." }, "aria-roles": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "name": "aria-roles", - "category": "Accessibility", - "description": "`[role]` values are valid.", - "helpText": "ARIA roles require specific values to perform their accessibility function." + "description": "`[role]` values are not valid", + "helpText": "ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-roles?application=lighthouse)." }, "aria-valid-attr-value": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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)." + "description": "`[aria-*]` attributes do not have valid values", + "helpText": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr-value?application=lighthouse)." }, "aria-valid-attr": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "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)." + "description": "`[aria-*]` attributes are not valid or misspelled", + "helpText": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr?application=lighthouse)." }, "audio-caption": { - "score": true, + "score": false, "displayValue": "", - "rawValue": true, - "extendedInfo": {}, + "rawValue": false, "scoringMode": "binary", + "informative": true, + "notApplicable": true, "name": "audio-caption", - "category": "Accessibility", - "description": "`