puppeteer:等待元素可见?

时间:2017-09-09 23:01:31

标签: javascript node.js google-chrome-devtools 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();

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

7 个答案:

答案 0 :(得分:20)

我认为您可以为此目的使用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();
});

答案 1 :(得分:11)

  

注意,以上所有答案均不正确!

因为存在或位于 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)并不是一个好主意,那么在页面的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

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

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

scrollWidth
scrollHeight
offsetTop
offsetWidth
offsetHeight
offsetLeft
clientWidth
clientHeight

对于样式visibility,请不要选择hidden

注意:我的Java语言或英语都不好,请随时改善此答案。

答案 2 :(得分:7)

您可以使用page.waitFor()page.waitForSelector()page.waitForXPath()等待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 );
  

注意::对于frame,您还可以使用frame.waitFor()frame.waitForSelector()frame.waitForXPath()

答案 3 :(得分:4)

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

exports.modifySauce = (req, res, next) => {
  let sauce = new Sauce({ _id: req.params._id });
  if (req.file) {
    const url = req.protocol + '://' + req.get('host');
    req.body.sauce = JSON.parse(req.body.sauce);
    sauce = {
      _id: req.params.id,
      name: req.body.sauce.name,
      manufacturer:req.body.sauce.manufacturer,
      mainPepper:req.body.sauce.mainPepper,
      description: req.body.sauce.description,
      imageUrl: url + '/images/' + req.file.filename,
      heat: req.body.sauce.heat,
      userId: req.body.sauce.userId
    };
  } else {
    sauce = {
      _id: req.params.id,
      name: req.body.name,
      manufacturer:req.body.manufacturer,
      mainPepper:req.body.mainPepper,
      description: req.body.description,
      imageUrl: req.body.imageUrl,
      heat: req.body.heat,
      userId: req.body.userId
    };
  }
  Sauce.updateOne({_id: req.params.id}, sauce).then(
    () => {
      res.status(201).json({
        message: 'Sauce updated successfully!'
      });
    }
  ).catch(
    (error) => {
      res.status(400).json({
        error: error
      });
    }
  );
};

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

答案 4 :(得分:2)

更新了答案并进行了一些优化:

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();
})();

答案 5 :(得分:1)

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})

答案 6 :(得分:0)

虽然我同意@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可以做到这一点,并且效果更好(请参见toMatchElement)。