Repository: GoogleChrome/lighthouse (js)

Issue:
Proposal: Increase generic protocol timeout to 10s
We're getting a trickle of bug reports for timeouts on random commands (#6450 #6470 #6472). 5s seems somewhat arbitrary and it might be that 10s increases chances of completion? Perhaps there's something else going on though and these are just surfacing other connection problems. If we think that's the case, we should probably dig a little and try to surface a different error message?

WDYT @Hoten ?



Pull request description:
core: increase default protocol timeout to 10s
Fixes #6475.

Patch:
diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js
index 6fa2b3076..39ad7100f 100644
--- a/lighthouse-core/config/default-config.js
+++ b/lighthouse-core/config/default-config.js
@@ -372,7 +372,7 @@ const defaultConfig = {
         {id: 'is-on-https', weight: 2, group: 'pwa-installable'},
         {id: 'service-worker', weight: 1, group: 'pwa-installable'},
         {id: 'webapp-install-banner', weight: 2, group: 'pwa-installable'},
-        // Engaging
+        // PWA Optimized
         {id: 'redirects-http', weight: 2, group: 'pwa-optimized'},
         {id: 'splash-screen', weight: 1, group: 'pwa-optimized'},
         {id: 'themed-omnibox', weight: 1, group: 'pwa-optimized'},
diff --git a/lighthouse-core/gather/driver.js b/lighthouse-core/gather/driver.js
index 52a0b2ef3..d181c4901 100644
--- a/lighthouse-core/gather/driver.js
+++ b/lighthouse-core/gather/driver.js
@@ -26,7 +26,7 @@ const Connection = require('./connections/connection.js');
 // Controls how long to wait after onLoad before continuing
 const DEFAULT_PAUSE_AFTER_LOAD = 0;
 // Controls how long to wait between network requests before determining the network is quiet
-const DEFAULT_NETWORK_QUIET_THRESHOLD = 5000;
+const DEFAULT_NETWORK_QUIET_THRESHOLD = 10000;
 // Controls how long to wait between longtasks before determining the CPU is idle, off by default
 const DEFAULT_CPU_QUIET_THRESHOLD = 0;
 // Controls how long to wait for a response after sending a DevTools protocol command.
diff --git a/lighthouse-core/report/html/renderer/pwa-category-renderer.js b/lighthouse-core/report/html/renderer/pwa-category-renderer.js
index 72d3a6fd5..57a5710b2 100644
--- a/lighthouse-core/report/html/renderer/pwa-category-renderer.js
+++ b/lighthouse-core/report/html/renderer/pwa-category-renderer.js
@@ -16,7 +16,7 @@
  */
 'use strict';
 
-/* globals self, CategoryRenderer */
+/* globals self, Util, CategoryRenderer */
 
 class PwaCategoryRenderer extends CategoryRenderer {
   /**
@@ -34,7 +34,7 @@ class PwaCategoryRenderer extends CategoryRenderer {
     // Regular audits aren't split up into pass/fail/not-applicable clumps, they're
     // all put in a top-level clump that isn't expandable/collapsable.
     const regularAuditRefs = auditRefs.filter(ref => ref.result.scoreDisplayMode !== 'manual');
-    const auditsElem = this.renderUnexpandableClump(regularAuditRefs, groupDefinitions);
+    const auditsElem = this._renderAudits(regularAuditRefs, groupDefinitions);
     categoryElem.appendChild(auditsElem);
 
     // Manual audits are still in a manual clump.
@@ -45,6 +45,45 @@ class PwaCategoryRenderer extends CategoryRenderer {
 
     return categoryElem;
   }
+
+  /**
+   * Returns the group IDs whose audits are all considered passing.
+   * @param {Array<LH.ReportResult.AuditRef>} auditRefs
+   * @return {Set<string>}
+   */
+  _getPassingGroupIds(auditRefs) {
+    const groupIds = auditRefs.map(ref => ref.group).filter(/** @return {g is string} */ g => !!g);
+    const uniqueGroupIds = new Set(groupIds);
+
+    // Remove any that have a failing audit.
+    for (const auditRef of auditRefs) {
+      if (!Util.showAsPassed(auditRef.result) && auditRef.group) {
+        uniqueGroupIds.delete(auditRef.group);
+      }
+    }
+
+    return uniqueGroupIds;
+  }
+
+  /**
+   * Render non-manual audits in groups, giving a badge to any group that has
+   * all passing audits.
+   * @param {Array<LH.ReportResult.AuditRef>} auditRefs
+   * @param {Object<string, LH.Result.ReportGroup>} groupDefinitions
+   * @return {Element}
+   */
+  _renderAudits(auditRefs, groupDefinitions) {
+    const auditsElem = this.renderUnexpandableClump(auditRefs, groupDefinitions);
+
+    // Add a 'badged' class to group if all audits in that group pass.
+    const passsingGroupIds = this._getPassingGroupIds(auditRefs);
+    for (const groupId of passsingGroupIds) {
+      const groupElem = this.dom.find(`.lh-audit-group--${groupId}`, auditsElem);
+      groupElem.classList.add('lh-badged');
+    }
+
+    return auditsElem;
+  }
 }
 
 if (typeof module !== 'undefined' && module.exports) {
diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css
index eb3eddc61..c9723c168 100644
--- a/lighthouse-core/report/html/report-styles.css
+++ b/lighthouse-core/report/html/report-styles.css
@@ -97,6 +97,10 @@
   --pwa-fast-reliable-gray-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="nonzero"><circle fill="#DAE0E3" cx="12" cy="12" r="12"/><path d="M12.3 4l6.3 2.8V11c0 3.88-2.69 7.52-6.3 8.4C8.69 18.52 6 14.89 6 11V6.8L12.3 4zm-.56 12.88l3.3-5.79.04-.08c.05-.1.01-.29-.26-.29h-1.96l.56-3.92h-.56L9.6 12.52c0 .03.07-.12-.03.07-.11.2-.12.37.2.37h1.97l-.56 3.92h.56z" fill="#FFF"/></g></svg>');
   --pwa-installable-gray-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="nonzero"><circle fill="#DAE0E3" cx="12" cy="12" r="12"/><path d="M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z" fill="#FFF"/></g></svg>');
   --pwa-optimized-gray-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect fill="#DAE0E3" width="24" height="24" rx="12"/><path fill="#FFF" d="M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z"/><path d="M5 5h14v14H5z"/></g></svg>');
+
+  --pwa-fast-reliable-color-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><circle fill="#CCE3F6" cx="12" cy="12" r="12"/><path d="M12 4.3l6.3 2.8v4.2c0 3.88-2.69 7.52-6.3 8.4-3.61-.88-6.3-4.51-6.3-8.4V7.1L12 4.3zm-.56 12.88l3.3-5.79.04-.08c.05-.1.01-.29-.26-.29h-1.96l.56-3.92h-.56L9.3 12.82c0 .03.07-.12-.03.07-.11.2-.12.37.2.37h1.97l-.56 3.92h.56z" fill="#304FFE"/></g></svg>');
+  --pwa-installable-color-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><circle fill="#D4ECD5" cx="12" cy="12" r="12"/><path d="M12 5a7 7 0 1 0 0 14 7 7 0 0 0 0-14zm3.5 7.7h-2.8v2.8h-1.4v-2.8H8.5v-1.4h2.8V8.5h1.4v2.8h2.8v1.4z" fill="#009688"/></g></svg>');
+  --pwa-optimized-color-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect fill="#FCE4EC" width="24" height="24" rx="12"/><path d="M5 5h14v14H5z"/><path fill="#EC3F7A" d="M12 15.07l3.6 2.18-.95-4.1 3.18-2.76-4.2-.36L12 6.17l-1.64 3.86-4.2.36 3.2 2.76-.96 4.1z"/></g></svg>');
 }
 
 .lh-vars.lh-devtools {
@@ -636,6 +640,15 @@ details, summary {
   background-image: var(--pwa-optimized-gray-url);
   background-size: var(--lh-group-icon-background-size);
 }
+.lh-audit-group--pwa-fast-reliable.lh-badged .lh-audit-group__header::before {
+  background-image: var(--pwa-fast-reliable-color-url);
+}
+.lh-audit-group--pwa-installable.lh-badged .lh-audit-group__header::before {
+  background-image: var(--pwa-installable-color-url);
+}
+.lh-audit-group--pwa-optimized.lh-badged .lh-audit-group__header::before {
+  background-image: var(--pwa-optimized-color-url);
+}
 
 /* Removing too much whitespace */
 .lh-audit-group--metrics {


Test patch:
diff --git a/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js b/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js
index 92500c311..11a12a0cd 100644
--- a/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/pwa-category-renderer-test.js
@@ -39,7 +39,12 @@ describe('PwaCategoryRenderer', () => {
     pwaRenderer = new PwaCategoryRenderer(dom, detailsRenderer);
 
     sampleResults = Util.prepareReportResult(sampleResultsOrig);
-    category = sampleResults.reportCategories.find(cat => cat.id === 'pwa');
+  });
+
+  beforeEach(() => {
+    // Clone category to allow modifications.
+    const pwaCategory = sampleResults.reportCategories.find(cat => cat.id === 'pwa');
+    category = JSON.parse(JSON.stringify(pwaCategory));
   });
 
   afterAll(() => {
@@ -90,4 +95,82 @@ describe('PwaCategoryRenderer', () => {
         `trouble with selector '${selector}'`);
     });
   });
+
+  describe('badging groups', () => {
+    let auditRefs;
+    let groupIds;
+
+    beforeEach(() => {
+      auditRefs = category.auditRefs
+        .filter(audit => audit.result.scoreDisplayMode !== 'manual');
+
+      // Expect results to all be scorable.
+      for (const auditRef of auditRefs) {
+        assert.strictEqual(auditRef.result.scoreDisplayMode, 'binary');
+      }
+
+      groupIds = [...new Set(auditRefs.map(ref => ref.group))];
+    });
+
+    it('only gives a group a badge when all the group\'s audits are passing', () => {
+      for (const auditRef of auditRefs) {
+        auditRef.result.score = 0;
+      }
+
+      const targetGroupId = groupIds[2];
+      assert.ok(targetGroupId);
+      const targetAuditRefs = auditRefs.filter(ref => ref.group === targetGroupId);
+
+      // Try every permutation of audit scoring.
+      const totalPermutations = Math.pow(2, targetAuditRefs.length);
+      for (let i = 0; i < totalPermutations; i++) {
+        for (let j = 0; j < targetAuditRefs.length; j++) {
+          // Set as passing if jth bit in i is set.
+          targetAuditRefs[j].result.score = i >> j & 1;
+        }
+
+        const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
+        const badgedElems = categoryElem.querySelectorAll(`.lh-badged`);
+
+        // Only expect a badge on last permutation (all bits are set).
+        const expectedBadgeCount = i === totalPermutations - 1 ? 1 : 0;
+        assert.strictEqual(badgedElems.length, expectedBadgeCount);
+      }
+    });
+
+    it('renders all badges when all audits are passing', () => {
+      for (const auditRef of auditRefs) {
+        auditRef.result.score = 1;
+      }
+
+      const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
+      assert.strictEqual(categoryElem.querySelectorAll('.lh-badged').length, groupIds.length);
+    });
+
+    it('renders no badges when no audit groups are passing', () => {
+      for (const auditRef of auditRefs) {
+        auditRef.result.score = 0;
+      }
+
+      const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
+      assert.strictEqual(categoryElem.querySelectorAll('.lh-badged').length, 0);
+    });
+
+    it('renders all but one badge when all groups but one are passing', () => {
+      for (const auditRef of auditRefs) {
+        auditRef.result.score = 1;
+      }
+      auditRefs[0].result.score = 0;
+      const failingGroupId = auditRefs[0].group;
+
+      const categoryElem = pwaRenderer.render(category, sampleResults.categoryGroups);
+
+      for (const groupId of groupIds) {
+        const expectedCount = groupId === failingGroupId ? 0 : 1;
+
+        const groupElems = categoryElem.querySelectorAll(`.lh-audit-group--${groupId}.lh-badged`);
+        assert.strictEqual(groupElems.length, expectedCount);
+      }
+    });
+  });
 });
