async _resolveNodes(context) {
    function resolveNodes() {
      /** @type {LH.Result.FullPageScreenshot['nodes']} */
      const nodes = {};
      if (!window.__lighthouseNodesDontTouchOrAllVarianceGoesAway) return nodes;

      const lhIdToElements = window.__lighthouseNodesDontTouchOrAllVarianceGoesAway;
      for (const [node, id] of lhIdToElements.entries()) {
        // @ts-expect-error - getBoundingClientRect put into scope via stringification
        const rect = getBoundingClientRect(node);
        nodes[id] = {id: node.id, ...rect};
      }

      return nodes;
    }

    /**
     * @param {{useIsolation: boolean}} _
     */
    function resolveNodesInPage({useIsolation}) {
      return context.driver.executionContext.evaluate(resolveNodes, {
        args: [],
        useIsolation,
        deps: [pageFunctions.getBoundingClientRect],
      });
    }

    // Collect nodes with the page context (`useIsolation: false`) and with our own, reused
    // context (`useIsolation: true`). Gatherers use both modes when collecting node details,
    // so we must do the same here too.
    const pageContextResult = await resolveNodesInPage({useIsolation: false});
    const isolatedContextResult = await resolveNodesInPage({useIsolation: true});
    return {...pageContextResult, ...isolatedContextResult};
  }
Gatherers can collect details about DOM nodes, including their position on the page.
Layout shifts occuring after a gatherer runs can cause these positions to be incorrect,
resulting in a poor experience for element screenshots.
`getNodeDetails` maintains a collection of DOM objects in the page, which we can iterate
to re-collect the bounding client rectangle.
@see pageFunctions.getNodeDetails
@param {LH.Gatherer.Context} context
@return {Promise<LH.Result.FullPageScreenshot['nodes']>}