Is there a way to verify if an element is visible using Puppeteer and only plain JavaScript?

I am looking for a way to verify the visibility of a DOM element using Puppeteer and pure JavaScript, without relying on jQuery. Specifically, I want to ensure that the element is visible based on its CSS properties, rather than being hidden (for example, with display: none).

One method to check if an element like #menu is not hidden due to the CSS rule display: none is by using the following code snippet:

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

However, I would like to know a more general approach to determine whether an element is hidden or not, beyond just checking for display: none.

Answer №1

After some exploration, I discovered that Puppeteer offers a convenient API method tailored for this specific task: Page.waitForSelector, featuring the use of its visible option. This functionality allows you to pause execution until an element becomes visible on the page.

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

On the flip side, you also have the ability to wait for an element to become hidden by employing the hidden option.

In my opinion, this method aligns well with the standard practices within the Puppeteer API. Nevertheless, credit should be given to Colin Cline for providing a potentially valuable JavaScript solution in his response.

Answer №2

Firstly, you can determine if an element is visible by examining its display style value. Another method is to assess the height of the element; for example, if the element is nested within a parent element with a display: none property, the offsetHeight will be 0, indicating that the element is not visible despite its display setting. It's important to note that an element with opacity: 0 is not necessarily considered hidden and should not be factored in during visibility checks.

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

Additionally, it may be helpful to verify elem.offsetWidth before performing any calculations to ensure the existence of the element.

Answer №4

The approved solution currently involves patiently waiting for an element to appear and then becoming visible.

If we are not keen on waiting for the element and simply want to verify its visibility, we can utilize a combination of getComputedStyle() and getBoundingClientRect() to check if the element is actually visible.

To begin with, we need to ensure that the visibility property is not set to hidden.

Subsequently, we can confirm the visibility of the bounding box by verifying that attributes like bottom, top, height, and width are not equal to 0 (this will eliminate elements with 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);
});

Answer №5

Perhaps you could try utilizing the elementHandle.boundingBox() method as suggested by @huypham.

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

Here is an example snippet:

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

Answer №6

Using the playwright's approach to determine element visibility - https://github.com/microsoft/playwright/blob/master/src/server/injected/injectedScript.ts#L120-L129

function isElementVisible(el: Element): boolean {
    // It is important to have logic similar to waitForDisplayedAtStablePosition() for consistent results.
    if (!el.ownerDocument || !el.ownerDocument.defaultView)
      return true;
    const computedStyle = el.ownerDocument.defaultView.getComputedStyle(el);
    if (!computedStyle || computedStyle.visibility === 'hidden')
      return false;
    const boundingRect = el.getBoundingClientRect();
    return boundingRect.width > 0 && boundingRect.height > 0;
}

Answer №7

The response from @aknuds1 is spot-on, but here's a helpful tip to make things even easier for yourself. You can create a helper function like this one, which will return true if the element is visible and false otherwise.

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

How to Use:

For plain JavaScript with Puppeteer

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

If you are using Jest or another testing framework

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

