Repository: GoogleChrome/lighthouse
Pull request #12609 (2021-06-02 23:37:46)

tests(treemap): add test for node coverage shading
followup from https://github.com/GoogleChrome/lighthouse/pull/12603#issuecomment-853288185

i wrote the code that was wrong, so here's my penance.

---

driveby change of port number... no reason it needs to conflict w/ 10200 and i'm always happy to avoid stopping my servers.


Patch:
diff --git a/lighthouse-core/fraggle-rock/config/default-config.js b/lighthouse-core/fraggle-rock/config/default-config.js
index 6d2d961ce..ef55b08ca 100644
--- a/lighthouse-core/fraggle-rock/config/default-config.js
+++ b/lighthouse-core/fraggle-rock/config/default-config.js
@@ -25,6 +25,8 @@ const artifacts = {
   FormElements: '',
   FullPageScreenshot: '',
   GlobalListeners: '',
+  HostFormFactor: '',
+  HostUserAgent: '',
   IFrameElements: '',
   ImageElements: '',
   InstallabilityErrors: '',
@@ -57,6 +59,8 @@ for (const key of Object.keys(artifacts)) {
 const defaultConfig = {
   artifacts: [
     // Artifacts which can be depended on come first.
+    {id: artifacts.HostUserAgent, gatherer: 'host-user-agent'},
+    {id: artifacts.HostFormFactor, gatherer: 'host-form-factor'},
     {id: artifacts.DevtoolsLog, gatherer: 'devtools-log'},
     {id: artifacts.Trace, gatherer: 'trace'},
 
@@ -109,6 +113,8 @@ const defaultConfig = {
       cpuQuietThresholdMs: 1000,
       artifacts: [
         // Artifacts which can be depended on come first.
+        artifacts.HostUserAgent,
+        artifacts.HostFormFactor,
         artifacts.DevtoolsLog,
         artifacts.Trace,
 
diff --git a/lighthouse-core/fraggle-rock/config/filters.js b/lighthouse-core/fraggle-rock/config/filters.js
index 701884022..7e88699dd 100644
--- a/lighthouse-core/fraggle-rock/config/filters.js
+++ b/lighthouse-core/fraggle-rock/config/filters.js
@@ -11,8 +11,6 @@ const Audit = require('../../audits/audit.js');
 const baseArtifactKeySource = {
   fetchTime: '',
   LighthouseRunWarnings: '',
-  HostFormFactor: '',
-  HostUserAgent: '',
   BenchmarkIndex: '',
   settings: '',
   Timing: '',
diff --git a/lighthouse-core/fraggle-rock/gather/base-artifacts.js b/lighthouse-core/fraggle-rock/gather/base-artifacts.js
index 76301994c..e417138bd 100644
--- a/lighthouse-core/fraggle-rock/gather/base-artifacts.js
+++ b/lighthouse-core/fraggle-rock/gather/base-artifacts.js
@@ -8,7 +8,6 @@
 const log = require('lighthouse-logger');
 const isEqual = require('lodash.isequal');
 const {
-  getBrowserVersion,
   getBenchmarkIndex,
   getEnvironmentWarnings,
 } = require('../../gather/driver/environment.js');
@@ -19,12 +18,6 @@ const {
  * @return {Promise<LH.BaseArtifacts>}
  */
 async function getBaseArtifacts(config, driver) {
-  const HostUserAgent = (await getBrowserVersion(driver.defaultSession)).userAgent;
-
-  // Whether Lighthouse was run on a mobile device (i.e. not on a desktop machine).
-  const HostFormFactor =
-    HostUserAgent.includes('Android') || HostUserAgent.includes('Mobile') ? 'mobile' : 'desktop';
-
   const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
 
   return {
@@ -34,13 +27,13 @@ async function getBaseArtifacts(config, driver) {
     LighthouseRunWarnings: [],
     settings: config.settings,
     // Environment artifacts that can always be computed.
-    HostFormFactor,
-    HostUserAgent,
     BenchmarkIndex,
     // Contextual artifacts whose collection changes based on gather mode.
     URL: {requestedUrl: '', finalUrl: ''},
     PageLoadError: null,
     // Artifacts that have been replaced by regular gatherers in Fraggle Rock.
+    HostFormFactor: 'mobile',
+    HostUserAgent: '',
     Stacks: [],
     NetworkUserAgent: '',
     WebAppManifest: null,
diff --git a/lighthouse-core/gather/gatherers/host-form-factor.js b/lighthouse-core/gather/gatherers/host-form-factor.js
new file mode 100644
index 000000000..5b7d1bf51
--- /dev/null
+++ b/lighthouse-core/gather/gatherers/host-form-factor.js
@@ -0,0 +1,31 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. 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 FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
+const HostUserAgent = require('./host-user-agent.js');
+
+class HostFormFactor extends FRGatherer {
+  static symbol = Symbol('HostFormFactor');
+
+  /** @type {LH.Gatherer.GathererMeta<'HostUserAgent'>} */
+  meta = {
+    symbol: HostFormFactor.symbol,
+    supportedModes: ['snapshot', 'timespan', 'navigation'],
+    dependencies: {HostUserAgent: HostUserAgent.symbol},
+  };
+
+  /**
+   * @param {LH.Gatherer.FRTransitionalContext<'HostUserAgent'>} context
+   * @return {Promise<LH.Artifacts['HostFormFactor']>}
+   */
+  async getArtifact(context) {
+    const userAgent = context.dependencies.HostUserAgent;
+    return userAgent.includes('Android') || userAgent.includes('Mobile') ? 'mobile' : 'desktop';
+  }
+}
+
+module.exports = HostFormFactor;
diff --git a/lighthouse-core/gather/gatherers/host-user-agent.js b/lighthouse-core/gather/gatherers/host-user-agent.js
new file mode 100644
index 000000000..f49d1cb5c
--- /dev/null
+++ b/lighthouse-core/gather/gatherers/host-user-agent.js
@@ -0,0 +1,29 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. 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 FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
+const {getBrowserVersion} = require('../driver/environment.js');
+
+class HostUserAgent extends FRGatherer {
+  static symbol = Symbol('HostUserAgent');
+
+  /** @type {LH.Gatherer.GathererMeta} */
+  meta = {
+    symbol: HostUserAgent.symbol,
+    supportedModes: ['snapshot', 'timespan', 'navigation'],
+  };
+
+  /**
+   * @param {LH.Gatherer.FRTransitionalContext} context
+   * @return {Promise<LH.Artifacts['HostUserAgent']>}
+   */
+  getArtifact(context) {
+    return getBrowserVersion(context.driver.defaultSession).then(v => v.userAgent);
+  }
+}
+
+module.exports = HostUserAgent;
diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts
index 9944615aa..09fc5ba0e 100644
--- a/types/artifacts.d.ts
+++ b/types/artifacts.d.ts
@@ -46,10 +46,6 @@ declare global {
       fetchTime: string;
       /** A set of warnings about unexpected things encountered while loading and testing the page. */
       LighthouseRunWarnings: Array<string | IcuMessage>;
-      /** Device which Chrome is running on. */
-      HostFormFactor: 'desktop'|'mobile';
-      /** The user agent string of the version of Chrome used. */
-      HostUserAgent: string;
       /** The benchmark index that indicates rough device class. */
       BenchmarkIndex: number;
       /** An object containing information about the testing configuration used by Lighthouse. */
@@ -72,6 +68,10 @@ declare global {
      * The set of base artifacts that were replaced by standard gatherers in Fraggle Rock.
      */
     export interface LegacyBaseArtifacts {
+      /** Device which Chrome is running on. */
+      HostFormFactor: 'desktop'|'mobile';
+      /** The user agent string of the version of Chrome used. */
+      HostUserAgent: string;
       /** The user agent string that Lighthouse used to load the page. Set to the empty string if unknown. */
       NetworkUserAgent: string;
       /** Information on detected tech stacks (e.g. JS libraries) used by the page. */
@@ -114,7 +114,7 @@ declare global {
      * Artifacts provided by the default gatherers. Augment this interface when adding additional
      * gatherers. Changes to these artifacts are not considered a breaking Lighthouse change.
      */
-    export interface GathererArtifacts extends PublicGathererArtifacts {
+    export interface GathererArtifacts extends PublicGathererArtifacts,LegacyBaseArtifacts {
       /** The results of running the aXe accessibility tests on the page. */
       Accessibility: Artifacts.Accessibility;
       /** Array of all anchors on the page. */
diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts
index 7e1a82bdd..382a49960 100644
--- a/types/gatherer.d.ts
+++ b/types/gatherer.d.ts
@@ -69,14 +69,7 @@ declare global {
       trace?: Trace;
     }
 
-    export type PhaseArtifact = |
-        LH.GathererArtifacts[keyof LH.GathererArtifacts] |
-      LH.Artifacts['devtoolsLogs'] |
-      LH.Artifacts['traces'] |
-      LH.Artifacts['WebAppManifest'] |
-      LH.Artifacts['InstallabilityErrors'] |
-      LH.Artifacts['Stacks'];
-    export type PhaseResultNonPromise = void|PhaseArtifact
+    export type PhaseResultNonPromise = void | LH.GathererArtifacts[keyof LH.GathererArtifacts];
     export type PhaseResult = PhaseResultNonPromise | Promise<PhaseResultNonPromise>
 
     export type GatherMode = 'snapshot'|'timespan'|'navigation';


Test patch:
diff --git a/lighthouse-core/test/fraggle-rock/config/filters-test.js b/lighthouse-core/test/fraggle-rock/config/filters-test.js
index a1c2a9738..df82b4061 100644
--- a/lighthouse-core/test/fraggle-rock/config/filters-test.js
+++ b/lighthouse-core/test/fraggle-rock/config/filters-test.js
@@ -92,7 +92,7 @@ describe('Fraggle Rock Config Filtering', () => {
       class SnapshotWithBase extends BaseAudit {
         static meta = {
           id: 'snapshot',
-          requiredArtifacts: /** @type {any} */ (['Snapshot', 'URL', 'HostUserAgent']),
+          requiredArtifacts: /** @type {any} */ (['Snapshot', 'URL', 'Timing']),
           ...auditMeta,
         };
       }
diff --git a/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js b/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js
index 2d9c7112a..e5ab2ac0d 100644
--- a/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js
+++ b/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js
@@ -15,40 +15,17 @@ const {initializeConfig} = require('../../../fraggle-rock/config/config.js');
 const {createMockDriver} = require('./mock-driver.js');
 const LighthouseError = require('../../../lib/lh-error.js');
 
-/** @param {{userAgent: string}} params */
-function getMockDriverForArtifacts({userAgent}) {
+function getMockDriverForArtifacts() {
   const driverMock = createMockDriver();
-  driverMock._session.sendCommand.mockResponse('Browser.getVersion', {
-    userAgent,
-    product: '',
-  });
-
   driverMock._executionContext.evaluate.mockResolvedValue(500);
-
   return driverMock;
 }
 
 describe('getBaseArtifacts', () => {
-  let driverMock = getMockDriverForArtifacts({userAgent: 'Desktop Chrome'});
+  let driverMock = getMockDriverForArtifacts();
 
   beforeEach(() => {
-    driverMock = getMockDriverForArtifacts({userAgent: 'Desktop Chrome'});
-  });
-
-  it('should fetch the desktop user agent', async () => {
-    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
-    const artifacts = await getBaseArtifacts(config, driverMock.asDriver());
-    expect(artifacts.HostUserAgent).toEqual('Desktop Chrome');
-    expect(artifacts.HostFormFactor).toEqual('desktop');
-  });
-
-  it('should fetch the mobile user agent', async () => {
-    driverMock = getMockDriverForArtifacts({userAgent: 'Mobile Chrome'});
-
-    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
-    const artifacts = await getBaseArtifacts(config, driverMock.asDriver());
-    expect(artifacts.HostUserAgent).toEqual('Mobile Chrome');
-    expect(artifacts.HostFormFactor).toEqual('mobile');
+    driverMock = getMockDriverForArtifacts();
   });
 
   it('should fetch benchmark index', async () => {
@@ -72,7 +49,7 @@ describe('finalizeArtifacts', () => {
 
   beforeEach(async () => {
     const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
-    const driver = getMockDriverForArtifacts({userAgent: 'Desktop Chrome'}).asDriver();
+    const driver = getMockDriverForArtifacts().asDriver();
     baseArtifacts = await getBaseArtifacts(config, driver);
     baseArtifacts.URL = {requestedUrl: 'http://example.com', finalUrl: 'https://example.com'};
     gathererArtifacts = {};
@@ -81,6 +58,7 @@ describe('finalizeArtifacts', () => {
   it('should merge the two objects', () => {
     baseArtifacts.LighthouseRunWarnings = [{i18nId: '1', formattedDefault: 'Yes'}];
     gathererArtifacts.LighthouseRunWarnings = [{i18nId: '2', formattedDefault: 'No'}];
+    gathererArtifacts.HostUserAgent = 'Desktop Chrome';
 
     const winningError = new LighthouseError(LighthouseError.errors.NO_LCP);
     baseArtifacts.PageLoadError = new LighthouseError(LighthouseError.errors.NO_FCP);
diff --git a/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js b/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js
index 74a9b9932..93002fb1d 100644
--- a/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js
+++ b/lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js
@@ -114,7 +114,7 @@ describe('NavigationRunner', () => {
 
     it('should collect base artifacts', async () => {
       const {baseArtifacts} = await runner._setup({driver, config, requestedUrl});
-      expect(baseArtifacts).toMatchObject({HostUserAgent: 'Chrome', URL: {requestedUrl}});
+      expect(baseArtifacts).toMatchObject({URL: {requestedUrl}});
     });
 
     it('should prepare the target for navigation', async () => {
@@ -123,8 +123,8 @@ describe('NavigationRunner', () => {
     });
 
     it('should prepare the target for navigation *after* base artifact collection', async () => {
-      mockDriver._session.sendCommand.mockReset();
-      mockDriver._session.sendCommand.mockRejectedValue(new Error('Not available'));
+      mockDriver._executionContext.evaluate.mockReset();
+      mockDriver._executionContext.evaluate.mockRejectedValue(new Error('Not available'));
       const setupPromise = runner._setup({driver, config, requestedUrl});
       await expect(setupPromise).rejects.toThrowError(/Not available/);
       expect(mocks.prepareMock.prepareTargetForNavigationMode).not.toHaveBeenCalled();
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 08928fde7..a8a8534d2 100644
--- a/lighthouse-core/test/report/html/renderer/report-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/report-renderer-test.js
@@ -323,8 +323,8 @@ describe('ReportRenderer', () => {
           resolve();
         });
       });
-    // Set timeout to 10s to give axe-core enough time to complete
+    // This test takes 40s on fast hardware, and 50-60s on GHA.
     // https://github.com/dequelabs/axe-core/tree/b573b1c1/doc/examples/jest_react#timeout-issues
-    }, /* timeout= */ 10 * 1000);
+    }, /* timeout= */ 100 * 1000);
   });
 });
diff --git a/lighthouse-treemap/test/treemap-test-pptr.js b/lighthouse-treemap/test/treemap-test-pptr.js
index c2893527b..214bcf0ac 100644
--- a/lighthouse-treemap/test/treemap-test-pptr.js
+++ b/lighthouse-treemap/test/treemap-test-pptr.js
@@ -12,7 +12,7 @@
 const fs = require('fs');
 const puppeteer = require('puppeteer');
 const {server} = require('../../lighthouse-cli/test/fixtures/static-server.js');
-const portNumber = 10200;
+const portNumber = 20202;
 const treemapUrl = `http://localhost:${portNumber}/dist/gh-pages/treemap/index.html`;
 const debugOptions = require('../app/debug.json');
 
@@ -141,4 +141,49 @@ describe('Lighthouse Treemap', () => {
       expect(optionsInPage.lhr.requestedUrl).toBe(options.lhr.requestedUrl);
     });
   });
+
+  describe('renders correctly', () => {
+    it('correctly shades coverage of gtm node', async () => {
+      await page.goto(`${treemapUrl}?debug`, {
+        waitUntil: 'networkidle0',
+        timeout: 30000,
+      });
+
+      await page.click('#view-mode--unused-bytes');
+      await page.waitForSelector('.lh-treemap--view-mode--unused-bytes');
+
+      // Identify the JS data.
+      const gtmNode = await page.evaluate(() => {
+        const d1Nodes = window.__treemapOptions.lhr.audits['script-treemap-data'].details.nodes;
+        const gtmNode = d1Nodes.find(n => n.name.includes('gtm.js'));
+        return gtmNode;
+      });
+
+      expect(gtmNode.unusedBytes).toBeGreaterThan(20_000);
+      expect(gtmNode.resourceBytes).toBeGreaterThan(20_000);
+
+      // Identify the DOM node.
+      const gtmElemHandle = await page.evaluateHandle(() => {
+        const captionEls = Array.from(document.querySelectorAll('.webtreemap-caption'));
+        return captionEls.find(el => el.textContent.includes('gtm.js')).parentElement;
+      });
+
+      expect(await gtmElemHandle.isIntersectingViewport()).toBeTruthy();
+
+      // Determine visual red shading percentage.
+      const percentRed = await gtmElemHandle.evaluate(node => {
+        const redWidthPx = parseInt(window.getComputedStyle(node, ':before').width);
+        const completeWidthPx = node.getBoundingClientRect().width;
+        return redWidthPx / completeWidthPx;
+      });
+
+      // Reminder! UNUSED == RED
+      const percentDataUnused = gtmNode.unusedBytes / gtmNode.resourceBytes;
+      expect(percentDataUnused).toBeGreaterThan(0);
+
+      // Assert 0.2520 ~= 0.2602 w/ 1 decimal place of precision.
+      // CSS pixels won't let us go to 2 decimal places.
+      expect(percentRed).toBeApproximately(percentDataUnused, 1);
+    });
+  });
 });