function collectAnchorElements() {
  /** @param {string} url */
  const resolveURLOrEmpty = url => {
    try {
      return new URL(url, window.location.href).href;
    } catch (_) {
      return '';
    }
  };

  /** @param {HTMLAnchorElement|SVGAElement} node */
  function getTruncatedOnclick(node) {
    const onclick = node.getAttribute('onclick') || '';
    return onclick.slice(0, 1024);
  }

  /**
   * @param {HTMLElement|SVGElement} node
   * @return {string|null}
   */
  function getLangOfInnerText(node) {
    let curNodeLang = null;

    // If we find multiple languages within this element, return null.
    for (const child of node.querySelectorAll('*')) {
      if (!child.textContent) continue;

      const childLang = child.closest('[lang]')?.getAttribute('lang');
      if (!childLang) continue;

      if (!curNodeLang) {
        curNodeLang = childLang;
        continue;
      }

      if (curNodeLang.split('-')[0] !== childLang.split('-')[0]) {
        return null;
      }
    }

    return curNodeLang ?? node.closest('[lang]')?.getAttribute('lang') ?? null;
  }

  /** @type {Array<HTMLAnchorElement|SVGAElement>} */
  // @ts-expect-error - put into scope via stringification
  const anchorElements = getElementsInDocument('a'); // eslint-disable-line no-undef

  // Check, if document has only one lang attribute in opening html or in body tag. If so,
  // there is no need to run the `getLangOfInnerText()` function with multiple
  // possible DOM traversals
  /** @type {Array<HTMLElement|SVGElement>} */
  // @ts-expect-error - put into scope via stringification
  const langElements = getElementsInDocument('[lang]'); // eslint-disable-line no-undef
  const documentHasSingleLang = langElements.length === 1 &&
    (langElements[0].nodeName === 'BODY' || langElements[0].nodeName === 'HTML');
  const singleLang = documentHasSingleLang ? langElements[0].getAttribute('lang') : null;

  // TODO: consider Content-Language.

  return anchorElements.map(node => {
    if (node instanceof HTMLAnchorElement) {
      return {
        href: node.href,
        rawHref: node.getAttribute('href') || '',
        onclick: getTruncatedOnclick(node),
        role: node.getAttribute('role') || '',
        name: node.name,
        text: node.innerText, // we don't want to return hidden text, so use innerText
        textLang: singleLang ?? getLangOfInnerText(node) ?? undefined,
        rel: node.rel,
        target: node.target,
        id: node.getAttribute('id') || '',
        attributeNames: node.getAttributeNames(),
        // @ts-expect-error - getNodeDetails put into scope via stringification
        node: getNodeDetails(node),
      };
    }

    return {
      href: resolveURLOrEmpty(node.href.baseVal),
      rawHref: node.getAttribute('href') || '',
      onclick: getTruncatedOnclick(node),
      role: node.getAttribute('role') || '',
      text: node.textContent || '',
      textLang: singleLang ?? getLangOfInnerText(node) ?? undefined,
      rel: '',
      target: node.target.baseVal || '',
      id: node.getAttribute('id') || '',
      attributeNames: node.getAttributeNames(),
      // @ts-expect-error - getNodeDetails put into scope via stringification
      node: getNodeDetails(node),
    };
  });
}
Function that is stringified and run in the page to collect anchor elements.
Additional complexity is introduced because anchors can be HTML or SVG elements.

We use this evaluateAsync method because the `node.getAttribute` method doesn't actually normalize
the values like access from JavaScript in-page does.

@return {LH.Artifacts['AnchorElements']}