In the case of Jest (or most other frameworks), you can take it a step further by creating a custom matcher to enhance the existing ones. (https://jestjs.io/docs/expect#expectextendmatchers)

expect.extend({
    async toHaveVisible(page, selector, timeout = 150) {
        let isVisible = await checkVisibility(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);

Answer №8

It seems like this is the method that jQuery uses:

visible = await page.evaluate((e) => e.offsetWidth > 0 && e.offsetHeight > 0, element)

Answer №9

If you're looking to determine the visibility of an element, you can utilize this handy function. It's important to ensure that the page has fully loaded before utilizing this function. One way to do this is by using waitForSelector on other elements that are expected to be visible.

async function checkVisibility(page, selector) {
  return await page.evaluate((selector) => {
    var elem = document.querySelector(selector);
    if (elem) {
      var computedStyle = window.getComputedStyle(elem);

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


// Example usage:
page.waitForSelector('#anotherElement');
var isVisible = await checkVisibility(page, '#visibleOrHidden');

if (isVisible) {
// Perform actions on #visibleOrHidden
}

Answer №10

This piece of code is quite useful for handling scenarios where an element is present on the page but not visible. Typically, this occurs when the element's display property is set to none or its visibility is hidden in CSS. In such cases, attempting to interact with the element using Puppeteer may result in failures.

async function checkElementVisibility(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;
}

Answer №11

Why not give Element.checkVisibility() a go? It's fully supported in Chrome. Here's a breakdown of how it functions, as outlined in the CSS specification:

When invoked on an element (this), checkVisibility(options) follows these steps:

  1. If there's no associated box with this element, the method returns false.
  2. If any ancestor of this containing shadow content has a visibility of "hidden", return false.
  3. If the checkOpacity option in options is enabled and either this element or its ancestors have an opacity value of 0, return false.
  4. If the checkVisibilityCSS option in options is enabled and this element is invisible, return false.
  5. Otherwise, return true.

Answer №12

To ensure that an element is visible, its parent's visibility must be recursively checked since the element will not be visible if its parent is hidden.

// Enhanced function for checking visibility
    function isVisible(element) {
        // Recursive function to check visibility of all parents
        function checkVisibilityRecursively(el) {
            if (!el || el === document) return true;

            const style = window.getComputedStyle(el);

            // Check if element or any parent is invisible due to styles
            if (style.display === 'none' ||
                style.visibility === 'hidden' ||
                style.opacity === '0'
            ) {
                return false;
            }

            // Recursively check visibility for parent element
            return checkVisibilityRecursively(el.parentElement);
        }

        return checkVisibilityRecursively(element);
    }

Answer №13

let userFirst= await page.$('[name=userFirst]')
expect(userFirst !== null).equal(true)

Answer №14

Although @aknuds1's method is recommended, there is another way to achieve the same result.

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

If you are making an asynchronous resource request, please note that the above assertion may fail because it does not wait for UI updates. Therefore, this alternative approach may not be suitable in such cases.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring a Ring with Three.js OrbitControls Rotation

I am having an issue with my OrbitControls camera setup. Currently, the camera rotates around a fixed point at player.position.x, y, and z coordinates. While it works fine, I need the camera to rotate around a ring instead. In the examples provided, the fi ...

"The authentication scheme is unrecognized" - this is the message produced by Node-LinkedIn module

I am currently utilizing the node-linkedin npm package to authenticate and retrieve information about other users, such as their name, job title, company name, profile picture, and shared connections. While I am able to successfully receive and store the a ...

Can you explain the concept of (A == B == C) comparison in JavaScript?

Surprisingly, the comparison I expected to fail was this: var A = B = 0; if(A == B == 0) console.log(true); else console.log(false); To my surprise, it doesn't return true. What's even more astonishing is that console.log((A == B == ...

It appears that Next.js's useDebouncedCallback function is not effectively delaying the request

I am currently learning Next.js and trying to work through the tutorial. I have hit a roadblock on this particular page: https://nextjs.org/learn/dashboard-app/adding-search-and-pagination Despite conducting an extensive web search, I couldn't find a ...

The solution for fixing contenteditable is as follows:

I am currently working on a script to clean up pasted text within a contenteditable div. While the script is functioning well for most part, I have noticed that in Firefox, line breaks get removed when the text is copied within or between the divs. Does ...

Arranging List Items within a Container

What's the best way to align three different elements within a div? The elements are structured in an unordered list: Left, Center, and Right. I attempted to float the right element using float: right, and applied margin: 0 auto with a set width for ...

Error in Laravel 5.5 PusherBroadcaster.php at line 106

I am facing a frustrating issue with the BroadcastException in PusherBroadcaster.php (line 106) error while using Laravel 5.5 and Vue 2.0. Despite trying various solutions, I have been unable to resolve it. Desperately seeking assistance. Here's what ...

How can you use code to compel mongoose to request and retrieve a specific field from a mongodb collection?

I am facing an issue where I have a password and salt stored in MongoDB, but I cannot query the salt field along with other fields. This is the schema: var userSchema = new Schema({ firstname : { type: String, trim: true}, lastname : { type: Str ...

`End session for inactive user after next authentication`

I am facing a challenge with managing inactive user sessions in my app. I tried setting the maxAge of ...nextauth to one minute and the refetch interval to 20s for SessionProvider, but encountered an issue where the session expiration on the browser cook ...

Vue 3 - Compelled to utilize any data type with computedRef

Recently, I've been diving into Vue/Typescript and encountered a puzzling error. The issue revolves around a class named UploadableFile: export class UploadableFile { file: File; dimensions: Ref; price: ComputedRef<number>; ... constr ...

What is the method for receiving socket emits in sails.io.js?

I am currently utilizing Sails 0.11 for the back-end and angularjs for the front-end of my project. Within Sails, I have a TwitterController containing the following code snippet to establish a connection with the Twitter Streaming API using the node modu ...

There seems to be a glitch with the functionality of the HighStocks Tooltip

I've implemented a modified version of the example from highcharts: $(function () { $.getJSON('http://www.highcharts.com/samples/data/jsonp.php?filename=aapl-c.json&callback=?', function (data) { // Create the chart $('#co ...

Alignment of Bootstrap input groups and buttons

I am looking to have the input group aligned on the left side and the "Add New" button aligned on the right side of the row. <div class="row"> <div class="input-group col-sm-6"> <input type="text" class="form-control" placehol ...

Rendering Highcharts React Pie Chart Multiple Times

Here is the code snippet: import React, { useEffect, useRef, useState } from "react"; import * as Highcharts from "highcharts"; import HighchartsReact from "highcharts-react-official"; export const PieChart = (props: any) =&g ...

Issue encountered when attempting to synchronize Protractor with the page: "unable to locate Angular on the window" while executing a Protractor test case

Currently, I am conducting tests on an Angular website. Our application flow begins with a Login page followed by a vertical application selection frame (located in the left corner of the page) that consists of non-Angular web pages. Once the user selects ...

My nestjs project is refusing to launch after a system restart

Recently, I've been encountering a strange issue where after rebooting my system, I am required to completely uninstall npm and nodejs before reinstalling them in order to successfully start my nestjs project. Upon running the npm run start command i ...

Prevent Object Prop Modification in Vue.js

Within the parent component, I initially have an empty filter object like this: {}. The child component, called filter component, is a child of the parent component and here's how it is implemented: <filter-component :filters.sync="filters&q ...

Remove an individual document from a nested array of documents based on its unique identifier

One issue I'm facing is with my user model, where users can also serve as drivers and each driver has an array of cars. My goal is to delete a specific car from all the drivers, but my current query ends up deleting all the cars. Here is the schema i ...

The filter is displaying incorrect categories

I am facing an issue with creating a work filter based on the last column which represents categories. When I select an option from the dropdown, I want to display only that specific category and hide the others. Currently, when I try clicking on an option ...

Encountering a problem while attempting to initiate a React app with npm start, an error

I am a beginner in web development with React and I recently followed these steps - npm install -g create-react-app create-react-app my-app cd my-app npm start However, I encountered the following error message: E:\Study\React-course\React- ...