Repository: GoogleChrome/lighthouse
Pull request #9109 (2019-06-03 20:45:15)

core(cli): accept flags from path
**Summary**
In order to easily accept an LH settings object over in Lighthouse CI, we need a way to easily passthrough the object. This can be done with config today but you miss out on some nice features like being able to use `--budgets-path`, set the `--output-path`, etc. 

This is a low-cost way to enable consumers of the LH CLI that run in a child process programmatic access to all our CLI flags without manually (and error-pronely) constructing an argument list.



Patch:
diff --git a/docs/readme.md b/docs/readme.md
index 4e376a975..0cd05d057 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -115,6 +115,14 @@ instance with an open debugging port.
 1. Navigate to your site and log in.
 1. In a separate terminal tab, run `lighthouse http://mysite.com --port port-number` using the port number from chrome-debug.
 
+## Testing on a site with an untrusted certificate
+
+When testing a site with an untrusted certificate, Chrome will be unable to load the page and so the Lighthouse report will mostly contain errors.
+
+If this certificate **is one you control** and is necessary for development (for instance, `localhost` with a self-signed certificate for local HTTP/2 testing), we recommend you _add the certificate to your locally-trusted certificate store_. In Chrome, see `Settings` > `Privacy and Security` > `Manage certificates` or consult instructions for adding to the certificate store in your operating system.
+
+Alternatively, you can instruct Chrome to ignore the invalid certificate by adding the Lighthouse CLI flag `--chrome-flags="--ignore-certificate-errors"`. However, you must be as careful with this flag as it's equivalent to browsing the web with TLS disabled. Any content loaded by the test page (e.g. third-party scripts or iframed ads) will *also* not be subject to certificate checks, [opening up avenues for MitM attacks](https://www.chromium.org/Home/chromium-security/education/tls#TOC-What-security-properties-does-TLS-give-me-). For these reasons, we recommend the earlier solution of adding the certificate to your local cert store.
+
 ## Testing on a mobile device
 
 Lighthouse can run against a real mobile device. You can follow the [Remote Debugging on Android (Legacy Workflow)](https://developer.chrome.com/devtools/docs/remote-debugging-legacy) up through step 3.3, but the TL;DR is install & run adb, enable USB debugging, then port forward 9222 from the device to the machine with Lighthouse.
diff --git a/lighthouse-cli/cli-flags.js b/lighthouse-cli/cli-flags.js
index 9b9ceb989..9e7188f04 100644
--- a/lighthouse-cli/cli-flags.js
+++ b/lighthouse-cli/cli-flags.js
@@ -62,6 +62,17 @@ function getFlags(manualArgv) {
           'lighthouse <url> --only-categories=performance,pwa',
           'Only run specific categories.')
 
+      /**
+       * Also accept a file for all of these flags. Yargs will merge in and override the file-based
+       * flags with the command-line flags.
+       *
+       * i.e. when command-line `--throttling-method=provided` and file `throttlingMethod: "devtools"`,
+       * throttlingMethod will be `provided`.
+       *
+       * @see https://github.com/yargs/yargs/blob/a6e67f15a61558d0ba28bfe53385332f0ce5d431/docs/api.md#config
+       */
+      .config('cli-flags-path')
+
       // List of options
       .group(['verbose', 'quiet'], 'Logging:')
       .describe({
@@ -78,6 +89,7 @@ function getFlags(manualArgv) {
         ],
         'Configuration:')
       .describe({
+        'cli-flags-path': 'The path to a JSON file that contains the desired CLI flags to apply. Flags specified at the command line will still override the file-based ones.',
         // We don't allowlist specific locales. Why? So we can support the user who requests 'es-MX' (unsupported) and we'll fall back to 'es' (supported)
         'locale': 'The locale/language the report should be formatted in',
         'enable-error-reporting':
diff --git a/lighthouse-core/gather/driver.js b/lighthouse-core/gather/driver.js
index 93f267a8a..a07c4d6d6 100644
--- a/lighthouse-core/gather/driver.js
+++ b/lighthouse-core/gather/driver.js
@@ -903,53 +903,6 @@ class Driver {
     };
   }
 
-  /**
-   * Return a promise that resolves when an insecure security state is encountered
-   * and a method to cancel internal listeners.
-   * @return {{promise: Promise<string>, cancel: function(): void}}
-   * @private
-   */
-  _monitorForInsecureState() {
-    /** @type {(() => void)} */
-    let cancel = () => {
-      throw new Error('_monitorForInsecureState.cancel() called before it was defined');
-    };
-
-    const promise = new Promise((resolve, reject) => {
-      /**
-       * @param {LH.Crdp.Security.SecurityStateChangedEvent} event
-       */
-      const securityStateChangedListener = ({
-        securityState,
-        explanations,
-        schemeIsCryptographic,
-      }) => {
-        if (securityState === 'insecure' && schemeIsCryptographic) {
-          cancel();
-          const insecureDescriptions = explanations
-            .filter(exp => exp.securityState === 'insecure')
-            .map(exp => exp.description);
-          resolve(insecureDescriptions.join(' '));
-        }
-      };
-      let canceled = false;
-      cancel = () => {
-        if (canceled) return;
-        canceled = true;
-        this.off('Security.securityStateChanged', securityStateChangedListener);
-        // TODO(@patrickhulce): cancel() should really be a promise itself to handle things like this
-        this.sendCommand('Security.disable').catch(() => {});
-      };
-      this.on('Security.securityStateChanged', securityStateChangedListener);
-      this.sendCommand('Security.enable').catch(() => {});
-    });
-
-    return {
-      promise,
-      cancel,
-    };
-  }
-
   /**
    * Returns whether the page appears to be hung.
    * @return {Promise<boolean>}
@@ -1001,13 +954,6 @@ class Driver {
     // CPU listener. Resolves when the CPU has been idle for cpuQuietThresholdMs after network idle.
     let waitForCPUIdle = this._waitForNothing();
 
-    const monitorForInsecureState = this._monitorForInsecureState();
-    const securityCheckPromise = monitorForInsecureState.promise.then(securityMessages => {
-      return function() {
-        throw new LHError(LHError.errors.INSECURE_DOCUMENT_REQUEST, {securityMessages});
-      };
-    });
-
     // Wait for both load promises. Resolves on cleanup function the clears load
     // timeout timer.
     const loadPromise = Promise.all([
@@ -1044,9 +990,8 @@ class Driver {
       };
     });
 
-    // Wait for security issue, load or timeout and run the cleanup function the winner returns.
+    // Wait for load or timeout and run the cleanup function the winner returns.
     const cleanupFn = await Promise.race([
-      securityCheckPromise,
       loadPromise,
       maxTimeoutPromise,
     ]);
@@ -1056,7 +1001,6 @@ class Driver {
     waitForLoadEvent.cancel();
     waitForNetworkIdle.cancel();
     waitForCPUIdle.cancel();
-    monitorForInsecureState.cancel();
 
     await cleanupFn();
   }
diff --git a/readme.md b/readme.md
index f911924eb..c0ce1e340 100644
--- a/readme.md
+++ b/readme.md
@@ -89,6 +89,8 @@ Output:
 Options:
   --help                        Show help                                                                                                  [boolean]
   --version                     Show version number                                                                                        [boolean]
+  --cli-flags-path              The path to a JSON file that contains the desired CLI flags to apply.
+                                Flags specified at the command line will still override the file-based ones.
   --blocked-url-patterns        Block any network requests to the specified URL patterns                                                     [array]
   --disable-storage-reset       Disable clearing the browser cache and other storage APIs before a run                                     [boolean]
   --throttling-method                  Controls throttling method         [choices: "devtools", "provided", "simulate"]
@@ -304,22 +306,24 @@ This section details services that have integrated Lighthouse data. If you're wo
 ## Related Projects
 Other awesome open source projects that use Lighthouse.
 
-* **[webpack-lighthouse-plugin](https://github.com/addyosmani/webpack-lighthouse-plugin)** - run Lighthouse from a Webpack build.
-* **[lighthouse-mocha-example](https://github.com/justinribeiro/lighthouse-mocha-example)** - gather performance metrics via Lighthouse and tests them in Mocha
-* **[pwmetrics](https://github.com/paulirish/pwmetrics/)** - gather performance metrics
-* **[lighthouse-hue](https://github.com/ebidel/lighthouse-hue)** - set the color of Philips Hue lights based on a Lighthouse score
-   * **[lighthouse-magic-light](https://github.com/manekinekko/lighthouse-magic-light)** set the color of the MagicLight Bluetooth Smart Light Bulb based on Lighthouse score
-* **[lighthouse-batch](https://www.npmjs.com/package/lighthouse-batch)** - run Lighthouse over a number of sites and generate a summary of their metrics/scores.
+* **[Garie](https://github.com/boyney123/garie)** — An open source tool for monitoring performance using Lighthouse,  PageSpeed Insights, [Prometheus](https://prometheus.io/), [Grafana](https://grafana.com/) and [Docker](https://www.docker.com/).
+* **[Gimbal](https://labs.moduscreate.com/gimbal-web-performance-audit-budgeting)** - An [open source (MIT licensed)](https://github.com/ModusCreateOrg/gimbal) tool used to measure, analyze, and budget aspects of a web application. Gimbal also integrates reports with GitHub pull requests.
+* **[lightcrawler](https://github.com/github/lightcrawler)** - Crawl a website and run each page found through Lighthouse.
 * **[lighthouse-badges](https://github.com/emazzotta/lighthouse-badges)** - Generate gh-badges (shields.io) based on Lighthouse performance.
+* **[lighthouse-batch](https://www.npmjs.com/package/lighthouse-batch)** - run Lighthouse over a number of sites and generate a summary of their metrics/scores.
+* **[lighthouse-ci](https://github.com/andreasonny83/lighthouse-ci)** - Run Lighthouse and assert scores satisfy your custom thresholds.
 * **[lighthouse-cron](https://github.com/thearegee/lighthouse-cron)** - Cron multiple batch Lighthouse audits and emit results for sending to remote server.
-* **[lightcrawler](https://github.com/github/lightcrawler)** - Crawl a website and run each page found through Lighthouse.
+* **[lighthouse-gh-reporter](https://github.com/carlesnunez/lighthouse-gh-reporter)** - Run Lighthouse in CI and report back in a comment on your pull requests
+* **[lighthouse-hue](https://github.com/ebidel/lighthouse-hue)** - set the color of Philips Hue lights based on a Lighthouse score
 * **[lighthouse-lambda](https://github.com/joytocode/lighthouse-lambda)** - Run Lighthouse on AWS Lambda with prebuilt stable desktop Headless Chrome.
-* **[Garie](https://github.com/boyney123/garie)** — An open source tool for monitoring performance using Lighthouse,  PageSpeed Insights, [Prometheus](https://prometheus.io/), [Grafana](https://grafana.com/) and [Docker](https://www.docker.com/).
-* **[lighthouse-ci](https://github.com/andreasonny83/lighthouse-ci)** - Run Lighthouse and assert scores satisfy your custom thresholds.
+* **[lighthouse-magic-light](https://github.com/manekinekko/lighthouse-magic-light)** set the color of the MagicLight Bluetooth Smart Light Bulb based on Lighthouse score
+* **[lighthouse-mocha-example](https://github.com/justinribeiro/lighthouse-mocha-example)** - gather performance metrics via Lighthouse and tests them in Mocha
 * **[lighthouse4u](https://github.com/godaddy/lighthouse4u)** - LH4U provides Google Lighthouse as a service, surfaced by both a friendly UI+API, and backed by Elastic Search for easy querying and visualization.
-* **[lighthouse-gh-reporter](https://github.com/carlesnunez/lighthouse-gh-reporter)** - Run Lighthouse in CI and report back in a comment on your pull requests
-* **[react-lighthouse-viewer](https://www.npmjs.com/package/react-lighthouse-viewer)** - Render a Lighthouse JSON report in a React Component.
 * **[performance-budgets](https://performance-budgets.netlify.com/)** - Easily assert Lighthouse budgets with Docker.
+* **[pwmetrics](https://github.com/paulirish/pwmetrics/)** - gather performance metrics
+* **[react-lighthouse-viewer](https://www.npmjs.com/package/react-lighthouse-viewer)** - Render a Lighthouse JSON report in a React Component.
+* **[webpack-lighthouse-plugin](https://github.com/addyosmani/webpack-lighthouse-plugin)** - run Lighthouse from a Webpack build.
+
 ## FAQ
 
 ### How does Lighthouse work?


Test patch:
diff --git a/lighthouse-cli/test/cli/cli-flags-test.js b/lighthouse-cli/test/cli/cli-flags-test.js
index 431386a0f..ed7d073ac 100644
--- a/lighthouse-cli/test/cli/cli-flags-test.js
+++ b/lighthouse-cli/test/cli/cli-flags-test.js
@@ -29,6 +29,25 @@ describe('CLI bin', function() {
     });
   });
 
+  it('settings are accepted from a file path', () => {
+    const flags = getFlags([
+      'http://www.example.com',
+      `--cli-flags-path="${__dirname}/../fixtures/cli-flags-path.json"`,
+      '--budgets-path=path/to/my/budget-from-command-line.json', // this should override the config value
+    ].join(' '));
+
+    expect(flags).toMatchObject({
+      budgetsPath: 'path/to/my/budget-from-command-line.json',
+      onlyCategories: ['performance', 'seo'],
+      chromeFlags: '--window-size 800,600',
+      throttlingMethod: 'devtools',
+      throttling: {
+        requestLatencyMs: 700,
+        cpuSlowdownMultiplier: 6,
+      },
+    });
+  });
+
   it('array values support csv when appropriate', () => {
     const flags = getFlags([
       'http://www.example.com',
diff --git a/lighthouse-cli/test/fixtures/cli-flags-path.json b/lighthouse-cli/test/fixtures/cli-flags-path.json
new file mode 100644
index 000000000..7d993c9df
--- /dev/null
+++ b/lighthouse-cli/test/fixtures/cli-flags-path.json
@@ -0,0 +1,10 @@
+{
+  "budgetPath": "path/to/my/budget-from-config.json",
+  "onlyCategories": ["performance", "seo"],
+  "chromeFlags": "--window-size 800,600",
+  "throttling-method": "devtools",
+  "throttling": {
+    "requestLatencyMs": 700,
+    "cpuSlowdownMultiplier": 6
+  }
+}
diff --git a/lighthouse-cli/test/smokehouse/error-expectations.js b/lighthouse-cli/test/smokehouse/error-expectations.js
index 04d804e5a..55afc640f 100644
--- a/lighthouse-cli/test/smokehouse/error-expectations.js
+++ b/lighthouse-cli/test/smokehouse/error-expectations.js
@@ -18,10 +18,10 @@ module.exports = [
     },
   },
   {
-    errorCode: 'INSECURE_DOCUMENT_REQUEST',
+    errorCode: undefined,
     lhr: {
       requestedUrl: 'https://expired.badssl.com',
-      finalUrl: 'https://expired.badssl.com',
+      finalUrl: 'https://expired.badssl.com/',
       audits: {},
     },
   },
diff --git a/lighthouse-core/test/gather/driver-test.js b/lighthouse-core/test/gather/driver-test.js
index d96ee191b..dfe5ed6ba 100644
--- a/lighthouse-core/test/gather/driver-test.js
+++ b/lighthouse-core/test/gather/driver-test.js
@@ -675,108 +675,6 @@ describe('.gotoURL', () => {
       // Make sure we still cleaned up our listeners
       expect(driver._waitForLoadEvent.getMockCancelFn()).toHaveBeenCalled();
     });
-
-    it('does not reject when page is secure', async () => {
-      const secureSecurityState = {
-        explanations: [],
-        securityState: 'secure',
-      };
-
-      driver.on = driver.once = createMockOnceFn()
-        .mockEvent('Security.securityStateChanged', secureSecurityState);
-
-      const startUrl = 'https://www.example.com';
-      const loadOptions = {
-        waitForLoad: true,
-        passContext: {
-          settings: {
-            maxWaitForLoad: 1,
-          },
-        },
-      };
-
-      const loadPromise = driver.gotoURL(startUrl, loadOptions);
-      await flushAllTimersAndMicrotasks();
-      await loadPromise;
-    });
-
-    it('does not reject when page is insecure but http', async () => {
-      const secureSecurityState = {
-        explanations: [],
-        securityState: 'insecure',
-        schemeIsCryptographic: false,
-      };
-
-      driver.on = driver.once = createMockOnceFn()
-        .mockEvent('Security.securityStateChanged', secureSecurityState);
-
-      const startUrl = 'https://www.example.com';
-      const loadOptions = {
-        waitForLoad: true,
-        passContext: {
-          settings: {
-            maxWaitForLoad: 1,
-          },
-        },
-      };
-
-      const loadPromise = driver.gotoURL(startUrl, loadOptions);
-      await flushAllTimersAndMicrotasks();
-      await loadPromise;
-    });
-
-    it('rejects when page is insecure', async () => {
-      const insecureSecurityState = {
-        explanations: [
-          {
-            description: 'reason 1.',
-            securityState: 'insecure',
-          },
-          {
-            description: 'blah.',
-            securityState: 'info',
-          },
-          {
-            description: 'reason 2.',
-            securityState: 'insecure',
-          },
-        ],
-        securityState: 'insecure',
-        schemeIsCryptographic: true,
-      };
-
-      driver.on = driver.once = createMockOnceFn();
-
-      const startUrl = 'https://www.example.com';
-      const loadOptions = {
-        waitForLoad: true,
-        passContext: {
-          passConfig: {
-            networkQuietThresholdMs: 1,
-          },
-        },
-      };
-
-      // 2 assertions in the catch block and the 1 implicit in `findListener`
-      expect.assertions(3);
-
-      try {
-        const loadPromise = driver.gotoURL(startUrl, loadOptions);
-        await flushAllTimersAndMicrotasks();
-
-        // Use `findListener` instead of `mockEvent` so we can control exactly when the promise resolves
-        const listener = driver.on.findListener('Security.securityStateChanged');
-        listener(insecureSecurityState);
-        await flushAllTimersAndMicrotasks();
-        await loadPromise;
-      } catch (err) {
-        expect(err).toHaveProperty('code', 'INSECURE_DOCUMENT_REQUEST');
-        expect(err.friendlyMessage).toBeDisplayString(
-          'The URL you have provided does not have a valid security certificate. ' +
-          'reason 1. reason 2.'
-        );
-      }
-    });
   });
 });