diff --git a/lighthouse-core/audits/metrics/first-contentful-paint-3g.js b/lighthouse-core/audits/metrics/first-contentful-paint-3g.js new file mode 100644 index 000000000000..a61adb2a88c0 --- /dev/null +++ b/lighthouse-core/audits/metrics/first-contentful-paint-3g.js @@ -0,0 +1,68 @@ +/** + * @license Copyright 2019 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const Audit = require('../audit.js'); +const regular3G = require('../../config/constants.js').throttling.mobileRegluar3G; +const i18n = require('../../lib/i18n/i18n.js'); +const FCP = require('./first-contentful-paint.js'); +const ComputedFcp = require('../../computed/metrics/first-contentful-paint.js'); + +const i18nFilename = require.resolve('./first-contentful-paint.js'); +const str_ = i18n.createMessageInstanceIdFn(i18nFilename, FCP.UIStrings); + +class FirstContentfulPaint3G extends Audit { + /** + * @return {LH.Audit.Meta} + */ + static get meta() { + return { + id: 'first-contentful-paint-3g', + title: str_(FCP.UIStrings.title), + description: str_(FCP.UIStrings.description), + scoreDisplayMode: Audit.SCORING_MODES.NUMERIC, + requiredArtifacts: ['traces', 'devtoolsLogs'], + }; + } + + /** + * @return {LH.Audit.ScoreOptions} + */ + static get defaultOptions() { + return { + // 75th and 95th percentiles HTTPArchive on Fast 3G -> multiply by 1.5 for RTT differential -> median and PODR + // https://bigquery.cloud.google.com/table/httparchive:lighthouse.2018_04_01_mobile?pli=1 + scorePODR: 3000, + scoreMedian: 6000, + }; + } + + /** + * @param {LH.Artifacts} artifacts + * @param {LH.Audit.Context} context + * @return {Promise} + */ + static async audit(artifacts, context) { + const trace = artifacts.traces[Audit.DEFAULT_PASS]; + const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS]; + /** @type {LH.Config.Settings} */ + const settings = {...context.settings, throttlingMethod: 'simulate', throttling: regular3G}; + const metricComputationData = {trace, devtoolsLog, settings}; + const metricResult = await ComputedFcp.request(metricComputationData, context); + + return { + score: Audit.computeLogNormalScore( + metricResult.timing, + context.options.scorePODR, + context.options.scoreMedian + ), + rawValue: metricResult.timing, + displayValue: str_(i18n.UIStrings.seconds, {timeInMs: metricResult.timing}), + }; + } +} + +module.exports = FirstContentfulPaint3G; diff --git a/lighthouse-core/config/constants.js b/lighthouse-core/config/constants.js index 46d19987f17e..4aea3ef776b2 100644 --- a/lighthouse-core/config/constants.js +++ b/lighthouse-core/config/constants.js @@ -16,6 +16,8 @@ const DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR = 0.9; const throttling = { DEVTOOLS_RTT_ADJUSTMENT_FACTOR, DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR, + // These values align with WebPageTest's definition of "Fast 3G" + // But offer similar charateristics to roughly the 75th percentile of 4G connections. mobileSlow4G: { rttMs: 150, throughputKbps: 1.6 * 1024, @@ -24,6 +26,17 @@ const throttling = { uploadThroughputKbps: 750 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR, cpuSlowdownMultiplier: 4, }, + // These values partially align with WebPageTest's definition of "Regular 3G". + // These values are meant to roughly align with Chrome UX report's 3G definition which are based + // on HTTP RTT of 300-1400ms and downlink throughput of <700kbps. + mobileRegluar3G: { + rttMs: 300, + throughputKbps: 700, + requestLatencyMs: 300 * DEVTOOLS_RTT_ADJUSTMENT_FACTOR, + downloadThroughputKbps: 700 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR, + uploadThroughputKbps: 700 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR, + cpuSlowdownMultiplier: 4, + }, }; /** @type {LH.Config.Settings} */ diff --git a/lighthouse-core/config/lr-desktop-config.js b/lighthouse-core/config/lr-desktop-config.js index 11fc64485e0e..8e0aa8f28a3e 100644 --- a/lighthouse-core/config/lr-desktop-config.js +++ b/lighthouse-core/config/lr-desktop-config.js @@ -18,7 +18,7 @@ const config = { throughputKbps: 10 * 1024, cpuSlowdownMultiplier: 1, }, - // Skip the h2 audit so it doesn't lie to us. See #6539 + // Skip the h2 audit so it doesn't lie to us. See https://github.com/GoogleChrome/lighthouse/issues/6539 skipAudits: ['uses-http2'], }, audits: [ diff --git a/lighthouse-core/config/lr-mobile-config.js b/lighthouse-core/config/lr-mobile-config.js index 264663542213..2c689ad1bdae 100644 --- a/lighthouse-core/config/lr-mobile-config.js +++ b/lighthouse-core/config/lr-mobile-config.js @@ -10,9 +10,20 @@ const config = { extends: 'lighthouse:default', settings: { maxWaitForLoad: 35 * 1000, - // Skip the h2 audit so it doesn't lie to us. See #6539 + // Skip the h2 audit so it doesn't lie to us. See https://github.com/GoogleChrome/lighthouse/issues/6539 skipAudits: ['uses-http2'], }, + audits: [ + 'metrics/first-contentful-paint-3g', + ], + // @ts-ignore TODO(bckenny): type extended Config where e.g. category.title isn't required + categories: { + performance: { + auditRefs: [ + {id: 'first-contentful-paint-3g', weight: 0}, + ], + }, + }, }; module.exports = config; diff --git a/lighthouse-core/test/audits/metrics/first-contentful-paint-3g-test.js b/lighthouse-core/test/audits/metrics/first-contentful-paint-3g-test.js new file mode 100644 index 000000000000..b569abe2fc5d --- /dev/null +++ b/lighthouse-core/test/audits/metrics/first-contentful-paint-3g-test.js @@ -0,0 +1,36 @@ +/** + * @license Copyright 2019 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const FCP3G = require('../../../audits/metrics/first-contentful-paint-3g.js'); +const options = FCP3G.defaultOptions; + +const pwaTrace = require('../../fixtures/traces/progressive-app-m60.json'); +const pwaDevtoolsLog = require('../../fixtures/traces/progressive-app-m60.devtools.log.json'); + +/* eslint-env jest */ + +describe('Performance: first-contentful-paint-3g audit', () => { + it('evaluates valid input correctly', async () => { + const artifacts = { + traces: { + [FCP3G.DEFAULT_PASS]: pwaTrace, + }, + devtoolsLogs: { + [FCP3G.DEFAULT_PASS]: pwaDevtoolsLog, + }, + }; + + const result = await FCP3G.audit(artifacts, {settings: {}, options, computedCache: new Map()}); + // Use InlineSnapshot here so changes to Lantern coefficients can be easily updated en masse + expect({score: result.score, value: Math.round(result.rawValue)}).toMatchInlineSnapshot(` +Object { + "score": 1, + "value": 1661, +} +`); + }); +});