diff --git a/lighthouse-core/test/report/html/renderer/report-renderer-test.js b/lighthouse-core/test/report/html/renderer/report-renderer-test.js
index 87c072f2d..22c3fee12 100644
--- a/lighthouse-core/test/report/html/renderer/report-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/report-renderer-test.js
@@ -16,9 +16,6 @@ const DOM = require('../../../../report/html/renderer/dom.js');
 const DetailsRenderer = require('../../../../report/html/renderer/details-renderer.js');
 const ReportUIFeatures = require('../../../../report/html/renderer/report-ui-features.js');
 const CategoryRenderer = require('../../../../report/html/renderer/category-renderer.js');
-// lazy loaded because they depend on CategoryRenderer to be available globally
-let PerformanceCategoryRenderer = null;
-let PwaCategoryRenderer = null;
 const CriticalRequestChainRenderer = require(
     '../../../../report/html/renderer/crc-details-renderer.js');
 const ReportRenderer = require('../../../../report/html/renderer/report-renderer.js');
@@ -39,16 +36,12 @@ describe('ReportRenderer', () => {
     global.CriticalRequestChainRenderer = CriticalRequestChainRenderer;
     global.DetailsRenderer = DetailsRenderer;
     global.CategoryRenderer = CategoryRenderer;
-    if (!PerformanceCategoryRenderer) {
-      PerformanceCategoryRenderer =
+
+    // lazy loaded because they depend on CategoryRenderer to be available globally
+    global.PerformanceCategoryRenderer =
         require('../../../../report/html/renderer/performance-category-renderer.js');
-    }
-    global.PerformanceCategoryRenderer = PerformanceCategoryRenderer;
-    if (!PwaCategoryRenderer) {
-      PwaCategoryRenderer =
+    global.PwaCategoryRenderer =
         require('../../../../report/html/renderer/pwa-category-renderer.js');
-    }
-    global.PwaCategoryRenderer = PwaCategoryRenderer;
 
     // Stub out matchMedia for Node.
     global.matchMedia = function() {
diff --git a/lighthouse-core/test/report/html/renderer/report-ui-features-test.js b/lighthouse-core/test/report/html/renderer/report-ui-features-test.js
index cea3db85e..1b3566fca 100644
--- a/lighthouse-core/test/report/html/renderer/report-ui-features-test.js
+++ b/lighthouse-core/test/report/html/renderer/report-ui-features-test.js
@@ -16,9 +16,6 @@ const DOM = require('../../../../report/html/renderer/dom.js');
 const DetailsRenderer = require('../../../../report/html/renderer/details-renderer.js');
 const ReportUIFeatures = require('../../../../report/html/renderer/report-ui-features.js');
 const CategoryRenderer = require('../../../../report/html/renderer/category-renderer.js');
-// lazy loaded because they depend on CategoryRenderer to be available globally
-let PerformanceCategoryRenderer = null;
-let PwaCategoryRenderer = null;
 const CriticalRequestChainRenderer = require(
     '../../../../report/html/renderer/crc-details-renderer.js');
 const ReportRenderer = require('../../../../report/html/renderer/report-renderer.js');
@@ -41,16 +38,12 @@ describe('ReportUIFeatures', () => {
     global.CriticalRequestChainRenderer = CriticalRequestChainRenderer;
     global.DetailsRenderer = DetailsRenderer;
     global.CategoryRenderer = CategoryRenderer;
-    if (!PerformanceCategoryRenderer) {
-      PerformanceCategoryRenderer =
+
+    // lazy loaded because they depend on CategoryRenderer to be available globally
+    global.PerformanceCategoryRenderer =
         require('../../../../report/html/renderer/performance-category-renderer.js');
-    }
-    global.PerformanceCategoryRenderer = PerformanceCategoryRenderer;
-    if (!PwaCategoryRenderer) {
-      PwaCategoryRenderer =
+    global.PwaCategoryRenderer =
         require('../../../../report/html/renderer/pwa-category-renderer.js');
-    }
-    global.PwaCategoryRenderer = PwaCategoryRenderer;
 
     // Stub out matchMedia for Node.
     global.matchMedia = function() {