display: none
)。例如,我可以通过以下方式确定我的元素#menu
是否未被CSS规则display: none
隐藏:const isNotHidden = await page.$eval('#menu', (elem) => {
return elem.style.display !== 'none'
})
我怎样一般性地确定元素是否隐藏,而不只是通过 display: none
属性?
display: none
)。例如,我可以通过以下方式确定我的元素#menu
是否未被CSS规则display: none
隐藏:const isNotHidden = await page.$eval('#menu', (elem) => {
return elem.style.display !== 'none'
})
我怎样一般性地确定元素是否隐藏,而不只是通过 display: none
属性?
visible
选项。我之前不知道后者选项,但它可以让你等到元素可见为止。await page.waitForSelector('#element', {
visible: true,
})
相反,您可以通过 hidden
选项等待元素被隐藏。
我认为这是关于 Puppeteer API 的惯用答案。感谢 Colin Cline,因为我认为他的答案可能对一般的 JavaScript 解决方案有用。
第一步是通过检查其显示样式值来确定元素是否可见。
第二步是通过检查其高度来确定元素是否可见。例如,如果该元素是 display: none
父元素的子元素,则 offsetHeight
将为 0
,因此您知道该元素虽然具有显示值,但不可见。我们不会检查 opacity: 0
元素是否隐藏。
const isNotHidden = await page.$eval('#menu', (elem) => {
return window.getComputedStyle(elem).getPropertyValue('display') !== 'none' && elem.offsetHeight
});
在进行任何计算之前,您可以检查 elem.offsetWidth
,这也不错,可以检查元素是否存在。
使用boundingBox()方法
此方法返回元素的边框框(相对于主框架),如果该元素不可见,则返回null。
API: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#elementhandleboundingbox
当前被接受的答案涉及等待元素出现并变得可见。
如果我们不想等待元素,并且只想测试元素的可见性,我们可以使用getComputedStyle()
和getBoundingClientRect()
的组合来检测元素是否可见。
我们首先可以检查visibility
是否未设置为hidden
。
然后,我们可以验证边界框是否可见,通过检查bottom
、top
、height
和width
属性是否未设置为0
(这将过滤掉同时具有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);
});
也许你可以使用elementHandle.boundingBox()
(感谢@huypham的想法)
它将返回一个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)
});
}
@aknuds1 给出的答案非常完美,但您可以通过创建此类助手使其更加方便。如果元素可见,则解决为true
,否则为false
。
function isElementVisible(page, selector, timeout = 150) {
return new Promise((resolve) => {
page.waitForSelector(selector, {visible: true, timeout}).then(() => {
resolve(true);
}).catch(() => {
resolve(false);
});
});
}
使用纯JS和Puppeteer
let isVisible = await isElementVisible(page, selector)
isVisible = await isElementVisible(page, selector, 300)
如果您使用Jest或其他框架
,expect(await isElementVisible(page, selector)).toBeTrue();
对于Jest(以及大多数其他框架)而言,您甚至可以更进一步,创建自定义匹配器来扩展现有的匹配器。(https://jestjs.io/docs/expect#expectextendmatchers)
expect.extend({
async toHaveVisible(page, selector, timeout = 150) {
let isVisible = await isElementVisible(page, selector, timeout);
if (isVisible) {
return {
message: () => `expected ${selector} not to be visible`,
pass: true
};
} else {
return {
message: () => `expected ${selector} to be visible`,
pass: false
};
}
}
});
await expect(page).toHaveVisible(selector);
await expect(page).not.toHaveVisible(anotherSelector);
await expect(page).not.toHaveVisible(yetAnotherSelector, 300);
基于剧作家的逻辑来检查元素是否可见 - 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;
}
显然,这是jQuery的做法:
visible = await page.evaluate((e) => e.offsetWidth > 0 && e.offsetHeight > 0, element)
Element.checkVisibility()
方法。它在Chrome中受支持。以下是它的工作原理,来自CSS规范:
调用此元素的checkVisibility(options)方法必须执行以下步骤:
- 如果此元素没有关联的框,则返回false。
- 如果此元素的包括阴影的祖先元素具有content-visibility: hidden样式,则返回false。
- 如果options的checkOpacity字典成员为true,并且此元素或其包括阴影的祖先元素的计算透明度值为0,则返回false。
- 如果options的checkVisibilityCSS字典成员为true,并且此元素不可见,则返回false。
- 返回true。
这段代码肯定会对你有所帮助。它基本上意味着该元素已经在页面上可用,但尚未可见或在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;
}
elem.getBoundingClientRect()
返回了可以进行测试的独特数据。 - AuxTacoelem.getBoundingClientRect()
在控制台上返回一个{}
,无论元素是否准备好:( - PayamB..evaluate()
在Puppeteer中使用。 - ggorlenJSON.stringify(elem.getBoundingClientRect())
吗?这很重要的原因是elem.getBounding...
是一个只读的DOMRect
而不是一个普通对象,所以 Puppeteer 的序列化似乎受到影响,无法捕获完整的对象。 - ggorlen