80

我想知道我是否可以告诉 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();

有什么办法可以做到这一点吗?

4

8 回答 8

103

我认为您可以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 链接。

于 2017-09-09T23:25:12.123 回答
66

如果要确保元素实际可见,则必须使用

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

否则,您只是在 DOM 中寻找元素,而不是检查可见性。

于 2019-03-17T22:16:49.393 回答
38

注意,直到今天提交的所有答案都是错误的

因为如果存在或位于 but NOT 可见或显示,它会回答一个元素

正确的答案是使用page.waitFor()或检查元素大小或可见性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();

解释

存在于页面 DOM 上的元素如果具有 CSS 属性display:nonevisibility:hidden为什么使用page.waitForSelector(selector)不是好主意,则并不总是可见的,让我们看看下面的片段中的不同之处。

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

但是在运行时isVisible()#idA可见或不显示。

这里还有其他对象来检查元素是否显示或使用 CSS 属性display

scrollWidth
scrollHeight
offsetTop
offsetWidth
offsetHeight
offsetLeft
clientWidth
clientHeight

用 not进行样式visibility检查hidden

注意:我不擅长 Javascript 或英语,请随时改进此答案。

于 2019-01-09T05:21:54.437 回答
15

您可以使用page.waitFor()page.waitForSelector()page.waitForXPath()等待页面上的元素:

// 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);

注意:在引用框架时,您还可以使用frame.waitFor()frame.waitForSelector()frame.waitForXPath()

于 2018-09-01T00:09:29.870 回答
12

更新了一些优化的答案:

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();
})();
于 2018-07-31T10:17:17.310 回答
6

虽然我同意@ewwink 的回答。Puppeteer 的 API 默认检查不隐藏,所以当你这样做时:

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

你不会被 CSS 隐藏和可见。为确保渲染,您可以像 @ewwink's 那样做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 ,它做得更好(参见toMatchElement)。

于 2019-10-29T09:27:44.697 回答
3
async function waitForVisible (selector){
    //const selector = '.foo';
  return  await page.waitForFunction(
      (selector) => document.querySelector(selector) && document.querySelector(selector).clientHeight != 0",
      {},
      selector
    );
}

上面的功能使其通用,因此您可以在任何地方使用它。


但是,如果您使用的是 pptr,还有另一种更快、更简单的解决方案:

https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-pagewaitforfunctionpagefunction-options-args


page.waitForSelector('#myId', {visible: true})
于 2021-06-05T13:44:14.480 回答
1

刚刚通过抓取健身网站进行了测试。@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)

并且错误地收到了以下内容,因为表 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>

于 2022-02-05T00:42:13.483 回答