Repository: GoogleChrome/lighthouse
Pull request #12423 (2021-04-28 21:44:37)

core(robots): use new fetcher to get robots.txt
Fixes https://github.com/GoogleChrome/lighthouse/issues/10225

Use new fetcher to get `robots.txt`. The CSP of the page should not interfere this way.

adamraine: > does this method trigger the download UI if the mime type isn't set like the previous fetching approach?

No
adamraine: https://github.com/GoogleChrome/lighthouse/pull/12423/checks?check_run_id=2470752493

Getting a diagnostic message "Getting browser version" in the DevTools integration tests. Does that happen for every call to `Browser.getVersion`?

Patch:
diff --git a/.npmignore b/.npmignore
index 8ce1a4dbb..b7e1728c5 100644
--- a/.npmignore
+++ b/.npmignore
@@ -22,8 +22,11 @@ lighthouse-logger/
 !lighthouse-core/scripts/manual-chrome-launcher.js
 !lighthouse-core/scripts/download-chrome.sh
 
-# keep smokehouse tests, etc
-!lighthouse-cli/test
+# Exclude the CLI smoketests but keep the smoketest runner that is used
+# by downstream projects (e.g. publisher ads).
+lighthouse-cli/test/cli
+lighthouse-cli/test/fixtures
+lighthouse-cli/test/smokehouse/test-definitions
 
 results/
 lantern-data/
