I'm building a framework that crawls websites hosted on our infrastructure to verify there is nothing against our policies. If there is content prohibited, we are taking a screenshot of it among other details.
Obviously, you can't take screenshot if the element is not visible due to being inside of an overflow: hidden
parent or because there is an absolute element positioned above it.
Is there a way in 2019 to know if an element can be visible to the human eye?
Since it's inside Puppeteer I can use either native Puppeteer API or whatever JavaScript library needed since I can inject it to the page.
Example of the problem:
$('#above').html(`You can see green: ${$('#below').is(':visible')}. But can you really?`)
#above {
width: 600px;
height: 600px;
position: absolute;
background-color: red;
text-align: center;
font-size: 30px;
padding: 30px;
}
#below {
width: 440;
height: 200;
background-color: red;
}
<script src=".3.1/jquery.min.js"></script>
<div id="above"></div>
<div id="below"></div>
I'm building a framework that crawls websites hosted on our infrastructure to verify there is nothing against our policies. If there is content prohibited, we are taking a screenshot of it among other details.
Obviously, you can't take screenshot if the element is not visible due to being inside of an overflow: hidden
parent or because there is an absolute element positioned above it.
Is there a way in 2019 to know if an element can be visible to the human eye?
Since it's inside Puppeteer I can use either native Puppeteer API or whatever JavaScript library needed since I can inject it to the page.
Example of the problem:
$('#above').html(`You can see green: ${$('#below').is(':visible')}. But can you really?`)
#above {
width: 600px;
height: 600px;
position: absolute;
background-color: red;
text-align: center;
font-size: 30px;
padding: 30px;
}
#below {
width: 440;
height: 200;
background-color: red;
}
<script src="https://cdnjs.cloudflare./ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="above"></div>
<div id="below"></div>
(Yes, I saw this question was asked, but it was 7 years ago and much was changed since and I can also use puppeteer which in his case was not possible).
I never did this, but the hover function will fail if the element is not visible or "hoverable". You could do something like this:
let error = null;
await page.hover('yourSelector').catch(e => error = e);
if (!error) {
//The element should be visible here.
}
I found a way (thanks @hardkoded for planting the idea in my head...)
I didn't test it on all edge cases, but it does seem to work on my example.
Basically I'm adding an eventListener for mouseover
event that sets a boolean flag and then calling puppeteer's hover
function that moves the mouse to the center of the element.
If the flag is set, the element is visible.
await page.goto('c:/temp/code.html')
const el = await page.$('#below')
await page.evaluate(el => {
el.addEventListener('mouseover', function () {
this.setAttribute('mouseover-worked', 'true')
console.log('hovered!!!')
})
}, el)
await el.hover()
const hovering_works = await page.evaluate(el => el.getAttribute('mouseover-worked'), el)
console.log(hovering_works)
If someone finds a flaw, please let me know...
There is an easy way built into the puppeteer library to check this. You can give a { visible: false }
option to the page.waitForSelector
function that will check or actually wait until an element is visible:
const element = await page.waitForSelector('#selector', { visible: true });
This will do a decent check. See the full code of the function here. It will check the following:
style.visibility !== 'hidden'
) according to window.getComputedStyle
?height
/width
/top
/bottom
greater than 0
according to element.getBoundingClientRect()
?The latter case would also cover your example code as the #below
element should be having a height of 0
. So it's a decent check but you will always be able to construct cases in which the code does not work. If you want to cover more edge cases, you can also expand the approach of the puppeteer devs.
Full code sample
As the function will not resolve if the element does not exist, you have to use a small helper Promise to timeout the check:
const element = await Promise.race([
new Promise(resolve => setTimeout(() => resolve(), 200)), // resolves without value after 200ms
page.waitForSelector('#selector', { visible: true })
]);
if (element) {
// element is visible
}
Maybe you can using elementHandle.boundingBox()
It will return a Promise that show a bounding box of the element (relative to the main frame), or null if the element is not visible.
The snippet example:
const loadMoreButton = await getDataPage.$(
'button.ao-tour-reviews__load-more-cta.js-ao-tour-reviews__load-more-cta'
);
const buttonVisible = await loadMoreButton.boundingBox();
if (buttonVisible) {
await loadMoreButton.click().catch((e) => {
console.log('