How can I check that an element is visible with Puppeteer and pure JavaScript?
Asked Answered
B

13

27

I wish to check that a DOM element is visible with Puppeteer and pure JavaScript (not jQuery), how can I do this? By visible I mean that the element is displayed through CSS, and not hidden (f.ex. by display: none).

For example, I can determine whether my element #menu is not hidden via CSS rule display: none, in the following way:

const isNotHidden = await page.$eval('#menu', (elem) => {
  return elem.style.display !== 'none'
})

How can I determine in general though if the element is hidden or not, and not just through display: none?

Beller answered 8/12, 2017 at 10:43 Comment(4)
I don't know how consistently it works for other methods of hiding, but elem.getBoundingClientRect() returns distinctive data you can test against.Undershorts
@Undershorts usingelem.getBoundingClientRect() returns a {} on console.log, whether the element is ready or not :(Behead
Since Puppeteer can always run normal JS, the canonical thread Check if element is visible in DOM should be linked because it has a huge number of resources that can be used via .evaluate() in Puppeteer.Loudermilk
@Behead did you try returning JSON.stringify(elem.getBoundingClientRect())? The reason it probably matters is that elem.getBounding... is a read-only DOMRect rather than a plain object, so Puppeteer's serialization seems to be affected and doesn't capture the full object.Loudermilk
B
50

I found that Puppeteer has an API method for this purpose: Page.waitForSelector, via its visible option. I wasn't aware of the latter option, but it lets you wait until an element is visible.

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

Conversely you can wait for an element to be hidden, via the hidden option.

I think this is the idiomatic answer, with regards to the Puppeteer API. Thanks to Colin Cline though as I think his answer is probably useful as a general JavaScript solution.

Beller answered 8/12, 2017 at 11:45 Comment(5)
This will throw if the element is not visible. If it is perfectly acceptable that the element not be visible, then throwing is NOT appropriate. I wish developers would stop throwing exceptions for normal behavior, just return a value like null!Gerrigerrie
@Gerrigerrie thats exactly my problem , i want to check for the element and then do something based on that if the element is not ready , but it throws and makes app crash when looking for the elementBehead
The question was how to test if an element is visible, not waiting for it to be added to the DOM and then to become visible, which is what this "accepted" solution does.Schlosser
This doesn't work for elements that are behind other elements z-Index, though.Unbent
I don't understand how the acceptable answer is an answer that will make the script pause if the element does not exist (due to the wait)Cleptomania
C
15

One is by checking its display style value. Second is by checking its height, for exp if the element is a child of an element which is display: none, the offsetHeight will be 0 and thus you know the element is not visible despite its display value. opacity: 0 is not considered as hidden element so we will not checking it.

const isNotHidden = await page.$eval('#menu', (elem) => {
    return window.getComputedStyle(elem).getPropertyValue('display') !== 'none' && elem.offsetHeight
});

You can check elem.offsetWidth as well and is not bad before any calculation, check if element exist or not.

Crawl answered 8/12, 2017 at 11:9 Comment(4)
And "window" var where is defined?Splendent
@Splendent The window object represents a window containing a DOM document; getComputedStyle. You can cache the window into a variable ofc but its a global objectCrawl
You're right. I'm a beginner with puppetter. All this code is working inside a "navigator", so window, document, etc are always available.Splendent
this solution just saved my day , except that i checked the height of the element .Behead
E
11

Use boundingBox()

This method returns the bounding box of the element (relative to the main frame), or null if the element is not visible.

API: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#elementhandleboundingbox

Eighteenmo answered 21/4, 2020 at 7:16 Comment(1)
The link in the question goes to github, but github responds with a 404. I submit a link to the same content on the pptr docs website. pptr.dev/api/puppeteer.elementhandle.boundingboxValency
C
8

The current accepted answer involves waiting for an element to appear and become visible.

If we are not interested in waiting on the element, and we would simply like to test the visibility of the element, we can use a combination of getComputedStyle() and getBoundingClientRect() to test whether or not the element is visible.

We can first check that the visibility is not set to hidden.

Then we can validate that the bounding box is visible by checking that the bottom, top, height, and width attributes are not set to 0 (this will filter out elements that have display set to none as well).

const element_is_visible = await page.evaluate(() => {
  const element = document.querySelector('#example');
  const style = getComputedStyle(element);
  const rect = element.getBoundingClientRect();

  return style.visibility !== 'hidden' && !!(rect.bottom || rect.top || rect.height || rect.width);
});
Cockleboat answered 18/3, 2020 at 3:24 Comment(1)
This does not work for elements that are behind other elements (ie: stacked elements in a z-index) ... is there a full-featured solution like this that does?Unbent
L
5

Maybe you can using elementHandle.boundingBox() (thank to @huypham idea)

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('💥💥💥: ' + e)
        });
      }
Leannaleanne answered 25/9, 2020 at 2:34 Comment(0)
F
3

based on playwright's logic for checking if the element is visible - https://github.com/microsoft/playwright/blob/master/src/server/injected/injectedScript.ts#L120-L129

