diff --git a/lighthouse-core/audits/load-fast-enough-for-pwa.js b/lighthouse-core/audits/load-fast-enough-for-pwa.js index 19446c184240..41ad3100e999 100644 --- a/lighthouse-core/audits/load-fast-enough-for-pwa.js +++ b/lighthouse-core/audits/load-fast-enough-for-pwa.js @@ -6,8 +6,16 @@ 'use strict'; +/** @fileoverview + * This audit evaluates if a page's load performance is fast enough for it to be considered a PWA. + * We are doublechecking that the network requests were throttled (or slow on their own) + * Afterwards, we report if the TTI is less than 10 seconds. + */ + const Audit = require('./audit'); const TTIMetric = require('./time-to-interactive'); +const Emulation = require('../lib/emulation'); + const Formatter = require('../formatters/formatter'); // Maximum TTI to be considered "fast" for PWA baseline checklist @@ -24,7 +32,7 @@ class LoadFastEnough4Pwa extends Audit { name: 'load-fast-enough-for-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).', - requiredArtifacts: ['traces'] + requiredArtifacts: ['traces', 'networkRecords'] }; } @@ -33,20 +41,39 @@ class LoadFastEnough4Pwa extends Audit { * @return {!AuditResult} */ static audit(artifacts) { + const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS]; + const allRequestLatencies = networkRecords.map(record => { + if (!record._timing) return Infinity; + // Use DevTools' definition of Waiting latency: https://github.com/ChromeDevTools/devtools-frontend/blob/66595b8a73a9c873ea7714205b828866630e9e82/front_end/network/RequestTimingView.js#L164 + return record._timing.receiveHeadersEnd - record._timing.sendEnd; + }); + + const latency3gMin = Emulation.settings.TYPICAL_MOBILE_THROTTLING_METRICS.latency - 10; + const areLatenciesAll3G = allRequestLatencies.every(val => val > latency3gMin); + return TTIMetric.audit(artifacts).then(ttiResult => { const timeToInteractive = ttiResult.extendedInfo.value.timings.timeToInteractive; const isFast = timeToInteractive < MAXIMUM_TTI; const extendedInfo = { formatter: Formatter.SUPPORTED_FORMATS.NULL, - value: {timeToInteractive} + value: {allRequestLatencies, timeToInteractive} }; + if (!areLatenciesAll3G) { + return LoadFastEnough4Pwa.generateAuditResult({ + rawValue: false, + // eslint-disable-next-line max-len + debugString: `The Time To Interactive was found at ${ttiResult.displayValue}, however, the network request latencies were not sufficiently realistic, so the performance measurements cannot be trusted.`, + extendedInfo + }); + } + if (!isFast) { return LoadFastEnough4Pwa.generateAuditResult({ rawValue: false, - debugString: `Under mobile conditions, the TTI was at ${ttiResult.displayValue}. ` + - 'More details in "Performance" section.', + // eslint-disable-next-line max-len + debugString: `Under 3G conditions, the Time To Interactive was at ${ttiResult.displayValue}. More details in the "Performance" section.`, extendedInfo }); } diff --git a/lighthouse-core/lib/emulation.js b/lighthouse-core/lib/emulation.js index 66e4cab8ebbb..39008acf0fbd 100644 --- a/lighthouse-core/lib/emulation.js +++ b/lighthouse-core/lib/emulation.js @@ -151,5 +151,14 @@ module.exports = { enableCPUThrottling, disableCPUThrottling, goOffline, - getEmulationDesc + getEmulationDesc, + settings: { + NEXUS5X_EMULATION_METRICS, + NEXUS5X_USERAGENT, + TYPICAL_MOBILE_THROTTLING_METRICS, + OFFLINE_METRICS, + NO_THROTTLING_METRICS, + NO_CPU_THROTTLE_METRICS, + CPU_THROTTLE_METRICS + } }; diff --git a/lighthouse-core/test/audits/load-fast-enough-for-pwa-test.js b/lighthouse-core/test/audits/load-fast-enough-for-pwa-test.js index 799312ba042a..d196c9de4fbe 100644 --- a/lighthouse-core/test/audits/load-fast-enough-for-pwa-test.js +++ b/lighthouse-core/test/audits/load-fast-enough-for-pwa-test.js @@ -10,23 +10,43 @@ const FastPWAAudit = require('../../audits/load-fast-enough-for-pwa'); const TTIAudit = require('../../audits/time-to-interactive'); const Audit = require('../../audits/audit.js'); const assert = require('assert'); -const traceEvents = require('../fixtures/traces/progressive-app.json'); -const GatherRunner = require('../../gather/gather-runner.js'); -const computedArtifacts = GatherRunner.instantiateComputedArtifacts(); +function generateTTIResults(ttiValue) { + const ttiResult = { + rawValue: ttiValue, + extendedInfo: { + value: { + timings: { + timeToInteractive: ttiValue + } + } + } + }; + return Promise.resolve.bind(Promise, ttiResult); +} + -function generateArtifactsWithTrace(trace) { - return Object.assign(computedArtifacts, { +function generateArtifacts() { + return { + networkRecords: { + [Audit.DEFAULT_PASS]: [] + }, traces: { - [Audit.DEFAULT_PASS]: {traceEvents: Array.isArray(trace) ? trace : trace.traceEvents} + [Audit.DEFAULT_PASS]: {traceEvents: []} } - }); + }; } /* eslint-env mocha */ describe('PWA: load-fast-enough-for-pwa audit', () => { + // monkeypatch TTI to for a more focused test + let origTTI; + beforeEach(() => origTTI = TTIAudit.audit); + afterEach(() => TTIAudit.audit = origTTI); + it('returns boolean based on TTI value', () => { - return FastPWAAudit.audit(generateArtifactsWithTrace(traceEvents)).then(result => { + TTIAudit.audit = generateTTIResults(5000); + return FastPWAAudit.audit(generateArtifacts()).then(result => { assert.equal(result.rawValue, true, 'fixture trace is not passing audit'); }).catch(err => { assert.ok(false, err); @@ -34,14 +54,10 @@ describe('PWA: load-fast-enough-for-pwa audit', () => { }); it('fails a bad TTI value', () => { - // mock a return for the TTI Audit - const origTTI = TTIAudit.audit; - const mockTTIResult = {extendedInfo: {value: {timings: {timeToInteractive: 15000}}}}; - TTIAudit.audit = _ => Promise.resolve(mockTTIResult); - - return FastPWAAudit.audit(generateArtifactsWithTrace(traceEvents)).then(result => { + TTIAudit.audit = generateTTIResults(15000); + return FastPWAAudit.audit(generateArtifacts()).then(result => { assert.equal(result.rawValue, false, 'not failing a long TTI value'); - TTIAudit.audit = origTTI; + assert.ok(result.debugString); }).catch(err => { assert.ok(false, err); });