puppeteer:如何等到元素可见?
puppeteer: how to wait until an element is visible?
我想知道我是否可以告诉 puppeteer 等到显示元素。
const inputValidate = await page.$('input[value=validate]');
await inputValidate.click()
// I want to do something like that
waitElemenentVisble('.btnNext ')
const btnNext = await page.$('.btnNext');
await btnNext.click();
有什么方法可以做到这一点吗?
我想你可以使用 page.waitForSelector(selector[, options])
功能来达到这个目的。
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
page
.waitForSelector('#myId')
.then(() => console.log('got it'));
browser.close();
});
要检查可用的选项,请参阅 github link.
更新了一些优化的答案:
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto('https://www.somedomain.com', {waitUntil: 'networkidle2'});
await page.click('input[value=validate]');
await page.waitForSelector('#myId');
await page.click('.btnNext');
console.log('got it');
browser.close();
})();
您可以使用 page.waitFor()
, page.waitForSelector()
, or page.waitForXPath()
to wait for an element on a page:
// Selectors
const css_selector = '.btnNext';
const xpath_selector = '//*[contains(concat(" ", normalize-space(@class), " "), " btnNext ")]';
// Wait for CSS Selector
await page.waitFor(css_selector);
await page.waitForSelector(css_selector);
// Wait for XPath Selector
await page.waitFor(xpath_selector);
await page.waitForXPath(xpath_selector);
Note: In reference to a frame, you can also use frame.waitFor()
, frame.waitForSelector()
, or frame.waitForXPath()
.
Note, All the answers submitted until today are incorrect
因为如果 存在或定位 but NOT
可见或显示
,它会回答一个元素
正确答案是使用 page.waitFor()
or page.waitForFunction()
检查元素大小或可见性,请参阅下面的说明。
// wait until present on the DOM
// await page.waitForSelector( css_selector );
// wait until "display"-ed
await page.waitForFunction("document.querySelector('.btnNext') && document.querySelector('.btnNext').clientHeight != 0");
// or wait until "visibility" not hidden
await page.waitForFunction("document.querySelector('.btnNext') && document.querySelector('.btnNext').style.visibility != 'hidden'");
const btnNext = await page.$('.btnNext');
await btnNext.click();
说明
如果有 CSS 属性 display:none
或 visibility:hidden
那为什么使用 [=21] 页面 DOM 上存在的元素并不总是可见=] 不是个好主意,让我们在下面的代码片段中看看不同之处。
function isExist(selector) {
let el = document.querySelector(selector);
let exist = el.length != 0 ? 'Exist!' : 'Not Exist!';
console.log(selector + ' is ' + exist)
}
function isVisible(selector) {
let el = document.querySelector(selector).clientHeight;
let visible = el != 0 ? 'Visible, ' + el : 'Not Visible, ' + el;
console.log(selector + ' is ' + visible + 'px')
}
isExist('#idA');
isVisible('#idA');
console.log('=============================')
isExist('#idB')
isVisible('#idB')
.bd {border: solid 2px blue;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="bd">
<div id="idA" style="display:none">#idA, hidden element</div>
</div>
<br>
<div class="bd">
<div id="idB">#idB, visible element</div>
</div>
在上面的代码片段中,函数 isExist()
是模拟
page.waitForSelector('#myId');
我们可以看到当 运行 isExist()
两个元素 #idA
一个 #idB
是 return 存在。
但是当 运行 isVisible()
时 #idA
不可见或显示。
这里还有其他对象来检查元素是否显示或使用 CSS 属性 display
.
scrollWidth
scrollHeight
offsetTop
offsetWidth
offsetHeight
offsetLeft
clientWidth
clientHeight
样式 visibility
不检查 hidden
。
注意:我的 Javascript 或英语不是很好,请随时改进此答案。
如果要确保元素确实可见,则必须使用
page.waitForSelector('#myId', {visible: true})
否则您只是在 DOM 中查找元素,而不是检查可见性。
虽然我同意@ewwink 的回答。 Puppeteer 的 API 默认情况下检查是否隐藏,所以当您这样做时:
await page.waitForSelector('#id', {visible: true})
您不会被 CSS 隐藏和显示。
为确保渲染,您可以按照@ewwink 的 waitForFunction
进行操作。但是,为了完全回答您的问题,这里有一段使用 puppeteer 的 API:
async waitElemenentVisble(selector) {
function waitVisible(selector) {
function hasVisibleBoundingBox(element) {
const rect = element.getBoundingClientRect()
return !!(rect.top || rect.bottom || rect.width || rect.height)
}
const elements = [document.querySelectorAll(selector)].filter(hasVisibleBoundingBox)
return elements[0]
}
await page.waitForFunction(waitVisible, {visible: true}, selector)
const jsHandle = await page.evaluateHandle(waitVisible, selector)
return jsHandle.asElement()
}
自己写了一些这样的方法后,发现expect-puppeteer which does this and more better (see toMatchElement).
async function waitForVisible (selector){
//const selector = '.foo';
return await page.waitForFunction(
(selector) => document.querySelector(selector) && document.querySelector(selector).clientHeight != 0",
{},
selector
);
}
以上函数使其通用,因此您可以在任何地方使用它。
但是,如果您使用的是 pptr,还有另一种更快更简单的解决方案:
page.waitForSelector('#myId', {visible: true})
刚刚通过抓取健身网站对此进行了测试。 @ewwink、@0fnt 和@caram 提供了最完整的答案。
仅仅因为 DOM 元素可见并不意味着它的内容已被完全填充。
今天,我运行:
await page.waitForSelector("table#some-table", {visible:true})
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)
并且错误地收到了以下内容,因为 table DOM 没有被运行时完全填充。可以看到outerHTML是空的
user@env:$ <table id="some-table"></table>
如预期的那样,添加 1 秒的暂停可以解决此问题:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await page.waitForSelector("table#some-table", {visible:true})
await sleep(1000)
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)
user@env:$ <table id="some-table"><tr><td>Data</td></tr></table>
但@ewwink 的回答也是如此,更优雅(没有人为超时):
await page.waitForSelector("table#some-table", {visible:true})
await page.waitForFunction("document.querySelector('table#sched-records').clientHeight != 0")
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)
user@env:$ <table id="some-table"><tr><td>Data</td></tr></table>
我想知道我是否可以告诉 puppeteer 等到显示元素。
const inputValidate = await page.$('input[value=validate]');
await inputValidate.click()
// I want to do something like that
waitElemenentVisble('.btnNext ')
const btnNext = await page.$('.btnNext');
await btnNext.click();
有什么方法可以做到这一点吗?
我想你可以使用 page.waitForSelector(selector[, options])
功能来达到这个目的。
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
page
.waitForSelector('#myId')
.then(() => console.log('got it'));
browser.close();
});
要检查可用的选项,请参阅 github link.
更新了一些优化的答案:
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto('https://www.somedomain.com', {waitUntil: 'networkidle2'});
await page.click('input[value=validate]');
await page.waitForSelector('#myId');
await page.click('.btnNext');
console.log('got it');
browser.close();
})();
您可以使用 page.waitFor()
, page.waitForSelector()
, or page.waitForXPath()
to wait for an element on a page:
// Selectors
const css_selector = '.btnNext';
const xpath_selector = '//*[contains(concat(" ", normalize-space(@class), " "), " btnNext ")]';
// Wait for CSS Selector
await page.waitFor(css_selector);
await page.waitForSelector(css_selector);
// Wait for XPath Selector
await page.waitFor(xpath_selector);
await page.waitForXPath(xpath_selector);
Note: In reference to a frame, you can also use
frame.waitFor()
,frame.waitForSelector()
, orframe.waitForXPath()
.
Note, All the answers submitted until today are incorrect
因为如果 存在或定位 but NOT
可见或显示
正确答案是使用 page.waitFor()
or page.waitForFunction()
检查元素大小或可见性,请参阅下面的说明。
// wait until present on the DOM
// await page.waitForSelector( css_selector );
// wait until "display"-ed
await page.waitForFunction("document.querySelector('.btnNext') && document.querySelector('.btnNext').clientHeight != 0");
// or wait until "visibility" not hidden
await page.waitForFunction("document.querySelector('.btnNext') && document.querySelector('.btnNext').style.visibility != 'hidden'");
const btnNext = await page.$('.btnNext');
await btnNext.click();
说明
如果有 CSS 属性 display:none
或 visibility:hidden
那为什么使用 [=21] 页面 DOM 上存在的元素并不总是可见=] 不是个好主意,让我们在下面的代码片段中看看不同之处。
function isExist(selector) {
let el = document.querySelector(selector);
let exist = el.length != 0 ? 'Exist!' : 'Not Exist!';
console.log(selector + ' is ' + exist)
}
function isVisible(selector) {
let el = document.querySelector(selector).clientHeight;
let visible = el != 0 ? 'Visible, ' + el : 'Not Visible, ' + el;
console.log(selector + ' is ' + visible + 'px')
}
isExist('#idA');
isVisible('#idA');
console.log('=============================')
isExist('#idB')
isVisible('#idB')
.bd {border: solid 2px blue;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="bd">
<div id="idA" style="display:none">#idA, hidden element</div>
</div>
<br>
<div class="bd">
<div id="idB">#idB, visible element</div>
</div>
在上面的代码片段中,函数 isExist()
是模拟
page.waitForSelector('#myId');
我们可以看到当 运行 isExist()
两个元素 #idA
一个 #idB
是 return 存在。
但是当 运行 isVisible()
时 #idA
不可见或显示。
这里还有其他对象来检查元素是否显示或使用 CSS 属性 display
.
scrollWidth
scrollHeight
offsetTop
offsetWidth
offsetHeight
offsetLeft
clientWidth
clientHeight
样式 visibility
不检查 hidden
。
注意:我的 Javascript 或英语不是很好,请随时改进此答案。
如果要确保元素确实可见,则必须使用
page.waitForSelector('#myId', {visible: true})
否则您只是在 DOM 中查找元素,而不是检查可见性。
虽然我同意@ewwink 的回答。 Puppeteer 的 API 默认情况下检查是否隐藏,所以当您这样做时:
await page.waitForSelector('#id', {visible: true})
您不会被 CSS 隐藏和显示。
为确保渲染,您可以按照@ewwink 的 waitForFunction
进行操作。但是,为了完全回答您的问题,这里有一段使用 puppeteer 的 API:
async waitElemenentVisble(selector) {
function waitVisible(selector) {
function hasVisibleBoundingBox(element) {
const rect = element.getBoundingClientRect()
return !!(rect.top || rect.bottom || rect.width || rect.height)
}
const elements = [document.querySelectorAll(selector)].filter(hasVisibleBoundingBox)
return elements[0]
}
await page.waitForFunction(waitVisible, {visible: true}, selector)
const jsHandle = await page.evaluateHandle(waitVisible, selector)
return jsHandle.asElement()
}
自己写了一些这样的方法后,发现expect-puppeteer which does this and more better (see toMatchElement).
async function waitForVisible (selector){
//const selector = '.foo';
return await page.waitForFunction(
(selector) => document.querySelector(selector) && document.querySelector(selector).clientHeight != 0",
{},
selector
);
}
以上函数使其通用,因此您可以在任何地方使用它。
但是,如果您使用的是 pptr,还有另一种更快更简单的解决方案:
page.waitForSelector('#myId', {visible: true})
刚刚通过抓取健身网站对此进行了测试。 @ewwink、@0fnt 和@caram 提供了最完整的答案。
仅仅因为 DOM 元素可见并不意味着它的内容已被完全填充。
今天,我运行:
await page.waitForSelector("table#some-table", {visible:true})
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)
并且错误地收到了以下内容,因为 table DOM 没有被运行时完全填充。可以看到outerHTML是空的
user@env:$ <table id="some-table"></table>
如预期的那样,添加 1 秒的暂停可以解决此问题:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await page.waitForSelector("table#some-table", {visible:true})
await sleep(1000)
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)
user@env:$ <table id="some-table"><tr><td>Data</td></tr></table>
但@ewwink 的回答也是如此,更优雅(没有人为超时):
await page.waitForSelector("table#some-table", {visible:true})
await page.waitForFunction("document.querySelector('table#sched-records').clientHeight != 0")
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)
user@env:$ <table id="some-table"><tr><td>Data</td></tr></table>