function isVisible(element: Element): boolean {
    // Note: this logic should be similar to waitForDisplayedAtStablePosition() to avoid surprises.
    if (!element.ownerDocument || !element.ownerDocument.defaultView)
      return true;
    const style = element.ownerDocument.defaultView.getComputedStyle(element);
    if (!style || style.visibility === 'hidden')
      return false;
    const rect = element.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  }
Fichte answered 11/10, 2020 at 16:20 Comment(0)
A
2

The answer @aknuds1 gave is perfect but you may make it even more convenient for yourself by creating a helper such as this one. This resolves to true if the element is visible and to false otherwise.

function isElementVisible(page, selector, timeout = 150) {
    return new Promise((resolve) => {
        page.waitForSelector(selector, {visible: true, timeout}).then(() => {
            resolve(true);
        }).catch(() => {
            resolve(false);
        });
    });
}

Usage

in pure JS with Puppeteer

let isVisible = await isElementVisible(page, selector)
isVisible = await isElementVisible(page, selector, 300)

and if you happen to use Jest or another framework

expect(await isElementVisible(page, selector)).toBeTrue();

in the case of Jest (and most other frameworks), you can go even further and create a custom matcher to extend the existing ones. (https://jestjs.io/docs/expect#expectextendmatchers)

expect.extend({
    async toHaveVisible(page, selector, timeout = 150) {
        let isVisible = await isElementVisible(page, selector, timeout);

        if (isVisible) {
            return {
                message: () => `expected ${selector} not to be visible`,
                pass: true
            };
        } else {
            return {
                message: () => `expected ${selector} to be visible`,
                pass: false
            };
        }
    }
});
await expect(page).toHaveVisible(selector);
await expect(page).not.toHaveVisible(anotherSelector);
await expect(page).not.toHaveVisible(yetAnotherSelector, 300);
Atone answered 20/7, 2022 at 14:32 Comment(0)
U
0

Apparently here's how jQuery does it:

visible = await page.evaluate((e) => e.offsetWidth > 0 && e.offsetHeight > 0, element)
Us answered 29/1, 2020 at 7:11 Comment(0)
S
0

If you just want to know if an element is visible or not then you can use this function. You should make sure that the page is ready before calling this function. You can do that by using waitForSelector on other elements you expect to be visible.

async function isVisible(page, selector) {
  return await page.evaluate((selector) => {
    var e = document.querySelector(selector);
    if (e) {
      var style = window.getComputedStyle(e);

      return style && style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
    }
    else {
      return false;
    }
  }, selector);
}


// Example usage:
page.waitForSelector('#otherPeerElement');
var myElementIsVisible = await isVisible(page, '#visibleOrNot');

if (myElementIsVisible) {
// Interact with #visibleOrNot
}
Switch answered 9/2, 2020 at 23:9 Comment(0)
W
0

this code definitely help you. It basically means the element is already available on the page but is not visible yet or in CSS, the display property is set as none or visibility is hidden. Now, while writing our tests, we assume that as soon as the element is available, do an action on it like clicking or typing. But as this element is not yet visible, Puppeteer fails to perform that action.

async function isLocatorReady(element, page) {
  const isVisibleHandle = await page.evaluateHandle((e) => 
{
    const style = window.getComputedStyle(e);
    return (style && style.display !== 'none' && 
    style.visibility !== 'hidden' && style.opacity !== '0');
 }, element);
  var visible = await isVisibleHandle.jsonValue();
  const box = await element.boxModel();
  if (visible && box) {
    return true;
  }
  return false;
}
Wenzel answered 14/5, 2020 at 12:27 Comment(0)
M
0

You can also try to utilize Element.checkVisibility(). It's supported in chrome. Here is how it works from the css spec:

The checkVisibility(options) method must run these steps, when called on an element this:

  1. If this does not have an associated box, return false.
  2. If a shadow-including ancestor of this has content-visibility: hidden, return false.
  3. If the checkOpacity dictionary member of options is true, and this, or a shadow-including ancestor of this, has a computed opacity value of 0, return false.
  4. If the checkVisibilityCSS dictionary member of options is true, and this is invisible, return false.
  5. Return true.
Maisey answered 27/6, 2023 at 21:7 Comment(0)
P
-1
const firstName= await page.$('[name=firstName]')
expect(firstName!=null).equal(true)
Phemia answered 15/7, 2020 at 5:56 Comment(0)
P
-3

I would use @aknuds1 's approach, but you can also do the following.

expect((await page.$('#element')) !== null).toEqual(true)

If you are fetching a resource asynchronously, be aware that the above expectation may not pass, since it won't wait for the changes to reflect on the UI. That's why this approach may not be preferred in this scenario.

Partial answered 11/2, 2019 at 22:34 Comment(2)
@Gerrigerrie you are utterly wrong. Please read the question better before you make a comment.Partial
It appears your answer is expecting the use of a puppeteer testing framework such as jest-puppeteer and the OP did not specify they are testing.Hohenlinden

© 2022 - 2025 — McMap. All rights reserved.