diff --git a/lighthouse-core/audits/metrics.js b/lighthouse-core/audits/metrics.js
index fa839d300..53820156c 100644
--- a/lighthouse-core/audits/metrics.js
+++ b/lighthouse-core/audits/metrics.js
@@ -44,7 +44,7 @@ class Metrics extends Audit {
     const trace = artifacts.traces[Audit.DEFAULT_PASS];
     const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
     const summary = await ComputedTimingSummary
-      .request({trace, devtoolsLog}, context);
+      .request({trace, devtoolsLog, settings: context.settings}, context);
     const metrics = summary.metrics;
     const debugInfo = summary.debugInfo;
 
diff --git a/lighthouse-core/audits/performance-budget.js b/lighthouse-core/audits/performance-budget.js
index c8e68a4da..217b51537 100644
--- a/lighthouse-core/audits/performance-budget.js
+++ b/lighthouse-core/audits/performance-budget.js
@@ -120,7 +120,8 @@ class ResourceBudget extends Audit {
    */
   static async audit(artifacts, context) {
     const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
-    const summary = await ResourceSummary.request({devtoolsLog, URL: artifacts.URL}, context);
+    const data = {devtoolsLog, URL: artifacts.URL, budgets: context.settings.budgets};
+    const summary = await ResourceSummary.request(data, context);
     const mainResource = await MainResource.request({URL: artifacts.URL, devtoolsLog}, context);
     const budget = Budget.getMatchingBudget(context.settings.budgets, mainResource.url);
 
diff --git a/lighthouse-core/audits/resource-summary.js b/lighthouse-core/audits/resource-summary.js
index e41da0885..5646b9fe1 100644
--- a/lighthouse-core/audits/resource-summary.js
+++ b/lighthouse-core/audits/resource-summary.js
@@ -45,7 +45,7 @@ class ResourceSummary extends Audit {
   static async audit(artifacts, context) {
     const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
     const summary = await ComputedResourceSummary
-      .request({devtoolsLog, URL: artifacts.URL}, context);
+      .request({devtoolsLog, URL: artifacts.URL, budgets: context.settings.budgets}, context);
 
     /** @type {LH.Audit.Details.Table['headings']} */
     const headings = [
diff --git a/lighthouse-core/audits/timing-budget.js b/lighthouse-core/audits/timing-budget.js
index 950c648d5..92e416ecb 100644
--- a/lighthouse-core/audits/timing-budget.js
+++ b/lighthouse-core/audits/timing-budget.js
@@ -143,7 +143,8 @@ class TimingBudget extends Audit {
     const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
     const trace = artifacts.traces[Audit.DEFAULT_PASS];
     const mainResource = await MainResource.request({URL: artifacts.URL, devtoolsLog}, context);
-    const summary = (await TimingSummary.request({trace, devtoolsLog}, context)).metrics;
+    const data = {trace, devtoolsLog, settings: context.settings};
+    const summary = (await TimingSummary.request(data, context)).metrics;
     const budget = Budget.getMatchingBudget(context.settings.budgets, mainResource.url);
 
     if (!budget) {
diff --git a/lighthouse-core/computed/computed-artifact.js b/lighthouse-core/computed/computed-artifact.js
index 268fee9d7..595ae93ce 100644
--- a/lighthouse-core/computed/computed-artifact.js
+++ b/lighthouse-core/computed/computed-artifact.js
@@ -11,7 +11,7 @@ const log = require('lighthouse-logger');
 /**
  * Decorate computableArtifact with a caching `request()` method which will
  * automatically call `computableArtifact.compute_()` under the hood.
- * @template {{name: string, compute_(artifacts: unknown, context: LH.Audit.Context): Promise<unknown>}} C
+ * @template {{name: string, compute_(artifacts: unknown, context: LH.Artifacts.ComputedContext): Promise<unknown>}} C
  * @param {C} computableArtifact
  */
 function makeComputedArtifact(computableArtifact) {
@@ -21,7 +21,7 @@ function makeComputedArtifact(computableArtifact) {
   /**
    * Return an automatically cached result from the computed artifact.
    * @param {FirstParamType<C['compute_']>} artifacts
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {ReturnType<C['compute_']>}
    */
   const request = (artifacts, context) => {
diff --git a/lighthouse-core/computed/critical-request-chains.js b/lighthouse-core/computed/critical-request-chains.js
index 3d53c33f9..667097060 100644
--- a/lighthouse-core/computed/critical-request-chains.js
+++ b/lighthouse-core/computed/critical-request-chains.js
@@ -125,7 +125,7 @@ class CriticalRequestChains {
 
   /**
    * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, trace: LH.Trace}} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.CriticalRequestNode>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/layout-shift-variants.js b/lighthouse-core/computed/layout-shift-variants.js
index ceafc060c..fca642ad8 100644
--- a/lighthouse-core/computed/layout-shift-variants.js
+++ b/lighthouse-core/computed/layout-shift-variants.js
@@ -140,7 +140,7 @@ class LayoutShiftVariants {
 
   /**
    * @param {LH.Trace} trace
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<{avgSessionGap5s: number, maxSessionGap1s: number, maxSessionGap1sLimit5s: number, maxSliding1s: number, maxSliding300ms: number}>}
    */
   static async compute_(trace, context) {
diff --git a/lighthouse-core/computed/load-simulator.js b/lighthouse-core/computed/load-simulator.js
index af34f9d1d..21d5c3c72 100644
--- a/lighthouse-core/computed/load-simulator.js
+++ b/lighthouse-core/computed/load-simulator.js
@@ -13,7 +13,7 @@ const NetworkAnalysis = require('./network-analysis.js');
 class LoadSimulator {
   /**
    * @param {{devtoolsLog: LH.DevtoolsLog, settings: Immutable<LH.Config.Settings>}} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<Simulator>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/main-resource.js b/lighthouse-core/computed/main-resource.js
index 4a432328c..d4579fb03 100644
--- a/lighthouse-core/computed/main-resource.js
+++ b/lighthouse-core/computed/main-resource.js
@@ -16,7 +16,7 @@ const NetworkRecords = require('./network-records.js');
 class MainResource {
   /**
    * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog}} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.NetworkRequest>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/main-thread-tasks.js b/lighthouse-core/computed/main-thread-tasks.js
index dfda2d057..4f27ae7d9 100644
--- a/lighthouse-core/computed/main-thread-tasks.js
+++ b/lighthouse-core/computed/main-thread-tasks.js
@@ -12,7 +12,7 @@ const TraceOfTab = require('./trace-of-tab.js');
 class MainThreadTasks {
   /**
    * @param {LH.Trace} trace
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<Array<LH.Artifacts.TaskNode>>}
    */
   static async compute_(trace, context) {
diff --git a/lighthouse-core/computed/metrics/cumulative-layout-shift-all-frames.js b/lighthouse-core/computed/metrics/cumulative-layout-shift-all-frames.js
index 15e3046b4..af0042135 100644
--- a/lighthouse-core/computed/metrics/cumulative-layout-shift-all-frames.js
+++ b/lighthouse-core/computed/metrics/cumulative-layout-shift-all-frames.js
@@ -15,7 +15,7 @@ const LayoutShiftVariants = require('../layout-shift-variants.js');
 class CumulativeLayoutShiftAllFrames {
   /**
    * @param {LH.Trace} trace
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<{value: number}>}
    */
   static async compute_(trace, context) {
diff --git a/lighthouse-core/computed/metrics/cumulative-layout-shift.js b/lighthouse-core/computed/metrics/cumulative-layout-shift.js
index ff830c5b2..ce9ddcd0e 100644
--- a/lighthouse-core/computed/metrics/cumulative-layout-shift.js
+++ b/lighthouse-core/computed/metrics/cumulative-layout-shift.js
@@ -12,7 +12,7 @@ const LayoutShiftVariants = require('../layout-shift-variants.js');
 class CumulativeLayoutShift {
   /**
    * @param {LH.Trace} trace
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<{value: number, debugInfo: Record<string,boolean> | null}>}
    */
   static async compute_(trace, context) {
diff --git a/lighthouse-core/computed/metrics/estimated-input-latency.js b/lighthouse-core/computed/metrics/estimated-input-latency.js
index 809f017d7..d38a0d76c 100644
--- a/lighthouse-core/computed/metrics/estimated-input-latency.js
+++ b/lighthouse-core/computed/metrics/estimated-input-latency.js
@@ -43,7 +43,7 @@ class EstimatedInputLatency extends ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/first-contentful-paint.js b/lighthouse-core/computed/metrics/first-contentful-paint.js
index 64efd5611..1b0346837 100644
--- a/lighthouse-core/computed/metrics/first-contentful-paint.js
+++ b/lighthouse-core/computed/metrics/first-contentful-paint.js
@@ -12,7 +12,7 @@ const LanternFirstContentfulPaint = require('./lantern-first-contentful-paint.js
 class FirstContentfulPaint extends ComputedMetric {
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/first-cpu-idle.js b/lighthouse-core/computed/metrics/first-cpu-idle.js
index a8e78f077..236d3fb30 100644
--- a/lighthouse-core/computed/metrics/first-cpu-idle.js
+++ b/lighthouse-core/computed/metrics/first-cpu-idle.js
@@ -167,7 +167,7 @@ class FirstCPUIdle extends ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/first-meaningful-paint.js b/lighthouse-core/computed/metrics/first-meaningful-paint.js
index ee9a5b97b..d952979ab 100644
--- a/lighthouse-core/computed/metrics/first-meaningful-paint.js
+++ b/lighthouse-core/computed/metrics/first-meaningful-paint.js
@@ -13,7 +13,7 @@ const LanternFirstMeaningfulPaint = require('./lantern-first-meaningful-paint.js
 class FirstMeaningfulPaint extends ComputedMetric {
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/interactive.js b/lighthouse-core/computed/metrics/interactive.js
index 89b5df3ad..414888013 100644
--- a/lighthouse-core/computed/metrics/interactive.js
+++ b/lighthouse-core/computed/metrics/interactive.js
@@ -142,7 +142,7 @@ class Interactive extends ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-estimated-input-latency.js b/lighthouse-core/computed/metrics/lantern-estimated-input-latency.js
index 95d746231..7bfdcec81 100644
--- a/lighthouse-core/computed/metrics/lantern-estimated-input-latency.js
+++ b/lighthouse-core/computed/metrics/lantern-estimated-input-latency.js
@@ -70,7 +70,7 @@ class LanternEstimatedInputLatency extends LanternMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-first-cpu-idle.js b/lighthouse-core/computed/metrics/lantern-first-cpu-idle.js
index b228118ff..60fd60c47 100644
--- a/lighthouse-core/computed/metrics/lantern-first-cpu-idle.js
+++ b/lighthouse-core/computed/metrics/lantern-first-cpu-idle.js
@@ -60,7 +60,7 @@ class LanternFirstCPUIdle extends LanternInteractive {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js b/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js
index 7edbe1b96..1abaa8dd5 100644
--- a/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js
+++ b/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js
@@ -66,7 +66,7 @@ class LanternFirstMeaningfulPaint extends LanternMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-interactive.js b/lighthouse-core/computed/metrics/lantern-interactive.js
index aa311ef6d..326c8a35c 100644
--- a/lighthouse-core/computed/metrics/lantern-interactive.js
+++ b/lighthouse-core/computed/metrics/lantern-interactive.js
@@ -82,7 +82,7 @@ class LanternInteractive extends LanternMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js b/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js
index 3ad61c0a6..d0b3d529f 100644
--- a/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js
+++ b/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js
@@ -94,7 +94,7 @@ class LanternLargestContentfulPaint extends LanternMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-max-potential-fid.js b/lighthouse-core/computed/metrics/lantern-max-potential-fid.js
index df9fc1c4a..6940dcbd6 100644
--- a/lighthouse-core/computed/metrics/lantern-max-potential-fid.js
+++ b/lighthouse-core/computed/metrics/lantern-max-potential-fid.js
@@ -67,7 +67,7 @@ class LanternMaxPotentialFID extends LanternMetricArtifact {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-metric.js b/lighthouse-core/computed/metrics/lantern-metric.js
index c2e0a3f6e..a1ee6f470 100644
--- a/lighthouse-core/computed/metrics/lantern-metric.js
+++ b/lighthouse-core/computed/metrics/lantern-metric.js
@@ -93,7 +93,7 @@ class LanternMetricArtifact {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @param {Omit<Extras, 'optimistic'>=} extras
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
@@ -148,7 +148,7 @@ class LanternMetricArtifact {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-speed-index.js b/lighthouse-core/computed/metrics/lantern-speed-index.js
index f010dfbf7..10e1c4629 100644
--- a/lighthouse-core/computed/metrics/lantern-speed-index.js
+++ b/lighthouse-core/computed/metrics/lantern-speed-index.js
@@ -93,7 +93,7 @@ class LanternSpeedIndex extends LanternMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/lantern-total-blocking-time.js b/lighthouse-core/computed/metrics/lantern-total-blocking-time.js
index a7b9d2427..082625327 100644
--- a/lighthouse-core/computed/metrics/lantern-total-blocking-time.js
+++ b/lighthouse-core/computed/metrics/lantern-total-blocking-time.js
@@ -87,7 +87,7 @@ class LanternTotalBlockingTime extends LanternMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/largest-contentful-paint.js b/lighthouse-core/computed/metrics/largest-contentful-paint.js
index 3b3bd4558..a8162ad39 100644
--- a/lighthouse-core/computed/metrics/largest-contentful-paint.js
+++ b/lighthouse-core/computed/metrics/largest-contentful-paint.js
@@ -21,7 +21,7 @@ const LanternLargestContentfulPaint = require('./lantern-largest-contentful-pain
 class LargestContentfulPaint extends ComputedMetric {
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/max-potential-fid.js b/lighthouse-core/computed/metrics/max-potential-fid.js
index dc65bc583..efb31af40 100644
--- a/lighthouse-core/computed/metrics/max-potential-fid.js
+++ b/lighthouse-core/computed/metrics/max-potential-fid.js
@@ -14,7 +14,7 @@ const TracingProcessor = require('../../lib/tracehouse/trace-processor.js');
 class MaxPotentialFID extends MetricArtifact {
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/metric.js b/lighthouse-core/computed/metrics/metric.js
index f0ff896e9..153663fda 100644
--- a/lighthouse-core/computed/metrics/metric.js
+++ b/lighthouse-core/computed/metrics/metric.js
@@ -23,7 +23,7 @@ class ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) { // eslint-disable-line no-unused-vars
@@ -32,7 +32,7 @@ class ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.Metric>}
    */
   static computeObservedMetric(data, context) { // eslint-disable-line no-unused-vars
@@ -41,7 +41,7 @@ class ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationDataInput} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric|LH.Artifacts.Metric>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/metrics/speed-index.js b/lighthouse-core/computed/metrics/speed-index.js
index 2003e25b3..67dc97b1d 100644
--- a/lighthouse-core/computed/metrics/speed-index.js
+++ b/lighthouse-core/computed/metrics/speed-index.js
@@ -13,7 +13,7 @@ const Speedline = require('../speedline.js');
 class SpeedIndex extends ComputedMetric {
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
@@ -22,7 +22,7 @@ class SpeedIndex extends ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.Metric>}
    */
   static async computeObservedMetric(data, context) {
diff --git a/lighthouse-core/computed/metrics/timing-summary.js b/lighthouse-core/computed/metrics/timing-summary.js
index 3e90253e2..e8ab0b2a2 100644
--- a/lighthouse-core/computed/metrics/timing-summary.js
+++ b/lighthouse-core/computed/metrics/timing-summary.js
@@ -27,15 +27,16 @@ class TimingSummary {
   /**
      * @param {LH.Trace} trace
      * @param {LH.DevtoolsLog} devtoolsLog
-     * @param {LH.Audit.Context} context
+     * @param {ImmutableObject<LH.Config.Settings>} settings
+     * @param {LH.Artifacts.ComputedContext} context
      * @return {Promise<{metrics: LH.Artifacts.TimingSummary, debugInfo: Record<string,boolean>}>}
      */
-  static async summarize(trace, devtoolsLog, context) {
-    const metricComputationData = {trace, devtoolsLog, settings: context.settings};
+  static async summarize(trace, devtoolsLog, settings, context) {
+    const metricComputationData = {trace, devtoolsLog, settings};
     /**
      * @template TArtifacts
      * @template TReturn
-     * @param {{request: (artifact: TArtifacts, context: LH.Audit.Context) => Promise<TReturn>}} Artifact
+     * @param {{request: (artifact: TArtifacts, context: LH.Artifacts.ComputedContext) => Promise<TReturn>}} Artifact
      * @param {TArtifacts} artifact
      * @return {Promise<TReturn|undefined>}
      */
@@ -141,12 +142,12 @@ class TimingSummary {
     return {metrics, debugInfo};
   }
   /**
-   * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
-   * @param {LH.Audit.Context} context
+   * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog, settings: ImmutableObject<LH.Config.Settings>}} data
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<{metrics: LH.Artifacts.TimingSummary, debugInfo: Record<string,boolean>}>}
    */
   static async compute_(data, context) {
-    return TimingSummary.summarize(data.trace, data.devtoolsLog, context);
+    return TimingSummary.summarize(data.trace, data.devtoolsLog, data.settings, context);
   }
 }
 
diff --git a/lighthouse-core/computed/metrics/total-blocking-time.js b/lighthouse-core/computed/metrics/total-blocking-time.js
index d55318eb7..989272478 100644
--- a/lighthouse-core/computed/metrics/total-blocking-time.js
+++ b/lighthouse-core/computed/metrics/total-blocking-time.js
@@ -79,7 +79,7 @@ class TotalBlockingTime extends ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.LanternMetric>}
    */
   static computeSimulatedMetric(data, context) {
@@ -88,7 +88,7 @@ class TotalBlockingTime extends ComputedMetric {
 
   /**
    * @param {LH.Artifacts.MetricComputationData} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.Metric>}
    */
   static async computeObservedMetric(data, context) {
diff --git a/lighthouse-core/computed/module-duplication.js b/lighthouse-core/computed/module-duplication.js
index 60bca2adb..ce36daa62 100644
--- a/lighthouse-core/computed/module-duplication.js
+++ b/lighthouse-core/computed/module-duplication.js
@@ -75,7 +75,7 @@ class ModuleDuplication {
 
   /**
    * @param {LH.Artifacts} artifacts
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    */
   static async compute_(artifacts, context) {
     const bundles = await JsBundles.request(artifacts, context);
diff --git a/lighthouse-core/computed/network-analysis.js b/lighthouse-core/computed/network-analysis.js
index f3b733db7..8c4d21bb0 100644
--- a/lighthouse-core/computed/network-analysis.js
+++ b/lighthouse-core/computed/network-analysis.js
@@ -50,7 +50,7 @@ class NetworkAnalysis {
 
   /**
    * @param {LH.DevtoolsLog} devtoolsLog
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.NetworkAnalysis>}
    */
   static async compute_(devtoolsLog, context) {
diff --git a/lighthouse-core/computed/page-dependency-graph.js b/lighthouse-core/computed/page-dependency-graph.js
index ecae3d832..74dffc38f 100644
--- a/lighthouse-core/computed/page-dependency-graph.js
+++ b/lighthouse-core/computed/page-dependency-graph.js
@@ -452,7 +452,7 @@ class PageDependencyGraph {
 
   /**
    * @param {{trace: LH.Trace, devtoolsLog: LH.DevtoolsLog}} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<Node>}
    */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/resource-summary.js b/lighthouse-core/computed/resource-summary.js
index bc3536ce8..da9e0774e 100644
--- a/lighthouse-core/computed/resource-summary.js
+++ b/lighthouse-core/computed/resource-summary.js
@@ -37,10 +37,10 @@ class ResourceSummary {
   /**
    * @param {Array<LH.Artifacts.NetworkRequest>} networkRecords
    * @param {string} mainResourceURL
-   * @param {LH.Audit.Context} context
+   * @param {ImmutableObject<LH.Budget[]|null>} budgets
    * @return {Record<LH.Budget.ResourceType, ResourceEntry>}
    */
-  static summarize(networkRecords, mainResourceURL, context) {
+  static summarize(networkRecords, mainResourceURL, budgets) {
     /** @type {Record<LH.Budget.ResourceType, ResourceEntry>} */
     const resourceSummary = {
       'stylesheet': {count: 0, resourceSize: 0, transferSize: 0},
@@ -53,7 +53,7 @@ class ResourceSummary {
       'total': {count: 0, resourceSize: 0, transferSize: 0},
       'third-party': {count: 0, resourceSize: 0, transferSize: 0},
     };
-    const budget = Budget.getMatchingBudget(context.settings.budgets, mainResourceURL);
+    const budget = Budget.getMatchingBudget(budgets, mainResourceURL);
     /** @type {ReadonlyArray<string>} */
     let firstPartyHosts = [];
     if (budget && budget.options && budget.options.firstPartyHostnames) {
@@ -102,16 +102,16 @@ class ResourceSummary {
   }
 
   /**
-   * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog}} data
-   * @param {LH.Audit.Context} context
+   * @param {{URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog, budgets: ImmutableObject<LH.Budget[]|null>}} data
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<Record<LH.Budget.ResourceType,ResourceEntry>>}
    */
   static async compute_(data, context) {
     const [networkRecords, mainResource] = await Promise.all([
       NetworkRecords.request(data.devtoolsLog, context),
-      MainResource.request(data, context),
+      MainResource.request({devtoolsLog: data.devtoolsLog, URL: data.URL}, context),
     ]);
-    return ResourceSummary.summarize(networkRecords, mainResource.url, context);
+    return ResourceSummary.summarize(networkRecords, mainResource.url, data.budgets);
   }
 }
 
diff --git a/lighthouse-core/computed/speedline.js b/lighthouse-core/computed/speedline.js
index aac3f855a..48c74ae53 100644
--- a/lighthouse-core/computed/speedline.js
+++ b/lighthouse-core/computed/speedline.js
@@ -13,7 +13,7 @@ const TraceOfTab = require('./trace-of-tab.js');
 class Speedline {
   /**
    * @param {LH.Trace} trace
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Artifacts.Speedline>}
    */
   static async compute_(trace, context) {
diff --git a/lighthouse-core/computed/unused-css.js b/lighthouse-core/computed/unused-css.js
index 5ac250903..847446a79 100644
--- a/lighthouse-core/computed/unused-css.js
+++ b/lighthouse-core/computed/unused-css.js
@@ -136,7 +136,7 @@ class UnusedCSS {
 
   /**
    * @param {{CSSUsage: LH.Artifacts['CSSUsage'], URL: LH.Artifacts['URL'], devtoolsLog: LH.DevtoolsLog}} data
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<LH.Audit.ByteEfficiencyItem[]>}
   */
   static async compute_(data, context) {
diff --git a/lighthouse-core/computed/user-timings.js b/lighthouse-core/computed/user-timings.js
index 78c75cb76..7ad702ea3 100644
--- a/lighthouse-core/computed/user-timings.js
+++ b/lighthouse-core/computed/user-timings.js
@@ -14,7 +14,7 @@ const TraceOfTab = require('./trace-of-tab.js');
 class UserTimings {
   /**
    * @param {LH.Trace} trace
-   * @param {LH.Audit.Context} context
+   * @param {LH.Artifacts.ComputedContext} context
    * @return {Promise<Array<MarkEvent|MeasureEvent>>}
    */
   static async compute_(trace, context) {
diff --git a/lighthouse-core/gather/fetcher.js b/lighthouse-core/gather/fetcher.js
index 87fb7784b..51eaa023a 100644
--- a/lighthouse-core/gather/fetcher.js
+++ b/lighthouse-core/gather/fetcher.js
@@ -13,6 +13,8 @@
 
 /* global document */
 
+/** @typedef {{content: string|null, status: number|null}} FetchResponse */
+
 const log = require('lighthouse-logger');
 const {getBrowserVersion} = require('./driver/environment.js');
 
@@ -97,7 +99,7 @@ class Fetcher {
    *
    * @param {string} url
    * @param {{timeout: number}=} options timeout is in ms
-   * @return {Promise<string>}
+   * @return {Promise<FetchResponse>}
    */
   async fetchResource(url, options = {timeout: 500}) {
     if (!this._enabled) {
@@ -107,7 +109,7 @@ class Fetcher {
     // `Network.loadNetworkResource` was introduced in M88.
     // The long timeout bug with `IO.read` was fixed in M92:
     // https://bugs.chromium.org/p/chromium/issues/detail?id=1191757
-    const milestone = await getBrowserVersion(this.session).then(v => v.milestone);
+    const {milestone} = await getBrowserVersion(this.session);
     if (milestone >= 92) {
       return await this._fetchResourceOverProtocol(url, options);
     }
@@ -141,7 +143,7 @@ class Fetcher {
 
   /**
    * @param {string} url
-   * @return {Promise<LH.Crdp.IO.StreamHandle>}
+   * @return {Promise<{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}>}
    */
   async _loadNetworkResource(url) {
     await this.session.sendCommand('Network.enable');
@@ -156,18 +158,28 @@ class Fetcher {
     });
     await this.session.sendCommand('Network.disable');
 
-    if (!networkResponse.resource.success || !networkResponse.resource.stream) {
-      const statusCode = networkResponse.resource.httpStatusCode || '';
-      throw new Error(`Loading network resource failed (${statusCode})`);
-    }
+    return {
+      stream: networkResponse.resource.success ? (networkResponse.resource.stream || null) : null,
+      status: networkResponse.resource.httpStatusCode || null,
+    };
+  }
 
-    return networkResponse.resource.stream;
+  /**
+   * @param {string} requestId
+   * @return {Promise<string>}
+   */
+  async _resolveResponseBody(requestId) {
+    const responseBody = await this.session.sendCommand('Fetch.getResponseBody', {requestId});
+    if (responseBody.base64Encoded) {
+      return Buffer.from(responseBody.body, 'base64').toString();
+    }
+    return responseBody.body;
   }
 
   /**
    * @param {string} url
    * @param {{timeout: number}} options timeout is in ms
-   * @return {Promise<string>}
+   * @return {Promise<FetchResponse>}
    */
   async _fetchResourceOverProtocol(url, options) {
     const startTime = Date.now();
@@ -180,21 +192,28 @@ class Fetcher {
       }, options.timeout);
     });
 
-    const fetchStreamPromise = this._loadNetworkResource(url);
-    const stream = await Promise.race([fetchStreamPromise, timeoutPromise])
+    const responsePromise = this._loadNetworkResource(url);
+
+    /** @type {{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}} */
+    const response = await Promise.race([responsePromise, timeoutPromise])
       .finally(() => clearTimeout(timeoutHandle));
 
-    return await this._readIOStream(stream, {timeout: options.timeout - (Date.now() - startTime)});
+    const isOk = response.status && response.status >= 200 && response.status <= 299;
+    if (!response.stream || !isOk) return {status: response.status, content: null};
+
+    const timeout = options.timeout - (Date.now() - startTime);
+    const content = await this._readIOStream(response.stream, {timeout});
+    return {status: response.status, content};
   }
 
   /**
    * Fetches resource by injecting an iframe into the page.
    * @param {string} url
    * @param {{timeout: number}} options timeout is in ms
-   * @return {Promise<string>}
+   * @return {Promise<FetchResponse>}
    */
   async _fetchResourceIframe(url, options) {
-    /** @type {Promise<string>} */
+    /** @type {Promise<FetchResponse>} */
     const requestInterceptionPromise = new Promise((resolve, reject) => {
       /** @param {LH.Crdp.Fetch.RequestPausedEvent} event */
       const handlerAsync = async event => {
@@ -216,17 +235,13 @@ class Fetcher {
           return;
         }
 
-        // Now in the response stage, but the request failed.
-        if (!(responseStatusCode >= 200 && responseStatusCode < 300)) {
-          reject(new Error(`Invalid response status code: ${responseStatusCode}`));
-          return;
-        }
-
-        const responseBody = await this.session.sendCommand('Fetch.getResponseBody', {requestId});
-        if (responseBody.base64Encoded) {
-          resolve(Buffer.from(responseBody.body, 'base64').toString());
+        if (responseStatusCode >= 200 && responseStatusCode <= 299) {
+          resolve({
+            status: responseStatusCode,
+            content: await this._resolveResponseBody(requestId),
+          });
         } else {
-          resolve(responseBody.body);
+          resolve({status: responseStatusCode, content: null});
         }
 
         // Fail the request (from the page's perspective) so that the iframe never loads.
diff --git a/lighthouse-core/gather/gatherers/seo/robots-txt.js b/lighthouse-core/gather/gatherers/seo/robots-txt.js
index 1490506bd..c6958a831 100644
--- a/lighthouse-core/gather/gatherers/seo/robots-txt.js
+++ b/lighthouse-core/gather/gatherers/seo/robots-txt.js
@@ -6,6 +6,7 @@
 'use strict';
 
 const FRGatherer = require('../../../fraggle-rock/gather/base-gatherer.js');
+const {getBrowserVersion} = require('../../driver/environment.js');
 
 /* global fetch, location */
 
@@ -36,11 +37,23 @@ class RobotsTxt extends FRGatherer {
    * @param {LH.Gatherer.FRTransitionalContext} passContext
    * @return {Promise<LH.Artifacts['RobotsTxt']>}
    */
-  getArtifact(passContext) {
-    return passContext.driver.executionContext.evaluate(getRobotsTxtContent, {
-      args: [],
-      useIsolation: true,
-    });
+  async getArtifact(passContext) {
+    const {milestone} = await getBrowserVersion(passContext.driver.defaultSession);
+
+    // TODO: Remove when 92 hits stable.
+    // Iframe fetcher still has issues with CSPs.
+    // Only use the fetcher if we are fetching over the CDP.
+    if (milestone < 92) {
+      return passContext.driver.executionContext.evaluate(getRobotsTxtContent, {
+        args: [],
+        useIsolation: true,
+      });
+    }
+
+    const robotsUrl = new URL('/robots.txt', passContext.url).href;
+    await passContext.driver.fetcher.enable();
+    return passContext.driver.fetcher.fetchResource(robotsUrl)
+      .catch(() => ({status: null, content: null}));
   }
 }
 
diff --git a/lighthouse-core/gather/gatherers/source-maps.js b/lighthouse-core/gather/gatherers/source-maps.js
index 3c1566f16..0b9d81d20 100644
--- a/lighthouse-core/gather/gatherers/source-maps.js
+++ b/lighthouse-core/gather/gatherers/source-maps.js
@@ -27,9 +27,11 @@ class SourceMaps extends Gatherer {
    * @return {Promise<LH.Artifacts.RawSourceMap>}
    */
   async fetchSourceMap(driver, sourceMapUrl) {
-    /** @type {string} */
-    const sourceMapJson = await driver.fetcher.fetchResource(sourceMapUrl, {timeout: 1500});
-    return JSON.parse(sourceMapJson);
+    const response = await driver.fetcher.fetchResource(sourceMapUrl, {timeout: 1500});
+    if (response.content === null) {
+      throw new Error(`Failed fetching source map (${response.status})`);
+    }
+    return JSON.parse(response.content);
   }
 
   /**
diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts
index 073da753f..8275fd809 100644
--- a/types/artifacts.d.ts
+++ b/types/artifacts.d.ts
@@ -9,6 +9,7 @@ import _LanternSimulator = require('../lighthouse-core/lib/dependency-graph/simu
 import _NetworkRequest = require('../lighthouse-core/lib/network-request.js');
 import speedline = require('speedline-core');
 import TextSourceMap = require('../lighthouse-core/lib/cdt/generated/SourceMap.js');
+import ArbitraryEqualityMap = require('../lighthouse-core/lib/arbitrary-equality-map.js');
 
 type _TaskNode = import('../lighthouse-core/lib/tracehouse/main-thread-tasks.js').TaskNode;
 
@@ -189,6 +190,10 @@ declare global {
     }
 
     module Artifacts {
+      export type ComputedContext = Immutable<{
+        computedCache: Map<string, ArbitraryEqualityMap>;
+      }>;
+
       export type NetworkRequest = _NetworkRequest;
       export type TaskNode = _TaskNode;
       export type MetaElement = LH.Artifacts['MetaElements'][0];


Test patch:
diff --git a/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js b/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js
index b19357d29..64ff9c726 100644
--- a/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js
+++ b/lighthouse-cli/test/smokehouse/frontends/smokehouse-bin.js
@@ -19,7 +19,7 @@ const log = require('lighthouse-logger');
 const {runSmokehouse} = require('../smokehouse.js');
 const {server, serverForOffline} = require('../../fixtures/static-server.js');
 
-const coreTestDefnsPath = require.resolve('../test-definitions/core-tests.js');
+const coreTestDefnsPath = `${__dirname}/../test-definitions/core-tests.js`;
 
 /**
  * Possible Lighthouse runners. Loaded dynamically so e.g. a CLI run isn't
diff --git a/lighthouse-cli/test/smokehouse/test-definitions/csp/csp-expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/csp/csp-expectations.js
index 51b4fdad0..2ee560786 100644
--- a/lighthouse-cli/test/smokehouse/test-definitions/csp/csp-expectations.js
+++ b/lighthouse-cli/test/smokehouse/test-definitions/csp/csp-expectations.js
@@ -47,6 +47,7 @@ module.exports = [
   },
   {
     artifacts: {
+      _maxChromiumMilestone: 91,
       RobotsTxt: {
         status: null,
         content: null,
@@ -54,8 +55,9 @@ module.exports = [
       InspectorIssues: {
         contentSecurityPolicy: [
           {
-            // TODO: Fix connect-src violation when fetching robots.txt.
             // https://github.com/GoogleChrome/lighthouse/issues/10225
+            //
+            // Fixed with new fetcher using M92.
             blockedURL: 'http://localhost:10200/robots.txt',
             violatedDirective: 'connect-src',
             isReportOnly: false,
@@ -75,7 +77,6 @@ module.exports = [
         // https://github.com/GoogleChrome/lighthouse/pull/12044#issuecomment-788274938
         //
         // Fixed with new fetcher using M92.
-        _maxChromiumMilestone: 91,
         sourceMapUrl: 'http://localhost:10200/source-map/script.js.map',
         errorMessage: 'Error: Timed out fetching resource.',
         map: undefined,
@@ -87,4 +88,35 @@ module.exports = [
       audits: {},
     },
   },
+  {
+    // Same CSP as above, but verifies correct behavior for M92.
+    artifacts: {
+      _minChromiumMilestone: 92,
+      RobotsTxt: {
+        status: 404,
+        content: null,
+      },
+      InspectorIssues: {
+        contentSecurityPolicy: [
+          {
+            // TODO: Fix style-src-elem violation when checking tap targets.
+            // https://github.com/GoogleChrome/lighthouse/issues/11862
+            violatedDirective: 'style-src-elem',
+            isReportOnly: false,
+            contentSecurityPolicyViolationType: 'kInlineViolation',
+          },
+        ],
+      },
+      SourceMaps: [{
+        sourceMapUrl: 'http://localhost:10200/source-map/script.js.map',
+        map: {},
+        errorMessage: undefined,
+      }],
+    },
+    lhr: {
+      requestedUrl: 'http://localhost:10200/csp.html?' + blockAllExceptInlineScriptCsp,
+      finalUrl: 'http://localhost:10200/csp.html?' + blockAllExceptInlineScriptCsp,
+      audits: {},
+    },
+  },
 ];
diff --git a/lighthouse-core/scripts/release/test.sh b/lighthouse-core/scripts/release/test.sh
index 2026e643a..8ae8b24ba 100755
--- a/lighthouse-core/scripts/release/test.sh
+++ b/lighthouse-core/scripts/release/test.sh
@@ -36,8 +36,10 @@ cd /tmp/lighthouse-local-test
 
 npm init -y
 npm install "$LH_PRISTINE_ROOT/lighthouse-$VERSION.tgz"
-cd node_modules/lighthouse/lighthouse-cli/test/ && npm install lodash.clonedeep && cd ../../../../
-npm explore lighthouse -- npm run smoke -- --retries=3
+npx add-dependencies package.json mime-types lodash.clonedeep
+npm install --only=prod
+cp -r "$LH_PRISTINE_ROOT/lighthouse-cli/test/fixtures" node_modules/lighthouse/lighthouse-cli/test
+npm explore lighthouse -- npm run smoke -- --tests-path "$LH_PRISTINE_ROOT/lighthouse-cli/test/smokehouse/test-definitions/core-tests.js" --retries=3
 npm explore lighthouse -- npm run fast -- http://example.com
 
 cd "$LH_PRISTINE_ROOT"
diff --git a/lighthouse-core/test/chromium-web-tests/README.md b/lighthouse-core/test/chromium-web-tests/README.md
index 12a4e1ead..3a8b58338 100644
--- a/lighthouse-core/test/chromium-web-tests/README.md
+++ b/lighthouse-core/test/chromium-web-tests/README.md
@@ -14,6 +14,22 @@ yarn test-devtools
 yarn update:test-devtools
 ```
 
+### Prerequistes
+
+<details>
+  <summary> Install `requests`</summary>
+  
+  Ensure you have `requests` module available globally on your python 2.7 install. New Macs do not come with pip for python 2.7 which is deprecated, so you might have to install that too.
+  
+  
+  ```sh
+  curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py
+  python get-pip.py
+  pip -m pip install requests
+  ```
+  
+</details>
+
 ### Debugging
 
 * Want logs from Lighthouse? Add `log.log('status', '**** hello test output ' + JSON.stringify({obj}));` which will be visible in the `lighthouse-successful-run.js` output thanks to `LighthouseTestRunner.addStatusListener`.
diff --git a/lighthouse-core/test/computed/metrics/timing-summary-test.js b/lighthouse-core/test/computed/metrics/timing-summary-test.js
index d0bc696a8..481d38745 100644
--- a/lighthouse-core/test/computed/metrics/timing-summary-test.js
+++ b/lighthouse-core/test/computed/metrics/timing-summary-test.js
@@ -13,8 +13,9 @@ const devtoolsLog = require('../../fixtures/traces/frame-metrics-m90.devtools.lo
 /* eslint-env jest */
 describe('Timing summary', () => {
   it('contains the correct data', async () => {
-    const context = {settings: {throttlingMethod: 'devtools'}, computedCache: new Map()};
-    const result = await TimingSummary.request({trace, devtoolsLog}, context);
+    const artifacts = {settings: {throttlingMethod: 'devtools'}, trace, devtoolsLog};
+    const context = {computedCache: new Map()};
+    const result = await TimingSummary.request(artifacts, context);
 
     expect(result.metrics).toMatchInlineSnapshot(`
       Object {
diff --git a/lighthouse-core/test/computed/resource-summary-test.js b/lighthouse-core/test/computed/resource-summary-test.js
index ec17a1643..48e2eee2b 100644
--- a/lighthouse-core/test/computed/resource-summary-test.js
+++ b/lighthouse-core/test/computed/resource-summary-test.js
@@ -19,6 +19,7 @@ function mockArtifacts(networkRecords) {
   return {
     devtoolsLog: networkRecordsToDevtoolsLog(networkRecords),
     URL: {requestedUrl: networkRecords[0].url, finalUrl: networkRecords[0].url},
+    budgets: null,
   };
 }
 
@@ -32,7 +33,7 @@ describe('Resource summary computed', () => {
       {url: 'http://cdn.example.com/script.js', resourceType: 'Script', transferSize: 50},
       {url: 'http://third-party.com/file.jpg', resourceType: 'Image', transferSize: 70},
     ]);
-    context = {computedCache: new Map(), settings: {budgets: null}};
+    context = {computedCache: new Map()};
   });
 
   it('includes all resource types, regardless of whether page contains them', async () => {
@@ -64,7 +65,7 @@ describe('Resource summary computed', () => {
     }
 
     const result = ComputedResourceSummary.summarize(
-      networkRecords, networkRecords[0].url, context);
+      networkRecords, networkRecords[0].url, artifacts.budgets);
     assert.equal(result.other.count, 1);
     assert.equal(result.other.transferSize, 50);
   });
@@ -81,7 +82,7 @@ describe('Resource summary computed', () => {
   });
 
   it('ignores records with non-network protocols', async () => {
-    context.settings.budgets = [{
+    artifacts.budgets = [{
       path: '/',
       options: {
         firstPartyHostnames: ['example.com'],
@@ -117,7 +118,7 @@ describe('Resource summary computed', () => {
 
     describe('when firstPartyHostnames is not set', () => {
       it('the root domain and all subdomains are considered first-party', async () => {
-        context.settings.budgets = null;
+        artifacts.budgets = null;
         const result = await ComputedResourceSummary.request(artifacts, context);
         expect(result['third-party'].transferSize).toBe(25 + 50 + 70);
         expect(result['third-party'].count).toBe(3);
@@ -129,7 +130,7 @@ describe('Resource summary computed', () => {
           {url: 'http://es.shopping-mall.co.uk/file.html', resourceType: 'Script', transferSize: 7},
           {url: 'http://co.uk', resourceType: 'Script', transferSize: 10},
         ]);
-        context.settings.budgets = null;
+        artifacts.budgets = null;
         const result = await ComputedResourceSummary.request(artifacts, context);
         expect(result['third-party'].transferSize).toBe(10);
         expect(result['third-party'].count).toBe(1);
@@ -140,7 +141,7 @@ describe('Resource summary computed', () => {
       const allResourcesSize = 30 + 10 + 25 + 50 + 70;
       const allResourcesCount = 5;
       it('handles subdomain hostnames correctly', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             firstPartyHostnames: ['cdn.example.com'],
@@ -152,7 +153,7 @@ describe('Resource summary computed', () => {
       });
 
       it('handles wildcard expressions correctly', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             // Matches example.com and cdn.example.com
@@ -165,7 +166,7 @@ describe('Resource summary computed', () => {
       });
 
       it('handles root domain hostname correctly', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             // Matches example.com; does not match cdn.example.com
@@ -178,7 +179,7 @@ describe('Resource summary computed', () => {
       });
 
       it('handles multiple hostnames correctly', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             firstPartyHostnames: ['example.com', 'my-cdn.com'],
@@ -190,7 +191,7 @@ describe('Resource summary computed', () => {
       });
 
       it('handles duplication of hostnames', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             firstPartyHostnames: ['my-cdn.com', 'my-cdn.com', 'my-cdn.com'],
@@ -202,7 +203,7 @@ describe('Resource summary computed', () => {
       });
 
       it('handles logical duplication of hostnames', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             firstPartyHostnames: ['example.com', '*.example.com', 'cdn.example.com'],
@@ -214,7 +215,7 @@ describe('Resource summary computed', () => {
       });
 
       it('handles using top-level domains as firstPartyHostnames correctly', async () => {
-        context.settings.budgets = [{
+        artifacts.budgets = [{
           path: '/',
           options: {
             firstPartyHostnames: ['*.com'],
diff --git a/lighthouse-core/test/gather/fetcher-test.js b/lighthouse-core/test/gather/fetcher-test.js
index 17960359d..139201c39 100644
--- a/lighthouse-core/test/gather/fetcher-test.js
+++ b/lighthouse-core/test/gather/fetcher-test.js
@@ -133,10 +133,10 @@ describe('._fetchResourceOverProtocol', () => {
       .mockResponse('Network.disable');
 
     const data = await fetcher._fetchResourceOverProtocol('https://example.com', {timeout: 500});
-    expect(data).toEqual(streamContents);
+    expect(data).toEqual({content: streamContents, status: 200});
   });
 
-  it('throws when resource could not be fetched', async () => {
+  it('returns null when resource could not be fetched', async () => {
     connectionStub.sendCommand = createMockSendCommandFn()
       .mockResponse('Network.enable')
       .mockResponse('Page.getFrameTree', {frameTree: {frame: {id: 'FRAME'}}})
@@ -145,8 +145,8 @@ describe('._fetchResourceOverProtocol', () => {
       })
       .mockResponse('Network.disable');
 
-    const dataPromise = fetcher._fetchResourceOverProtocol('https://example.com', {timeout: 500});
-    await expect(dataPromise).rejects.toThrowError(/Loading network resource failed/);
+    const data = await fetcher._fetchResourceOverProtocol('https://example.com', {timeout: 500});
+    expect(data).toEqual({content: null, status: 404});
   });
 
   it('throws on timeout', async () => {
diff --git a/lighthouse-core/test/gather/gatherers/source-maps-test.js b/lighthouse-core/test/gather/gatherers/source-maps-test.js
index 8ee4f4199..8f27f41d5 100644
--- a/lighthouse-core/test/gather/gatherers/source-maps-test.js
+++ b/lighthouse-core/test/gather/gatherers/source-maps-test.js
@@ -30,7 +30,7 @@ describe('SourceMaps gatherer', () => {
    * `resolvedSourceMapUrl` is used to assert that the SourceMaps gatherer is using the expected
    *                        url to fetch the source map.
    * `fetchError` mocks an error that happens in the page. Only fetch error message make sense.
-   * @param {Array<{scriptParsedEvent: LH.Crdp.Debugger.ScriptParsedEvent, map: string, resolvedSourceMapUrl?: string, fetchError: string}>} mapsAndEvents
+   * @param {Array<{scriptParsedEvent: LH.Crdp.Debugger.ScriptParsedEvent, map: string, status?: number, resolvedSourceMapUrl?: string, fetchError: string}>} mapsAndEvents
    * @return {Promise<LH.Artifacts['SourceMaps']>}
    */
   async function runSourceMaps(mapsAndEvents) {
@@ -50,7 +50,14 @@ describe('SourceMaps gatherer', () => {
       .mockResponse('Fetch.disable', {});
     const fetchMock = jest.fn();
 
-    for (const {scriptParsedEvent, map, resolvedSourceMapUrl, fetchError} of mapsAndEvents) {
+    for (const mapAndEvents of mapsAndEvents) {
+      const {
+        scriptParsedEvent,
+        map,
+        status = null,
+        resolvedSourceMapUrl,
+        fetchError,
+      } = mapAndEvents;
       onMock.mockEvent('protocolevent', {
         method: 'Debugger.scriptParsed',
         params: scriptParsedEvent,
@@ -71,7 +78,7 @@ describe('SourceMaps gatherer', () => {
           throw new Error(fetchError);
         }
 
-        return map;
+        return {content: map, status};
       });
     }
     const connectionStub = new Connection();
@@ -173,6 +180,28 @@ describe('SourceMaps gatherer', () => {
     ]);
   });
 
+  it('throws an error message when fetching map returns bad status code', async () => {
+    const mapsAndEvents = [
+      {
+        scriptParsedEvent: {
+          url: 'http://www.example.com/bundle.js',
+          sourceMapURL: 'http://www.example.com/bundle.js.map',
+        },
+        status: 404,
+        map: null,
+      },
+    ];
+    const artifact = await runSourceMaps(mapsAndEvents);
+    expect(artifact).toEqual([
+      {
+        scriptUrl: mapsAndEvents[0].scriptParsedEvent.url,
+        sourceMapUrl: mapsAndEvents[0].scriptParsedEvent.sourceMapURL,
+        errorMessage: 'Error: Failed fetching source map (404)',
+        map: undefined,
+      },
+    ]);
+  });
+
   it('generates an error message when fetching map fails', async () => {
     const mapsAndEvents = [
       {
diff --git a/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt b/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt
index b790273b8..ad9396418 100644
--- a/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt
+++ b/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt
@@ -112,6 +112,7 @@ Gathering: TagsBlockingFirstPaint
 Gathering: FontSize
 Gathering: EmbeddedContent
 Gathering: RobotsTxt
+Getting browser version
 Gathering: TapTargets
 Gathering: Accessibility
 Gathering: TraceElements