puppeteer: how to wait until an element is visible?

Javascriptnode.jsGoogle Chrome-DevtoolsPuppeteer

Javascript Problem Overview


I would like to know if I can tell puppeteer to wait until an element is displayed.

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

Is there any way I can accomplish this?

Javascript Solutions


Solution 1 - Javascript

I think you can use page.waitForSelector(selector[, options]) function for that purpose.

const puppeteer = require('puppeteer');

puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  page
    .waitForSelector('#myId')
    .then(() => console.log('got it'));
    browser.close();
});

To check the options avaible, please see the https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#pagewaitforselectorselector-options"> github link.

Solution 2 - Javascript

If you want to ensure the element is actually visible, you have to use

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

Otherwise you are just looking for the element in the DOM and not checking for visibility.

Solution 3 - Javascript

> Note, All the answers submitted until today are incorrect

Because it answer for an element if Exist or Located but NOT Visible or Displayed

The right answer is to check an element size or visibility using page.waitFor() or page.waitForFunction(), see explaination below.

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

##Explanation

The element that Exist on the DOM of page not always Visible if has CSS property display:none or visibility:hidden that why using page.waitForSelector(selector) is not good idea, let see the different in the snippet below.

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>

on the snippet above the function isExist() is simulate

page.waitForSelector('#myId');

and we can see while running isExist() for both element #idA an #idB is return exist.

But when running isVisible() the #idA is not visible or dislayed.

And here other objects to check if an element is displayed or using CSS property display.

scrollWidth
scrollHeight
offsetTop
offsetWidth
offsetHeight
offsetLeft
clientWidth
clientHeight

for style visibility check with not hidden.

note: I'm not good in Javascript or English, feel free to improve this answer.

Solution 4 - Javascript

You can use 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().

Solution 5 - Javascript

Updated answer with some optimizations:

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

Solution 6 - Javascript

While I agree with @ewwink answer. Puppeteer's API checks for not hidden by default, so when you do:

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

You get not hidden and visible by CSS. To ensure rendering you can do as @ewwink's waitForFunction. However to completely answer your question, here's a snippet using puppeteer's 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()
}

After writing some methods like this myself, I found expect-puppeteer which does this and more better (see toMatchElement).

Solution 7 - Javascript

async function waitForVisible (selector){
    //const selector = '.foo';
  return  await page.waitForFunction(
      (selector) => document.querySelector(selector) && document.querySelector(selector).clientHeight != 0",
      {},
      selector
    );
}

Above function makes it generic, so that you can use it anywhere.


But, if you are using pptr there is another faster and easier solution:

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


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

Solution 8 - Javascript

Just tested this by scraping a fitness website. @ewwink, @0fnt, and @caram have provided the most complete answer.

Just because a DOM element is visible doesn't mean that it's content has been fully populated.

Today, I ran:

await page.waitForSelector("table#some-table", {visible:true})
const data = await page.$eval("table#some-table",(el)=>el.outerHTML)
console.log(data)

And incorrectly received the following, because the table DOM hadn't been populated fully by runtime. You can see that the outerHTML is empty.

user@env:$ <table id="some-table"></table>

Adding a pause of 1 second fixed this, as might be expected:

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>

But so did @ewwink's answer, more elegantly (no artificial timeouts):

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPipoView Question on Stackoverflow
Solution 1 - JavascriptturmukaView Answer on Stackoverflow
Solution 2 - JavascriptfinnView Answer on Stackoverflow
Solution 3 - JavascriptewwinkView Answer on Stackoverflow
Solution 4 - JavascriptGrant MillerView Answer on Stackoverflow
Solution 5 - JavascriptandyrandyView Answer on Stackoverflow
Solution 6 - JavascriptMoshishoView Answer on Stackoverflow
Solution 7 - JavascriptFrank GuoView Answer on Stackoverflow
Solution 8 - JavascriptjmtornettaView Answer on Stackoverflow