function createLHR(pageUrl, auditDefs, prandom) {
  /** @type {LH.Result['audits']} */
  const audits = {};

  const loremIpsumTokens = LOREM_IPSUM.split(' ');
  for (const auditDef of auditDefs) {
    const {auditId, averageNumericValue, averageWastedMs, unit = 'items'} = auditDef;
    const groupId = getGroupForAuditId(auditId);
    if (typeof averageNumericValue === 'number') {
      const numericValue = generateNumericValue(averageNumericValue, prandom);
      // score of 100 = <1000
      // score of 0 = >10000
      const score = 1 - Math.min(1, Math.max((numericValue - 1000) / 9000, 0));
      audits[auditId] = {
        score,
        numericValue,
        scoreDisplayMode: 'numeric',
        displayValue:
          groupId === 'metrics'
            ? `${(numericValue / 1000).toLocaleString(undefined, {maximumFractionDigits: 1})} s`
            : `${Math.round(numericValue)} ${unit}`,
      };
    } else if (typeof averageWastedMs === 'number') {
      const wastedMs = generateNumericValue(averageWastedMs, prandom);
      // score of 100 = <100
      // score of 0 = >1000
      const score = 1 - Math.min(1, Math.max((wastedMs - 100) / 900, 0));
      const hasBytes = auditDef.items && auditDef.items.some(item => item.averageWastedBytes);
      const wastedBytes = wastedMs * 257;
      audits[auditId] = {
        score,
        scoreDisplayMode: 'numeric',
        displayValue: hasBytes
          ? `${(wastedBytes / 1024).toLocaleString(undefined, {
              maximumFractionDigits: 0,
            })} KB`
          : `${(wastedMs / 1000).toLocaleString(undefined, {
              maximumFractionDigits: 1,
            })} s`,
        details: {
          type: 'opportunity',
          overallSavingsMs: wastedMs,
          overallSavingsBytes: hasBytes ? wastedBytes : undefined,
          items: [],
        },
      };
    } else {
      const {passRate = 1} = auditDef;
      audits[auditId] = {score: prandom.next() <= passRate ? 1 : 0, scoreDisplayMode: 'binary'};
    }

    /** @type {Record<string, any>} */
    const headersAsObject = {};
    if (auditDef.items) {
      const items = [];
      for (const {url, inclusionRate = 1, ...rest} of auditDef.items) {
        if (prandom.next() > inclusionRate) continue;
        /** @type {any} */
        const item = {};
        if (url) {
          if (url.includes('lorempixel')) {
            headersAsObject.thumbnail = {key: 'url', valueType: 'thumbnail'};
          }

          headersAsObject.url = {key: 'url', valueType: 'url', label: 'URL'};
          item.url = new URL(url, pageUrl).href;
        }

        for (const key of Object.keys(rest)) {
          if (key.startsWith('average')) {
            const dataKey = key.replace('average', '').replace(/^\w/, v => v.toLowerCase());
            const valueType = key.endsWith('Ms') ? 'timespanMs' : 'bytes';
            headersAsObject[dataKey] = {key: dataKey, valueType, label: _.startCase(dataKey)};
            // @ts-ignore - tsc can't understand Object.keys
            const averageValue = rest[key];
            item[dataKey] = generateNumericValue(averageValue, prandom);
          } else {
            headersAsObject[key] = {key, valueType: 'text', label: _.startCase(key)};
            // @ts-ignore - tsc can't understand Object.keys
            item[key] = rest[key];
          }
        }

        items.push(item);
      }

      const details = audits[auditId].details || {type: 'table'};
      details.headings = Object.values(headersAsObject).map(header => {
        // Do the inverse of _getCanonicalizedTableHeadings
        if (getGroupForAuditId(auditId) === 'load-opportunities') return header;
        return {...header, itemType: header.valueType, text: header.label, valueType: undefined};
      });
      details.items = items;
      audits[auditId].details = details;
    }

    audits[auditId].title = _.startCase(auditId);
    audits[auditId].description = `Help text for ${_.startCase(auditId)}. ${loremIpsumTokens
      .slice(0, Math.round(auditId.length * 1.5))
      .join(' ')}.`;
  }

  /** @type {LH.Result['categories']} */
  const categories = {};

  const auditsGroupedByCategory = _.groupBy(Object.entries(audits), pair =>
    getCategoryForAuditId(pair[0])
  );
  for (const audits of auditsGroupedByCategory) {
    const category = getCategoryForAuditId(audits[0][0]);
    const sum = audits.reduce((sum, next) => sum + (next[1].score || 0), 0);
    categories[category] = {
      id: category,
      title: category.toUpperCase(),
      score: sum / audits.length,
      auditRefs: audits.map(([id]) => ({id, weight: 1, group: getGroupForAuditId(id)})),
    };
  }

  const fetchTimeBase = new Date('2019-08-01T12:00:00').getTime();
  const fetchTimeOffset = Math.round(prandom.next() * 1000 * 60 * 60 * 24 * 7);
  const fetchTime = new Date(fetchTimeBase + fetchTimeOffset).toISOString();

  return {
    requestedUrl: pageUrl,
    finalUrl: pageUrl,
    categories,
    audits,

    fetchTime,
    lighthouseVersion: '5.2.0',
    configSettings: {channel: 'cli'},
    categoryGroups: {
      metrics: {title: 'Metrics'},
      'load-opportunities': {title: 'Opportunities'},
      diagnostics: {title: 'Diagnostics'},
      accessibility: {title: 'Accessibility'},
      seo: {title: 'SEO'},
      'best-practices': {title: 'Best Practices'},
      'pwa-fast-reliable': {title: 'PWA Fast & Reliable'},
      'pwa-installable': {title: 'PWA Installable'},
      'pwa-optimized': {title: 'PWA Optimized'},
    },
    runWarnings: [],
    userAgent: 'Chrome!',
    environment: {hostUserAgent: '', networkUserAgent: '', benchmarkIndex: 500},
    timing: {total: 1, entries: []},
    i18n: {rendererFormattedStrings: {}, icuMessagePaths: {}},
  };
}
@param {string} pageUrl
@param {Array<AuditGenDef>} auditDefs
@param {PRandom} prandom
@return {LH.Result}