Repository: GoogleChrome/lighthouse
Pull request #5657 (2018-07-11 23:48:38)

core(ttfb): reuse requestMainResource
One of our top sentry errors is from TTFB not finding the right URL. It somehow hadn't moved to `requestMainResource` yet.

It's unclear why this started failing in 3.0 though...it should've been failing much earlier than that.

googlebot: So there's good news and bad news.

:thumbsup: The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so.  Everything is all good there.

:confused: The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter.  We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that here in the pull request.

*Note to project maintainer: This is a terminal state, meaning the `cla/google` commit status will not change from this state. It's up to you to confirm consent of the commit author(s) and merge this pull request when appropriate.*

<!-- need_author_consent -->

Patch:
diff --git a/lighthouse-core/audits/time-to-first-byte.js b/lighthouse-core/audits/time-to-first-byte.js
index 244ac31af..3bbebbc0c 100644
--- a/lighthouse-core/audits/time-to-first-byte.js
+++ b/lighthouse-core/audits/time-to-first-byte.js
@@ -35,46 +35,32 @@ class TTFBMetric extends Audit {
    * @param {LH.Artifacts} artifacts
    * @return {Promise<LH.Audit.Product>}
    */
-  static audit(artifacts) {
-    const devtoolsLogs = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+  static async audit(artifacts) {
+    const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+    const mainResource = await artifacts.requestMainResource({devtoolsLog, URL: artifacts.URL});
 
-    return artifacts.requestNetworkRecords(devtoolsLogs)
-      .then((networkRecords) => {
-        /** @type {LH.Audit.DisplayValue} */
-        let displayValue = '';
+    const ttfb = TTFBMetric.caclulateTTFB(mainResource);
+    const passed = ttfb < TTFB_THRESHOLD;
 
-        const finalUrl = artifacts.URL.finalUrl;
-        const finalUrlRequest = networkRecords.find(record => record.url === finalUrl);
-        if (!finalUrlRequest) {
-          throw new Error(`finalUrl '${finalUrl} not found in network records.`);
-        }
-        const ttfb = TTFBMetric.caclulateTTFB(finalUrlRequest);
-        const passed = ttfb < TTFB_THRESHOLD;
-
-        if (!passed) {
-          displayValue = ['Root document took %10d', ttfb];
-        }
-
-        /** @type {LH.Result.Audit.OpportunityDetails} */
-        const details = {
-          type: 'opportunity',
-          overallSavingsMs: ttfb - TTFB_THRESHOLD,
-          headings: [],
-          items: [],
-        };
+    /** @type {LH.Result.Audit.OpportunityDetails} */
+    const details = {
+      type: 'opportunity',
+      overallSavingsMs: ttfb - TTFB_THRESHOLD,
+      headings: [],
+      items: [],
+    };
 
-        return {
-          rawValue: ttfb,
-          score: Number(passed),
-          displayValue,
-          details,
-          extendedInfo: {
-            value: {
-              wastedMs: ttfb - TTFB_THRESHOLD,
-            },
-          },
-        };
-      });
+    return {
+      rawValue: ttfb,
+      score: Number(passed),
+      displayValue: passed ? '' : ['Root document took %10d', ttfb],
+      details,
+      extendedInfo: {
+        value: {
+          wastedMs: ttfb - TTFB_THRESHOLD,
+        },
+      },
+    };
   }
 }
 


Test patch:
diff --git a/lighthouse-core/test/audits/time-to-first-byte-test.js b/lighthouse-core/test/audits/time-to-first-byte-test.js
index 5a668ae7d..d1f4258c0 100644
--- a/lighthouse-core/test/audits/time-to-first-byte-test.js
+++ b/lighthouse-core/test/audits/time-to-first-byte-test.js
@@ -11,14 +11,15 @@ const assert = require('assert');
 /* eslint-env jest */
 describe('Performance: time-to-first-byte audit', () => {
   it('fails when ttfb of root document is higher than 600ms', () => {
-    const networkRecords = [
-      {url: 'https://example.com/', requestId: '0', timing: {receiveHeadersEnd: 830, sendEnd: 200}},
-      {url: 'https://google.com/styles.css', requestId: '1', timing: {receiveHeadersEnd: 450, sendEnd: 200}},
-      {url: 'https://google.com/image.jpg', requestId: '2', timing: {receiveHeadersEnd: 600, sendEnd: 400}},
-    ];
+    const mainResource = {
+      url: 'https://example.com/',
+      requestId: '0',
+      timing: {receiveHeadersEnd: 830, sendEnd: 200},
+    };
+
     const artifacts = {
       devtoolsLogs: {[TimeToFirstByte.DEFAULT_PASS]: []},
-      requestNetworkRecords: () => Promise.resolve(networkRecords),
+      requestMainResource: () => Promise.resolve(mainResource),
       URL: {finalUrl: 'https://example.com/'},
     };
 
@@ -29,14 +30,15 @@ describe('Performance: time-to-first-byte audit', () => {
   });
 
   it('succeeds when ttfb of root document is lower than 600ms', () => {
-    const networkRecords = [
-      {url: 'https://example.com/', requestId: '0', timing: {receiveHeadersEnd: 400, sendEnd: 200}},
-      {url: 'https://google.com/styles.css', requestId: '1', timing: {receiveHeadersEnd: 850, sendEnd: 200}},
-      {url: 'https://google.com/image.jpg', requestId: '2', timing: {receiveHeadersEnd: 1000, sendEnd: 400}},
-    ];
+    const mainResource = {
+      url: 'https://example.com/',
+      requestId: '0',
+      timing: {receiveHeadersEnd: 400, sendEnd: 200},
+    };
+
     const artifacts = {
       devtoolsLogs: {[TimeToFirstByte.DEFAULT_PASS]: []},
-      requestNetworkRecords: () => Promise.resolve(networkRecords),
+      requestMainResource: () => Promise.resolve(mainResource),
       URL: {finalUrl: 'https://example.com/'},
     };
 
@@ -45,25 +47,4 @@ describe('Performance: time-to-first-byte audit', () => {
       assert.strictEqual(result.score, 1);
     });
   });
-
-  it('throws when somehow finalUrl is not in network records', async () => {
-    const networkRecords = [
-      {url: 'https://example.com/', requestId: '0', timing: {receiveHeadersEnd: 400, sendEnd: 200}},
-      {url: 'https://google.com/styles.css', requestId: '1', timing: {receiveHeadersEnd: 850, sendEnd: 200}},
-      {url: 'https://google.com/image.jpg', requestId: '2', timing: {receiveHeadersEnd: 1000, sendEnd: 400}},
-    ];
-    const badFinalUrl = 'https://badexample.com/';
-    const artifacts = {
-      devtoolsLogs: {[TimeToFirstByte.DEFAULT_PASS]: []},
-      requestNetworkRecords: () => Promise.resolve(networkRecords),
-      URL: {finalUrl: badFinalUrl},
-    };
-
-    try {
-      await TimeToFirstByte.audit(artifacts);
-      throw new Error('TimeToFirstByte did not throw');
-    } catch (e) {
-      assert.ok(e.message.includes(badFinalUrl));
-    }
-  });
 });