function collateResults(localConsole, actual, expected) {
  // If actual run had a runtimeError, expected *must* have a runtimeError.
  // Relies on the fact that an `undefined` argument to makeComparison() can only match `undefined`.
  const runtimeErrorAssertion = makeComparison('runtimeError', actual.lhr.runtimeError,
      expected.lhr.runtimeError);

  // Same for warnings, exclude the slow CPU warning which is flaky and differs between CI machines.
  const warnings = actual.lhr.runWarnings
    .filter(warning => !warning.includes('loaded too slowly'))
    .filter(warning => !warning.includes('a slower CPU'));
  const runWarningsAssertion = makeComparison('runWarnings', warnings,
      expected.lhr.runWarnings || []);

  /** @type {Comparison[]} */
  let artifactAssertions = [];
  if (expected.artifacts) {
    const expectedArtifacts = expected.artifacts;
    const artifactNames = /** @type {(keyof LH.Artifacts)[]} */ (Object.keys(expectedArtifacts));
    const actualArtifacts = actual.artifacts || {};
    artifactAssertions = artifactNames.map(artifactName => {
      if (!(artifactName in actualArtifacts)) {
        localConsole.log(log.redify('Error: ') +
          `Config run did not generate artifact ${artifactName}`);
      }

      const actualResult = actualArtifacts[artifactName];
      const expectedResult = expectedArtifacts[artifactName];
      return makeComparison(artifactName + ' artifact', actualResult, expectedResult);
    });
  }

  /** @type {Comparison[]} */
  let auditAssertions = [];
  auditAssertions = Object.keys(expected.lhr.audits).map(auditName => {
    const actualResult = actual.lhr.audits[auditName];
    if (!actualResult) {
      localConsole.log(log.redify('Error: ') +
        `Config did not trigger run of expected audit ${auditName}`);
    }

    const expectedResult = expected.lhr.audits[auditName];
    return makeComparison(auditName + ' audit', actualResult, expectedResult);
  });

  /** @type {Comparison[]} */
  const extraAssertions = [];

  if (expected.lhr.timing) {
    const comparison = makeComparison('timing', actual.lhr.timing, expected.lhr.timing);
    extraAssertions.push(comparison);
  }

  if (expected.networkRequests) {
    extraAssertions.push(makeComparison(
      'Requests',
      actual.networkRequests,
      expected.networkRequests
    ));
  }

  if (expected.lhr.fullPageScreenshot) {
    extraAssertions.push(makeComparison('fullPageScreenshot', actual.lhr.fullPageScreenshot,
      expected.lhr.fullPageScreenshot));
  }

  return [
    makeComparison('final url', actual.lhr.finalDisplayedUrl, expected.lhr.finalDisplayedUrl),
    runtimeErrorAssertion,
    runWarningsAssertion,
    ...artifactAssertions,
    ...auditAssertions,
    ...extraAssertions,
  ];
}
Collate results into comparisons of actual and expected scores on each audit/artifact.
@param {LocalConsole} localConsole
@param {{lhr: LH.Result, artifacts: LH.Artifacts, networkRequests?: string[]}} actual
@param {Smokehouse.ExpectedRunnerResult} expected
@return {Comparison[]}