render(category, groups, options) {
    const strings = Globals.strings;
    const element = this.dom.createElement('div', 'lh-category');
    element.id = category.id;
    element.append(this.renderCategoryHeader(category, groups, options));

    // Metrics.
    const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics');
    if (metricAudits.length) {
      const [metricsGroupEl, metricsFooterEl] = this.renderAuditGroup(groups.metrics);

      // Metric descriptions toggle.
      const checkboxEl = this.dom.createElement('input', 'lh-metrics-toggle__input');
      const checkboxId = `lh-metrics-toggle${Globals.getUniqueSuffix()}`;
      checkboxEl.setAttribute('aria-label', 'Toggle the display of metric descriptions');
      checkboxEl.type = 'checkbox';
      checkboxEl.id = checkboxId;
      metricsGroupEl.prepend(checkboxEl);
      const metricHeaderEl = this.dom.find('.lh-audit-group__header', metricsGroupEl);
      const labelEl = this.dom.createChildOf(metricHeaderEl, 'label', 'lh-metrics-toggle__label');
      labelEl.htmlFor = checkboxId;
      const showEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--show');
      const hideEl = this.dom.createChildOf(labelEl, 'span', 'lh-metrics-toggle__labeltext--hide');
      showEl.textContent = Globals.strings.expandView;
      hideEl.textContent = Globals.strings.collapseView;

      const metricsBoxesEl = this.dom.createElement('div', 'lh-metrics-container');
      metricsGroupEl.insertBefore(metricsBoxesEl, metricsFooterEl);
      metricAudits.forEach(item => {
        metricsBoxesEl.append(this._renderMetric(item));
      });

      // Only add the disclaimer with the score calculator link if the category was rendered with a score gauge.
      if (element.querySelector('.lh-gauge__wrapper')) {
        const descriptionEl = this.dom.find('.lh-category-header__description', element);
        const estValuesEl = this.dom.createChildOf(descriptionEl, 'div', 'lh-metrics__disclaimer');
        const disclaimerEl = this.dom.convertMarkdownLinkSnippets(strings.varianceDisclaimer);
        estValuesEl.append(disclaimerEl);

        // Add link to score calculator.
        const calculatorLink = this.dom.createChildOf(estValuesEl, 'a', 'lh-calclink');
        calculatorLink.target = '_blank';
        calculatorLink.textContent = strings.calculatorLink;
        this.dom.safelySetHref(calculatorLink, this._getScoringCalculatorHref(category.auditRefs));
      }

      metricsGroupEl.classList.add('lh-audit-group--metrics');
      element.append(metricsGroupEl);
    }

    // Filmstrip
    const timelineEl = this.dom.createChildOf(element, 'div', 'lh-filmstrip-container');
    const thumbnailAudit = category.auditRefs.find(audit => audit.id === 'screenshot-thumbnails');
    const thumbnailResult = thumbnailAudit?.result;
    if (thumbnailResult?.details) {
      timelineEl.id = thumbnailResult.id;
      const filmstripEl = this.detailsRenderer.render(thumbnailResult.details);
      filmstripEl && timelineEl.append(filmstripEl);
    }

    this._renderInsightsToggle(element);

    const legacyAuditsSection =
      this.renderFilterableSection(category, groups, ['diagnostics'], metricAudits);
    legacyAuditsSection?.classList.add('lh-perf-audits--swappable', 'lh-perf-audits--legacy');

    const experimentalInsightsSection =
      this.renderFilterableSection(category, groups, ['insights', 'diagnostics'], metricAudits);
    experimentalInsightsSection?.classList.add(
      'lh-perf-audits--swappable', 'lh-perf-audits--experimental');

    if (legacyAuditsSection) {
      element.append(legacyAuditsSection);

      // Many tests expect just one of these sections to be in the DOM at a given time.
      // To prevent the hidden section from tripping up these tests, we will just remove the hidden
      // section from the DOM and store it in memory.
      if (experimentalInsightsSection) {
        this.dom.registerSwappableSections(legacyAuditsSection, experimentalInsightsSection);
      }
    }
    // Deal with the user loading the report and having toggled to Insights
    // which is now stored in local storage. Put in a rAF otherwise this code
    // runs before the DOM is created.
    if (this._getInsightToggleState() === 'INSIGHTS') {
      requestAnimationFrame(() => {
        const swappableSection = this.dom.maybeFind('.lh-perf-audits--swappable');
        if (swappableSection) {
          this.dom.swapSectionIfPossible(swappableSection);
        }
      });
    }

    // Log the initial state.
    this.dom.fireEventOn('lh-analytics', this.dom.document(), {
      name: 'initial_insights_state',
      data: {state: this._getRawInsightToggleState()},
    });

    const isNavigationMode = !options || options?.gatherMode === 'navigation';
    if (isNavigationMode && category.score !== null) {
      const el = createGauge(this.dom);
      updateGauge(this.dom, el, category);
      this.dom.find('.lh-score__gauge', element).replaceWith(el);
    }

    return element;
  }
@param {LH.ReportResult.Category} category
@param {Object<string, LH.Result.ReportGroup>} groups
@param {{gatherMode: LH.Result.GatherMode}=} options
@return {Element}
@override