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>