如何使用 Puppeteer 和纯 JavaScript 检查元素是否可见?

How can I check that an element is visible with Puppeteer and pure JavaScript?

我想用 Puppeteer 和纯 JavaScript(不是 jQuery)检查 DOM 元素是否可见,我该怎么做?可见是指元素通过 CSS 显示,而不是隐藏(f.ex。通过 display: none)。

例如,我可以通过CSS规则display: none判断我的元素#menu是否没有被隐藏,方法如下:

const isNotHidden = await page.$eval('#menu', (elem) => {
  return elem.style.display !== 'none'
})

我一般如何确定元素是否隐藏,而不仅仅是通过 display: none

一个是通过检查其显示样式值。 第二个是通过检查它的高度,对于exp如果该元素是display: none元素的子元素,则offsetHeight将是0并且因此您知道该元素不可见,尽管它具有显示值。 opacity: 0 不被视为隐藏元素,因此我们不会对其进行检查。

const isNotHidden = await page.$eval('#menu', (elem) => {
    return window.getComputedStyle(elem).getPropertyValue('display') !== 'none' && elem.offsetHeight
});

你也可以检查elem.offsetWidth,在任何计算之前都不错,检查元素是否存在。

我发现 Puppeteer 有一个用于此目的的 API 方法:Page.waitForSelector,通过它的 visible 选项。我不知道后一个选项,但它让你等到元素可见。

await page.waitForSelector('#element', {
  visible: true,
})

相反,您可以通过 hidden 选项等待元素被隐藏。

我认为这是关于 Puppeteer API 的惯用答案。感谢 Colin Cline,因为我认为他的回答可能作为一般 JavaScript 解决方案很有用。

我会使用@aknuds1 的方法,但您也可以执行以下操作。

expect((await page.$('#element')) !== null).toEqual(true)

如果您正在异步获取资源,请注意上述预期可能不会通过,因为它不会等待更改反映在 UI 上。这就是为什么这种方法在这种情况下可能不是首选的原因。

显然,jQuery 是这样做的:

visible = await page.evaluate((e) => e.offsetWidth > 0 && e.offsetHeight > 0, element)

如果你只是想知道一个元素是否可见,那么你可以使用这个函数。在调用此函数之前,您应该确保页面已准备就绪。您可以通过在您希望可见的其他元素上使用 waitForSelector 来做到这一点。

async function isVisible(page, selector) {
  return await page.evaluate((selector) => {
    var e = document.querySelector(selector);
    if (e) {
      var style = window.getComputedStyle(e);

      return style && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
    }
    else {
      return false;
    }
  }, selector);
}


// Example usage:
page.waitForSelector('#otherPeerElement');
var myElementIsVisible = await isVisible(page, '#visibleOrNot');

if (myElementIsVisible) {
// Interact with #visibleOrNot
}

当前接受的答案涉及等待元素出现变得可见。

如果我们对等待元素不感兴趣,只想测试元素的可见性,我们可以使用getComputedStyle() and getBoundingClientRect()的组合来测试元素是否可见。

我们可以先检查visibility没有设置为hidden

然后我们可以通过检查 bottomtopheightwidth 属性是否未设置为 [= 来验证边界框是否可见19=](这将过滤掉 display 也设置为 none 的元素)。

const element_is_visible = await page.evaluate(() => {
  const element = document.querySelector('#example');
  const style = getComputedStyle(element);
  const rect = element.getBoundingClientRect();

  return style.visibility !== 'hidden' && !!(rect.bottom || rect.top || rect.height || rect.width);
});

使用 boundingBox()

此方法returns元素的边界框(相对于主框架),如果元素不可见则为 null。

API: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#elementhandleboundingbox

这段代码绝对能帮到你。 它基本上意味着该元素已经在页面上可用但尚不可见或在 CSS 中,显示 属性 设置为 none 或隐藏可见性。现在,在编写我们的测试时,我们假设一旦元素可用,就对其执行操作,例如单击或键入。但由于此元素尚不可见,Puppeteer 无法执行该操作。

async function isLocatorReady(element, page) {
  const isVisibleHandle = await page.evaluateHandle((e) => 
{
    const style = window.getComputedStyle(e);
    return (style && style.display !== 'none' && 
    style.visibility !== 'hidden' && style.opacity !== '0');
 }, element);
  var visible = await isVisibleHandle.jsonValue();
  const box = await element.boxModel();
  if (visible && box) {
    return true;
  }
  return false;
}
const firstName= await page.$('[name=firstName]')
expect(firstName!=null).equal(true)

也许你可以使用 elementHandle.boundingBox()(感谢@huypham 的想法)

它将 return 显示元素边界框(相对于主框架)的 Promise,如果元素不可见则为 null。

片段示例:

      const loadMoreButton = await getDataPage.$(
        'button.ao-tour-reviews__load-more-cta.js-ao-tour-reviews__load-more-cta'
      );

      const buttonVisible = await loadMoreButton.boundingBox();

      if (buttonVisible) {
        await loadMoreButton.click().catch((e) => {
          console.log(': ' + e)
        });
      }

基于剧作家检查元素是否可见的逻辑 - https://github.com/microsoft/playwright/blob/master/src/server/injected/injectedScript.ts#L120-L129

function isVisible(element: Element): boolean {
    // Note: this logic should be similar to waitForDisplayedAtStablePosition() to avoid surprises.
    if (!element.ownerDocument || !element.ownerDocument.defaultView)
      return true;
    const style = element.ownerDocument.defaultView.getComputedStyle(element);
    if (!style || style.visibility === 'hidden')
      return false;
    const rect = element.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  }