diff --git a/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js b/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js index 61ee4ef6583d..d248334c0559 100644 --- a/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +++ b/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js @@ -197,7 +197,7 @@ class UnusedBytes extends Audit { static computeWastedMsWithThroughput(wastedBytes, simulator) { const bitsPerSecond = simulator.getOptions().throughput; const wastedBits = wastedBytes * 8; - const wastedMs = wastedBits / bitsPerSecond / 1000; + const wastedMs = wastedBits / bitsPerSecond * 1000; return wastedMs; } diff --git a/lighthouse-core/computed/metrics/timing-summary.js b/lighthouse-core/computed/metrics/timing-summary.js index ea017735e99a..d8a4c1a7dfe2 100644 --- a/lighthouse-core/computed/metrics/timing-summary.js +++ b/lighthouse-core/computed/metrics/timing-summary.js @@ -43,18 +43,18 @@ class TimingSummary { }; const processedTrace = await ProcessedTrace.request(trace, context); - const processedNavigation = await ProcessedNavigation.request(processedTrace, context); + const processedNavigation = await requestOrUndefined(ProcessedNavigation, processedTrace); const speedline = await Speedline.request(trace, context); - const firstContentfulPaint = await FirstContentfulPaint.request(metricComputationData, context); + const firstContentfulPaint = await requestOrUndefined(FirstContentfulPaint, metricComputationData); // eslint-disable-line max-len const firstContentfulPaintAllFrames = await requestOrUndefined(FirstContentfulPaintAllFrames, metricComputationData); // eslint-disable-line max-len - const firstMeaningfulPaint = await FirstMeaningfulPaint.request(metricComputationData, context); + const firstMeaningfulPaint = await requestOrUndefined(FirstMeaningfulPaint, metricComputationData); // eslint-disable-line max-len const largestContentfulPaint = await requestOrUndefined(LargestContentfulPaint, metricComputationData); // eslint-disable-line max-len const largestContentfulPaintAllFrames = await requestOrUndefined(LargestContentfulPaintAllFrames, metricComputationData); // eslint-disable-line max-len const interactive = await requestOrUndefined(Interactive, metricComputationData); const cumulativeLayoutShiftValues = await requestOrUndefined(CumulativeLayoutShift, trace); const maxPotentialFID = await requestOrUndefined(MaxPotentialFID, metricComputationData); const speedIndex = await requestOrUndefined(SpeedIndex, metricComputationData); - const totalBlockingTime = await TotalBlockingTime.request(metricComputationData, context); // eslint-disable-line max-len + const totalBlockingTime = await requestOrUndefined(TotalBlockingTime, metricComputationData); const { cumulativeLayoutShift, @@ -65,12 +65,12 @@ class TimingSummary { /** @type {LH.Artifacts.TimingSummary} */ const metrics = { // Include the simulated/observed performance metrics - firstContentfulPaint: firstContentfulPaint.timing, - firstContentfulPaintTs: firstContentfulPaint.timestamp, + firstContentfulPaint: firstContentfulPaint && firstContentfulPaint.timing, + firstContentfulPaintTs: firstContentfulPaint && firstContentfulPaint.timestamp, firstContentfulPaintAllFrames: firstContentfulPaintAllFrames && firstContentfulPaintAllFrames.timing, // eslint-disable-line max-len firstContentfulPaintAllFramesTs: firstContentfulPaintAllFrames && firstContentfulPaintAllFrames.timestamp, // eslint-disable-line max-len - firstMeaningfulPaint: firstMeaningfulPaint.timing, - firstMeaningfulPaintTs: firstMeaningfulPaint.timestamp, + firstMeaningfulPaint: firstMeaningfulPaint && firstMeaningfulPaint.timing, + firstMeaningfulPaintTs: firstMeaningfulPaint && firstMeaningfulPaint.timestamp, largestContentfulPaint: largestContentfulPaint && largestContentfulPaint.timing, largestContentfulPaintTs: largestContentfulPaint && largestContentfulPaint.timestamp, largestContentfulPaintAllFrames: largestContentfulPaintAllFrames && largestContentfulPaintAllFrames.timing, // eslint-disable-line max-len @@ -79,7 +79,7 @@ class TimingSummary { interactiveTs: interactive && interactive.timestamp, speedIndex: speedIndex && speedIndex.timing, speedIndexTs: speedIndex && speedIndex.timestamp, - totalBlockingTime: totalBlockingTime.timing, + totalBlockingTime: totalBlockingTime && totalBlockingTime.timing, maxPotentialFID: maxPotentialFID && maxPotentialFID.timing, cumulativeLayoutShift, cumulativeLayoutShiftMainFrame, @@ -89,27 +89,26 @@ class TimingSummary { observedTimeOrigin: processedTrace.timings.timeOrigin, observedTimeOriginTs: processedTrace.timestamps.timeOrigin, // For now, navigationStart is always timeOrigin. - // These properties might be undefined in a future major version, but preserve them for now. - observedNavigationStart: processedTrace.timings.timeOrigin, - observedNavigationStartTs: processedTrace.timestamps.timeOrigin, - observedFirstPaint: processedNavigation.timings.firstPaint, - observedFirstPaintTs: processedNavigation.timestamps.firstPaint, - observedFirstContentfulPaint: processedNavigation.timings.firstContentfulPaint, - observedFirstContentfulPaintTs: processedNavigation.timestamps.firstContentfulPaint, - observedFirstContentfulPaintAllFrames: processedNavigation.timings.firstContentfulPaintAllFrames, // eslint-disable-line max-len - observedFirstContentfulPaintAllFramesTs: processedNavigation.timestamps.firstContentfulPaintAllFrames, // eslint-disable-line max-len - observedFirstMeaningfulPaint: processedNavigation.timings.firstMeaningfulPaint, - observedFirstMeaningfulPaintTs: processedNavigation.timestamps.firstMeaningfulPaint, - observedLargestContentfulPaint: processedNavigation.timings.largestContentfulPaint, - observedLargestContentfulPaintTs: processedNavigation.timestamps.largestContentfulPaint, - observedLargestContentfulPaintAllFrames: processedNavigation.timings.largestContentfulPaintAllFrames, // eslint-disable-line max-len - observedLargestContentfulPaintAllFramesTs: processedNavigation.timestamps.largestContentfulPaintAllFrames, // eslint-disable-line max-len + observedNavigationStart: processedNavigation && processedNavigation.timings.timeOrigin, + observedNavigationStartTs: processedNavigation && processedNavigation.timestamps.timeOrigin, + observedFirstPaint: processedNavigation && processedNavigation.timings.firstPaint, + observedFirstPaintTs: processedNavigation && processedNavigation.timestamps.firstPaint, + observedFirstContentfulPaint: processedNavigation && processedNavigation.timings.firstContentfulPaint, // eslint-disable-line max-len + observedFirstContentfulPaintTs: processedNavigation && processedNavigation.timestamps.firstContentfulPaint, // eslint-disable-line max-len + observedFirstContentfulPaintAllFrames: processedNavigation && processedNavigation.timings.firstContentfulPaintAllFrames, // eslint-disable-line max-len + observedFirstContentfulPaintAllFramesTs: processedNavigation && processedNavigation.timestamps.firstContentfulPaintAllFrames, // eslint-disable-line max-len + observedFirstMeaningfulPaint: processedNavigation && processedNavigation.timings.firstMeaningfulPaint, // eslint-disable-line max-len + observedFirstMeaningfulPaintTs: processedNavigation && processedNavigation.timestamps.firstMeaningfulPaint, // eslint-disable-line max-len + observedLargestContentfulPaint: processedNavigation && processedNavigation.timings.largestContentfulPaint, // eslint-disable-line max-len + observedLargestContentfulPaintTs: processedNavigation && processedNavigation.timestamps.largestContentfulPaint, // eslint-disable-line max-len + observedLargestContentfulPaintAllFrames: processedNavigation && processedNavigation.timings.largestContentfulPaintAllFrames, // eslint-disable-line max-len + observedLargestContentfulPaintAllFramesTs: processedNavigation && processedNavigation.timestamps.largestContentfulPaintAllFrames, // eslint-disable-line max-len observedTraceEnd: processedTrace.timings.traceEnd, observedTraceEndTs: processedTrace.timestamps.traceEnd, - observedLoad: processedNavigation.timings.load, - observedLoadTs: processedNavigation.timestamps.load, - observedDomContentLoaded: processedNavigation.timings.domContentLoaded, - observedDomContentLoadedTs: processedNavigation.timestamps.domContentLoaded, + observedLoad: processedNavigation && processedNavigation.timings.load, + observedLoadTs: processedNavigation && processedNavigation.timestamps.load, + observedDomContentLoaded: processedNavigation && processedNavigation.timings.domContentLoaded, + observedDomContentLoadedTs: processedNavigation && processedNavigation.timestamps.domContentLoaded, // eslint-disable-line max-len observedCumulativeLayoutShift: cumulativeLayoutShift, observedCumulativeLayoutShiftMainFrame: cumulativeLayoutShiftMainFrame, observedTotalCumulativeLayoutShift: totalCumulativeLayoutShift, @@ -123,7 +122,9 @@ class TimingSummary { observedSpeedIndexTs: (speedline.speedIndex + speedline.beginning) * 1000, }; /** @type {Record} */ - const debugInfo = {lcpInvalidated: processedNavigation.lcpInvalidated}; + const debugInfo = { + lcpInvalidated: !!(processedNavigation && processedNavigation.lcpInvalidated), + }; return {metrics, debugInfo}; } 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 fe6c9ed74447..e025545289ec 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 @@ -90,7 +90,7 @@ describe('Byte efficiency base audit', () => { const result = ByteEfficiencyAudit.createAuditProduct({ headings: baseHeadings, items: [], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); assert.deepEqual(result.details.items, []); }); @@ -104,7 +104,8 @@ describe('Byte efficiency base audit', () => { ], }, graph, - simulator + simulator, + {gatherMode: 'navigation'} ); // 900ms savings comes from the graph calculation @@ -115,22 +116,22 @@ describe('Byte efficiency base audit', () => { const perfectResult = ByteEfficiencyAudit.createAuditProduct({ headings: baseHeadings, items: [{url: 'http://example.com/', wastedBytes: 1 * 1000}], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); const goodResult = ByteEfficiencyAudit.createAuditProduct({ headings: baseHeadings, items: [{url: 'http://example.com/', wastedBytes: 20 * 1000}], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); const averageResult = ByteEfficiencyAudit.createAuditProduct({ headings: baseHeadings, items: [{url: 'http://example.com/', wastedBytes: 100 * 1000}], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); const failingResult = ByteEfficiencyAudit.createAuditProduct({ headings: baseHeadings, items: [{url: 'http://example.com/', wastedBytes: 400 * 1000}], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); assert.equal(perfectResult.score, 1, 'scores perfect wastedMs'); assert.ok(goodResult.score > 0.75 && goodResult.score < 1, 'scores good wastedMs'); @@ -143,7 +144,7 @@ describe('Byte efficiency base audit', () => { ByteEfficiencyAudit.createAuditProduct({ headings: baseHeadings, items: [{wastedBytes: 350, totalBytes: 700, wastedPercent: 50}], - }, null); + }, null, simulator, {gatherMode: 'navigation'}); }); }); @@ -154,7 +155,7 @@ describe('Byte efficiency base audit', () => { {wastedBytes: 2048, totalBytes: 4096, wastedPercent: 50}, {wastedBytes: 1986, totalBytes: 5436}, ], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); assert.equal(result.details.items[0].wastedBytes, 2048); assert.equal(result.details.items[0].totalBytes, 4096); @@ -170,7 +171,7 @@ describe('Byte efficiency base audit', () => { {wastedBytes: 450, totalBytes: 1000, wastedPercent: 50}, {wastedBytes: 400, totalBytes: 450, wastedPercent: 50}, ], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); assert.equal(result.details.items[0].wastedBytes, 450); assert.equal(result.details.items[1].wastedBytes, 400); @@ -185,7 +186,7 @@ describe('Byte efficiency base audit', () => { {wastedBytes: 512, totalBytes: 1000, wastedPercent: 50}, {wastedBytes: 1024, totalBytes: 1200, wastedPercent: 50}, ], - }, graph, simulator); + }, graph, simulator, {gatherMode: 'navigation'}); expect(result.displayValue).toBeDisplayString(/savings of 2/); }); @@ -204,7 +205,8 @@ describe('Byte efficiency base audit', () => { ], }, graph, - simulator + simulator, + {gatherMode: 'navigation'} ); assert.equal(result.numericValue, 300); @@ -221,6 +223,7 @@ describe('Byte efficiency base audit', () => { } const artifacts = { + GatherContext: {gatherMode: 'navigation'}, traces: {defaultPass: trace}, devtoolsLogs: {defaultPass: devtoolsLog}, }; @@ -259,6 +262,7 @@ describe('Byte efficiency base audit', () => { } const artifacts = { + GatherContext: {gatherMode: 'navigation'}, traces: {defaultPass: traceM78}, devtoolsLogs: {defaultPass: devtoolsLogM78}, }; @@ -290,6 +294,7 @@ describe('Byte efficiency base audit', () => { } const artifacts = { + GatherContext: {gatherMode: 'navigation'}, traces: {defaultPass: trace}, devtoolsLogs: {defaultPass: devtoolsLog}, }; @@ -301,4 +306,27 @@ describe('Byte efficiency base audit', () => { const resultOverride = await MockOverrideAudit.audit(artifacts, {settings, computedCache}); expect(resultOverride.numericValue).toEqual(result.numericValue * 0.5); }); + + it('should compute savings with throughput in timespan mode', async () => { + class MockAudit extends ByteEfficiencyAudit { + static audit_(artifacts, records) { + return { + items: records.map(record => ({url: record.url, wastedBytes: record.transferSize * 0.5})), + headings: [], + }; + } + } + + const artifacts = { + GatherContext: {gatherMode: 'timespan'}, + traces: {defaultPass: trace}, + devtoolsLogs: {defaultPass: devtoolsLog}, + }; + const computedCache = new Map(); + + const modestThrottling = {rttMs: 150, throughputKbps: 1000, cpuSlowdownMultiplier: 2}; + const settings = {throttlingMethod: 'simulate', throttling: modestThrottling}; + const result = await MockAudit.audit(artifacts, {settings, computedCache}); + expect(result.details.overallSavingsMs).toBeCloseTo(914.2695); + }); }); diff --git a/lighthouse-core/test/audits/metrics/total-blocking-time-test.js b/lighthouse-core/test/audits/metrics/total-blocking-time-test.js index f5ff6b35b671..bfc1e06979d9 100644 --- a/lighthouse-core/test/audits/metrics/total-blocking-time-test.js +++ b/lighthouse-core/test/audits/metrics/total-blocking-time-test.js @@ -15,9 +15,9 @@ const devtoolsLog = require('../../fixtures/traces/progressive-app-m60.devtools. const lcpTrace = require('../../fixtures/traces/lcp-m78.json'); const lcpDevtoolsLog = require('../../fixtures/traces/lcp-m78.devtools.log.json'); -function generateArtifacts({trace, devtoolsLog}) { +function generateArtifacts({gatherMode = 'navigation', trace, devtoolsLog}) { return { - GatherContext: {gatherMode: 'navigation'}, + GatherContext: {gatherMode}, traces: {[TBTAudit.DEFAULT_PASS]: trace}, devtoolsLogs: {[TBTAudit.DEFAULT_PASS]: devtoolsLog}, }; @@ -71,4 +71,12 @@ describe('Performance: total-blocking-time audit', () => { expect(outputDesktop.score).toBe(0.53); expect(outputDesktop.displayValue).toBeDisplayString('330\xa0ms'); }); + + it('marks metric not applicable (throttlingMethod=simulate, gatherMode=timespan)', async () => { + const artifacts = generateArtifacts({gatherMode: 'timespan', trace, devtoolsLog}); + const context = getFakeContext({formFactor: 'mobile', throttlingMethod: 'simulate'}); + + const output = await TBTAudit.audit(artifacts, context); + expect(output.notApplicable).toBe(true); + }); }); diff --git a/lighthouse-core/test/fraggle-rock/api-test-pptr.js b/lighthouse-core/test/fraggle-rock/api-test-pptr.js index 21a1ec3f6d47..16635b0db4fb 100644 --- a/lighthouse-core/test/fraggle-rock/api-test-pptr.js +++ b/lighthouse-core/test/fraggle-rock/api-test-pptr.js @@ -121,7 +121,7 @@ describe('Fraggle Rock API', () => { const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr); // TODO(FR-COMPAT): This assertion can be removed when full compatibility is reached. - expect(auditResults.length).toMatchInlineSnapshot(`40`); + expect(auditResults.length).toMatchInlineSnapshot(`63`); expect(erroredAudits).toHaveLength(0); expect(failedAudits.map(audit => audit.id)).toContain('errors-in-console'); diff --git a/lighthouse-core/test/lib/dependency-graph/simulator/network-analyzer-test.js b/lighthouse-core/test/lib/dependency-graph/simulator/network-analyzer-test.js index cce5b8b8d486..8adcfb6ec62c 100644 --- a/lighthouse-core/test/lib/dependency-graph/simulator/network-analyzer-test.js +++ b/lighthouse-core/test/lib/dependency-graph/simulator/network-analyzer-test.js @@ -377,6 +377,10 @@ describe('DependencyGraph/Simulator/NetworkAnalyzer', () => { assert.equal(mainDocument.url, 'https://pwa.rocks/'); }); + it('should throw when it cannot be found', async () => { + expect(() => NetworkAnalyzer.findMainDocument([])).toThrow(/main resource/); + }); + it('should break ties using position in array', async () => { const records = [ {url: 'http://example.com', resourceType: 'Other'}, @@ -389,6 +393,18 @@ describe('DependencyGraph/Simulator/NetworkAnalyzer', () => { }); }); + describe('#findOptionalMainDocument', () => { + it('should find the main document', async () => { + const records = await NetworkRecords.request(devtoolsLog, {computedCache: new Map()}); + const mainDocument = NetworkAnalyzer.findOptionalMainDocument(records); + assert.equal(mainDocument.url, 'https://pwa.rocks/'); + }); + + it('should return undefined when it cannot be found', async () => { + expect(() => NetworkAnalyzer.findOptionalMainDocument([])).toBe(undefined); + }); + }); + describe('#resolveRedirects', () => { it('should resolve to the same document when no redirect', async () => { const records = await NetworkRecords.request(devtoolsLog, {computedCache: new Map()}); diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 023e90c7b671..5324882e5aee 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -743,11 +743,11 @@ declare global { } export interface TimingSummary { - firstContentfulPaint: number; + firstContentfulPaint: number | undefined; firstContentfulPaintTs: number | undefined; firstContentfulPaintAllFrames: number | undefined; firstContentfulPaintAllFramesTs: number | undefined; - firstMeaningfulPaint: number; + firstMeaningfulPaint: number | undefined; firstMeaningfulPaintTs: number | undefined; largestContentfulPaint: number | undefined; largestContentfulPaintTs: number | undefined; @@ -761,20 +761,20 @@ declare global { cumulativeLayoutShift: number | undefined; cumulativeLayoutShiftMainFrame: number | undefined; totalCumulativeLayoutShift: number | undefined; - totalBlockingTime: number; + totalBlockingTime: number | undefined; observedTimeOrigin: number; observedTimeOriginTs: number; - observedNavigationStart: number; - observedNavigationStartTs: number; + observedNavigationStart: number | undefined; + observedNavigationStartTs: number | undefined; observedCumulativeLayoutShift: number | undefined; observedCumulativeLayoutShiftMainFrame: number | undefined; observedTotalCumulativeLayoutShift: number | undefined; observedFirstPaint: number | undefined; observedFirstPaintTs: number | undefined; - observedFirstContentfulPaint: number; - observedFirstContentfulPaintTs: number; - observedFirstContentfulPaintAllFrames: number; - observedFirstContentfulPaintAllFramesTs: number; + observedFirstContentfulPaint: number | undefined; + observedFirstContentfulPaintTs: number | undefined; + observedFirstContentfulPaintAllFrames: number | undefined; + observedFirstContentfulPaintAllFramesTs: number | undefined; observedFirstMeaningfulPaint: number | undefined; observedFirstMeaningfulPaintTs: number | undefined; observedLargestContentfulPaint: number | undefined;