Repository: GoogleChrome/lighthouse
Pull request #7132 (2019-02-01 21:00:48)

core(metrics): do not fail if TTI fails
This one's for you @benschwarz 


Patch:
diff --git a/lighthouse-core/audits/metrics.js b/lighthouse-core/audits/metrics.js
index 35ab49f60..06f22c2e1 100644
--- a/lighthouse-core/audits/metrics.js
+++ b/lighthouse-core/audits/metrics.js
@@ -39,13 +39,25 @@ class Metrics extends Audit {
     const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
     const metricComputationData = {trace, devtoolsLog, settings: context.settings};
 
+
+    /**
+     * @template TArtifacts
+     * @template TReturn
+     * @param {{request: (artifact: TArtifacts, context: LH.Audit.Context) => Promise<TReturn>}} Artifact
+     * @param {TArtifacts} artifact
+     * @return {Promise<TReturn|undefined>}
+     */
+    const requestOrUndefined = (Artifact, artifact) => {
+      return Artifact.request(artifact, context).catch(_ => undefined);
+    };
+
     const traceOfTab = await TraceOfTab.request(trace, context);
     const speedline = await Speedline.request(trace, context);
     const firstContentfulPaint = await FirstContentfulPaint.request(metricComputationData, context);
     const firstMeaningfulPaint = await FirstMeaningfulPaint.request(metricComputationData, context);
-    const firstCPUIdle = await FirstCPUIdle.request(metricComputationData, context);
-    const interactive = await Interactive.request(metricComputationData, context);
-    const speedIndex = await SpeedIndex.request(metricComputationData, context);
+    const firstCPUIdle = await requestOrUndefined(FirstCPUIdle, metricComputationData);
+    const interactive = await requestOrUndefined(Interactive, metricComputationData);
+    const speedIndex = await requestOrUndefined(SpeedIndex, metricComputationData);
     const estimatedInputLatency = await EstimatedInputLatency.request(metricComputationData, context); // eslint-disable-line max-len
 
     /** @type {UberMetricsItem} */
@@ -55,12 +67,12 @@ class Metrics extends Audit {
       firstContentfulPaintTs: firstContentfulPaint.timestamp,
       firstMeaningfulPaint: firstMeaningfulPaint.timing,
       firstMeaningfulPaintTs: firstMeaningfulPaint.timestamp,
-      firstCPUIdle: firstCPUIdle.timing,
-      firstCPUIdleTs: firstCPUIdle.timestamp,
-      interactive: interactive.timing,
-      interactiveTs: interactive.timestamp,
-      speedIndex: speedIndex.timing,
-      speedIndexTs: speedIndex.timestamp,
+      firstCPUIdle: firstCPUIdle && firstCPUIdle.timing,
+      firstCPUIdleTs: firstCPUIdle && firstCPUIdle.timestamp,
+      interactive: interactive && interactive.timing,
+      interactiveTs: interactive && interactive.timestamp,
+      speedIndex: speedIndex && speedIndex.timing,
+      speedIndexTs: speedIndex && speedIndex.timestamp,
       estimatedInputLatency: estimatedInputLatency.timing,
       estimatedInputLatencyTs: estimatedInputLatency.timestamp,
 
@@ -101,7 +113,7 @@ class Metrics extends Audit {
 
     return {
       score: 1,
-      rawValue: interactive.timing,
+      rawValue: (interactive && interactive.timing) || 0,
       details,
     };
   }
@@ -113,11 +125,11 @@ class Metrics extends Audit {
  * @property {number=} firstContentfulPaintTs
  * @property {number} firstMeaningfulPaint
  * @property {number=} firstMeaningfulPaintTs
- * @property {number} firstCPUIdle
+ * @property {number=} firstCPUIdle
  * @property {number=} firstCPUIdleTs
- * @property {number} interactive
+ * @property {number=} interactive
  * @property {number=} interactiveTs
- * @property {number} speedIndex
+ * @property {number=} speedIndex
  * @property {number=} speedIndexTs
  * @property {number} estimatedInputLatency
  * @property {number=} estimatedInputLatencyTs


Test patch:
diff --git a/lighthouse-core/test/audits/metrics-test.js b/lighthouse-core/test/audits/metrics-test.js
index a433b141d..09c364214 100644
--- a/lighthouse-core/test/audits/metrics-test.js
+++ b/lighthouse-core/test/audits/metrics-test.js
@@ -6,6 +6,7 @@
 'use strict';
 
 const MetricsAudit = require('../../audits/metrics.js');
+const TTIComputed = require('../../computed/metrics/interactive.js');
 
 const pwaTrace = require('../fixtures/traces/progressive-app-m60.json');
 const pwaDevtoolsLog = require('../fixtures/traces/progressive-app-m60.devtools.log.json');
@@ -27,4 +28,21 @@ describe('Performance: metrics', () => {
     const result = await MetricsAudit.audit(artifacts, context);
     expect(result.details.items[0]).toMatchSnapshot();
   });
+
+  it('does to fail the entire audit when TTI errors', async () => {
+    const artifacts = {
+      traces: {
+        [MetricsAudit.DEFAULT_PASS]: pwaTrace,
+      },
+      devtoolsLogs: {
+        [MetricsAudit.DEFAULT_PASS]: pwaDevtoolsLog,
+      },
+    };
+
+    const mockTTIFn = jest.spyOn(TTIComputed, 'request');
+    mockTTIFn.mockRejectedValueOnce(new Error('TTI failed'));
+    const context = {settings: {throttlingMethod: 'simulate'}, computedCache: new Map()};
+    const result = await MetricsAudit.audit(artifacts, context);
+    expect(result.details.items[0].interactive).toEqual(undefined);
+  });
 });