Skip to content

Commit 9771117

Browse files
authored
core(load-fast-4-pwa): use computed artifacts (#4981)
1 parent fff50f2 commit 9771117

File tree

4 files changed

+77
-184
lines changed

4 files changed

+77
-184
lines changed

lighthouse-core/audits/load-fast-enough-for-pwa.js

Lines changed: 38 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@
1111
* Afterwards, we report if the TTFI is less than 10 seconds.
1212
*/
1313

14+
const isDeepEqual = require('lodash.isequal');
1415
const Audit = require('./audit');
15-
const URL = require('../lib/url-shim');
16-
const targetLatencyMs = require('../config/constants').throttling.mobile3G.rttMs;
17-
const Sentry = require('../lib/sentry');
16+
const mobile3GThrottling = require('../config/constants').throttling.mobile3G;
1817
const Util = require('../report/v2/renderer/util.js');
1918

20-
// Maximum TTFI to be considered "fast" for PWA baseline checklist
19+
// Maximum TTI to be considered "fast" for PWA baseline checklist
2120
// https://developers.google.com/web/progressive-web-apps/checklist
22-
const MAXIMUM_TTFI = 10 * 1000;
23-
24-
const WHITELISTED_STATUS_CODES = [307];
21+
const MAXIMUM_TTI = 10 * 1000;
2522

2623
class LoadFastEnough4Pwa extends Audit {
2724
/**
@@ -32,116 +29,47 @@ class LoadFastEnough4Pwa extends Audit {
3229
name: 'load-fast-enough-for-pwa',
3330
description: 'Page load is fast enough on 3G',
3431
failureDescription: 'Page load is not fast enough on 3G',
35-
helpText: 'A fast page load over a 3G network ensures a good mobile user experience. ' +
36-
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g).',
32+
helpText:
33+
'A fast page load over a 3G network ensures a good mobile user experience. ' +
34+
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g).',
3735
requiredArtifacts: ['traces', 'devtoolsLogs'],
3836
};
3937
}
4038

4139
/**
42-
* @param {!Artifacts} artifacts
43-
* @return {!AuditResult}
40+
* @param {LH.Artifacts} artifacts
41+
* @param {LH.Audit.Context} context
42+
* @return {LH.AuditResult}
4443
*/
45-
static audit(artifacts) {
46-
const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
47-
return artifacts.requestNetworkRecords(devtoolsLogs).then(networkRecords => {
48-
const firstRequestLatenciesByOrigin = new Map();
49-
networkRecords.forEach(record => {
50-
// Ignore requests that don't have valid origin, timing data, came from the cache, were
51-
// redirected by Chrome without going to the network, or are not finished.
52-
const fromCache = record._fromDiskCache || record._fromMemoryCache;
53-
const origin = URL.getOrigin(record._url);
54-
if (!origin || !record._timing || fromCache ||
55-
WHITELISTED_STATUS_CODES.includes(record.statusCode) || !record.finished) {
56-
return;
57-
}
58-
59-
// Disregard requests with an invalid start time, (H2 request start times are sometimes less
60-
// than issue time and even negative which throws off timing)
61-
if (record._startTime < record._issueTime) {
62-
return;
63-
}
64-
65-
// Use DevTools' definition of Waiting latency: https://github.com/ChromeDevTools/devtools-frontend/blob/66595b8a73a9c873ea7714205b828866630e9e82/front_end/network/RequestTimingView.js#L164
66-
const latency = record._timing.receiveHeadersEnd - record._timing.sendEnd;
67-
const latencyInfo = {
68-
url: record._url,
69-
startTime: record._startTime,
70-
origin,
71-
latency,
72-
};
73-
74-
// Only examine the first request per origin to reduce noisiness from cases like H2 push
75-
// where individual request latency may not apply.
76-
const existing = firstRequestLatenciesByOrigin.get(origin);
77-
if (!existing || latencyInfo.startTime < existing.startTime) {
78-
firstRequestLatenciesByOrigin.set(origin, latencyInfo);
79-
}
80-
});
81-
82-
let firstRequestLatencies = Array.from(firstRequestLatenciesByOrigin.values());
83-
const latency3gMin = targetLatencyMs - 10;
84-
const areLatenciesAll3G = firstRequestLatencies.every(val => val.latency > latency3gMin);
85-
firstRequestLatencies = firstRequestLatencies.map(item => ({
86-
url: item.url,
87-
latency: Util.formatNumber(item.latency, 0.01),
88-
}));
89-
90-
const trace = artifacts.traces[Audit.DEFAULT_PASS];
91-
return artifacts.requestFirstInteractive(trace).then(firstInteractive => {
92-
const timeToFirstInteractive = firstInteractive.timeInMs;
93-
const isFast = timeToFirstInteractive < MAXIMUM_TTFI;
94-
95-
const extendedInfo = {
96-
value: {areLatenciesAll3G, firstRequestLatencies, isFast, timeToFirstInteractive},
97-
};
44+
static async audit(artifacts, context) {
45+
const trace = artifacts.traces[Audit.DEFAULT_PASS];
46+
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
47+
48+
// If throttling was default devtools or lantern 3G throttling, then reuse the given settings
49+
// Otherwise, we'll force the usage of lantern 3G.
50+
const settingOverrides = {throttlingMethod: 'simulate', throttling: mobile3GThrottling};
51+
const settings =
52+
context.settings.throttlingMethod !== 'provided' &&
53+
isDeepEqual(context.settings.throttling, mobile3GThrottling)
54+
? context.settings
55+
: Object.assign({}, context.settings, settingOverrides);
56+
57+
const metricComputationData = {trace, devtoolsLog, settings};
58+
const tti = await artifacts.requestConsistentlyInteractive(metricComputationData);
59+
60+
const score = Number(tti.timing < MAXIMUM_TTI);
61+
62+
let debugString;
63+
if (!score) {
64+
// eslint-disable-next-line max-len
65+
debugString = `First Interactive was ${Util.formatMilliseconds(tti.timing)}. More details in the "Performance" section.`;
66+
}
9867

99-
const details = Audit.makeTableDetails([
100-
{key: 'url', itemType: 'url', text: 'URL'},
101-
{key: 'latency', itemType: 'text', text: 'Latency (ms)'},
102-
], firstRequestLatencies);
103-
104-
if (!isFast) {
105-
return {
106-
rawValue: false,
107-
// eslint-disable-next-line max-len
108-
debugString: `First Interactive was at ${Util.formatMilliseconds(timeToFirstInteractive)}. More details in the "Performance" section.`,
109-
extendedInfo,
110-
};
111-
}
112-
113-
if (!areLatenciesAll3G) {
114-
const sentryContext = Sentry.getContext();
115-
const hadThrottlingEnabled = sentryContext && sentryContext.extra &&
116-
sentryContext.extra.networkThrottling;
117-
118-
if (hadThrottlingEnabled) {
119-
// Track these instances in Sentry since there should be no requests that are fast when
120-
// throttling is enabled, and it's likely a throttling bug we should look into.
121-
const violatingLatency = firstRequestLatencies
122-
.find(item => Number(item.latency) < latency3gMin);
123-
Sentry.captureMessage('Network request latencies were not realistic', {
124-
tags: {audit: this.meta.name},
125-
extra: {violatingLatency},
126-
level: 'warning',
127-
});
128-
}
129-
130-
return {
131-
rawValue: true,
132-
// eslint-disable-next-line max-len
133-
debugString: `First Interactive was found at ${Util.formatMilliseconds(timeToFirstInteractive)}; however, the network request latencies were not sufficiently realistic, so the performance measurements cannot be trusted.`,
134-
extendedInfo,
135-
details,
136-
};
137-
}
138-
139-
return {
140-
rawValue: true,
141-
extendedInfo,
142-
};
143-
});
144-
});
68+
return {
69+
score,
70+
debugString,
71+
rawValue: tti.timing,
72+
};
14573
}
14674
}
14775

lighthouse-core/gather/computed/metrics/metric.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class ComputedMetric extends ComputedArtifact {
4848
*/
4949
async compute_(data, artifacts) {
5050
const {trace, devtoolsLog, settings} = data;
51+
if (!trace || !devtoolsLog || !settings) {
52+
throw new Error('Did not provide necessary metric computation data');
53+
}
54+
5155
const augmentedData = Object.assign({
5256
networkRecords: await artifacts.requestNetworkRecords(devtoolsLog),
5357
traceOfTab: await artifacts.requestTraceOfTab(trace),

lighthouse-core/test/audits/load-fast-enough-for-pwa-test.js

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,88 +6,66 @@
66
'use strict';
77

88
const FastPWAAudit = require('../../audits/load-fast-enough-for-pwa');
9+
const Runner = require('../../runner.js');
910
const Audit = require('../../audits/audit.js');
11+
const mobile3GThrottling = require('../../config/constants').throttling.mobile3G;
1012
const assert = require('assert');
1113

12-
function generateArtifacts(firstInteractiveValue, networkRecords = []) {
14+
const trace = require('../fixtures/traces/progressive-app-m60.json');
15+
const devtoolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
16+
17+
function generateArtifacts(ttiValue) {
1318
return {
1419
devtoolsLogs: {
1520
[Audit.DEFAULT_PASS]: [],
1621
},
17-
requestNetworkRecords: () => {
18-
return Promise.resolve(networkRecords);
19-
},
2022
traces: {
2123
[Audit.DEFAULT_PASS]: {traceEvents: []},
2224
},
23-
requestFirstInteractive: () => Promise.resolve({
24-
timeInMs: firstInteractiveValue,
25+
requestConsistentlyInteractive: () => Promise.resolve({
26+
timing: ttiValue,
2527
}),
2628
};
2729
}
2830

2931
/* eslint-env mocha */
3032
describe('PWA: load-fast-enough-for-pwa audit', () => {
3133
it('returns boolean based on TTI value', () => {
32-
return FastPWAAudit.audit(generateArtifacts(5000)).then(result => {
33-
assert.equal(result.rawValue, true, 'fixture trace is not passing audit');
34+
const settings = {throttlingMethod: 'devtools', throttling: mobile3GThrottling};
35+
return FastPWAAudit.audit(generateArtifacts(5000), {settings}).then(result => {
36+
assert.equal(result.score, true, 'fixture trace is not passing audit');
37+
assert.equal(result.rawValue, 5000);
3438
});
3539
});
3640

3741
it('fails a bad TTI value', () => {
38-
return FastPWAAudit.audit(generateArtifacts(15000)).then(result => {
39-
assert.equal(result.rawValue, false, 'not failing a long TTI value');
42+
const settings = {throttlingMethod: 'devtools', throttling: mobile3GThrottling};
43+
return FastPWAAudit.audit(generateArtifacts(15000), {settings}).then(result => {
44+
assert.equal(result.score, false, 'not failing a long TTI value');
45+
assert.equal(result.rawValue, 15000);
4046
assert.ok(result.debugString);
4147
});
4248
});
4349

44-
it('warns on a good TTI value with no throttling', () => {
45-
// latencies are very short
46-
const mockNetworkRecords = [
47-
{_timing: {sendEnd: 0, receiveHeadersEnd: 50}, finished: true, _url: 'https://google.com/'},
48-
{_timing: {sendEnd: 0, receiveHeadersEnd: 75}, finished: true, _url: 'https://google.com/a'},
49-
{ },
50-
{_timing: {sendEnd: 0, receiveHeadersEnd: 50}, finished: true, _url: 'https://google.com/b'},
51-
];
52-
return FastPWAAudit.audit(generateArtifacts(5000, mockNetworkRecords)).then(result => {
53-
assert.equal(result.rawValue, true);
54-
assert.ok(result.debugString.includes('network request latencies'));
55-
assert.ok(result.details, 'contains details when latencies were not realistic');
56-
});
57-
});
50+
it('respects the observed result when throttling is preset', async () => {
51+
const artifacts = Object.assign({
52+
traces: {defaultPass: trace},
53+
devtoolsLogs: {defaultPass: devtoolsLog},
54+
}, Runner.instantiateComputedArtifacts());
5855

59-
it('ignores resources coming from cache', () => {
60-
const mockNetworkRecords = [
61-
{_timing: {sendEnd: 0, receiveHeadersEnd: 50}, _fromDiskCache: true},
62-
];
63-
return FastPWAAudit.audit(generateArtifacts(5000, mockNetworkRecords)).then(result => {
64-
assert.equal(result.rawValue, true);
65-
assert.strictEqual(result.debugString, undefined);
66-
});
56+
const settings = {throttlingMethod: 'devtools', throttling: mobile3GThrottling};
57+
const result = await FastPWAAudit.audit(artifacts, {settings});
58+
assert.equal(Math.round(result.rawValue), 1582);
6759
});
6860

69-
it('passes a good TTI value and WITH throttling', () => {
70-
// latencies are very long
71-
const urlA = 'https://google.com';
72-
const urlB = 'https://example.com';
73-
const urlC = 'https://example-c.com';
74-
const mockNetworkRecords = [
75-
{_timing: {sendEnd: 0, receiveHeadersEnd: 250}, finished: true, _url: urlA, _startTime: 0},
76-
{_timing: {sendEnd: 0, receiveHeadersEnd: 250}, finished: true, _url: urlB},
77-
// ignored for not having timing
78-
{ },
79-
// ignored for not being the first of the origin
80-
{_timing: {sendEnd: 0, receiveHeadersEnd: 100}, finished: true, _url: urlA, _startTime: 100},
81-
// ignored for being redirected internally
82-
{_timing: {sendEnd: 0, receiveHeadersEnd: 100}, finished: true, _url: urlC, _startTime: 0,
83-
statusCode: 307},
84-
// ignored for not finishing
85-
{_timing: {sendEnd: 0, receiveHeadersEnd: -1}, finished: false},
86-
];
87-
return FastPWAAudit.audit(generateArtifacts(5000, mockNetworkRecords)).then(result => {
88-
assert.equal(result.rawValue, true);
89-
assert.strictEqual(result.debugString, undefined);
90-
assert.ok(!result.details, 'does not contain details when latencies are realistic');
91-
});
61+
it('overrides with simulated result when throttling is modified', async () => {
62+
const artifacts = Object.assign({
63+
traces: {defaultPass: trace},
64+
devtoolsLogs: {defaultPass: devtoolsLog},
65+
}, Runner.instantiateComputedArtifacts());
66+
67+
const settings = {throttlingMethod: 'provided', throttling: {rttMs: 40, throughput: 100000}};
68+
const result = await FastPWAAudit.audit(artifacts, {settings});
69+
assert.equal(Math.round(result.rawValue), 5308);
9270
});
9371
});

lighthouse-core/test/results/sample_v2.json

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,7 @@
105105
"load-fast-enough-for-pwa": {
106106
"score": 1,
107107
"displayValue": "",
108-
"rawValue": true,
109-
"extendedInfo": {
110-
"value": {
111-
"areLatenciesAll3G": true,
112-
"firstRequestLatencies": [
113-
{
114-
"url": "http://localhost:10200/dobetterweb/dbw_tester.html",
115-
"latency": "570.56"
116-
},
117-
{
118-
"url": "http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js",
119-
"latency": "564.12"
120-
}
121-
],
122-
"isFast": true,
123-
"timeToFirstInteractive": 4927.278
124-
}
125-
},
108+
"rawValue": 4927.278,
126109
"scoreDisplayMode": "binary",
127110
"name": "load-fast-enough-for-pwa",
128111
"description": "Page load is fast enough on 3G",
@@ -5403,6 +5386,6 @@
54035386
}
54045387
},
54055388
"timing": {
5406-
"total": 877
5389+
"total": 788
54075390
}
54085391
}

0 commit comments

Comments
 (0)