如何检查一个元素是否可见的 WebDriver

使用 Selenium 2.0 a2中的 WebDriver,我在检查元素是否可见时遇到了麻烦。

WebDriver.findElement返回一个 WebElement,遗憾的是它没有提供 isVisible方法。我可以通过使用 WebElement.clear或者 WebElement.click来绕过这个问题,它们都抛出一个 ElementNotVisibleException,但是这个感觉很脏。

有更好的主意吗?

275327 次浏览

element instanceof RenderedWebElement should work.

It is important to see if the element is visible or not as the Driver.FindElement will only check the HTML source. But popup code could be in the page html, and not be visible. Therefore, Driver.FindElement function returns a false positive (and your test will fail)

Even though I'm somewhat late answering the question:

You can now use WebElement.isDisplayed() to check if an element is visible.

Note:

There are many reasons why an element could be invisible. Selenium tries cover most of them, but there are edge cases where it does not work as expected.

For example, isDisplayed() does return false if an element has display: none or opacity: 0, but at least in my test, it does not reliably detect if an element is covered by another due to CSS positioning.

I have the following 2 suggested ways:

  1. You can use isDisplayed() as below:

    driver.findElement(By.id("idOfElement")).isDisplayed();
    
  2. You can define a method as shown below and call it:

    public boolean isElementPresent(By by) {
    try {
    driver.findElement(by);
    return true;
    }
    catch (org.openqa.selenium.NoSuchElementException e) {
    return false;
    }
    }
    

Now, you can do assertion as below to check either the element is present or not:

assertTrue(isElementPresent(By.id("idOfElement")));

If you're using C#, it would be driver.Displayed. Here's an example from my own project:

if (!driver.FindElement(By.Name("newtagfield")).Displayed)      //if the tag options is not displayed
driver.FindElement(By.Id("expand-folder-tags")).Click();    //make sure the folder and tags options are visible

Verifying ele is visible.

public static boolean isElementVisible(final By by)
throws InterruptedException {
boolean value = false;


if (driver.findElements(by).size() > 0) {
value = true;
}
return value;
}

try this

public boolean isPrebuiltTestButtonVisible() {
try {


if (preBuiltTestButton.isEnabled()) {


return true;


} else {


return false;
}


} catch (Exception e) {


e.printStackTrace();
return false;
}
}

Here is how I would do it (please ignore worry Logger class calls):

public boolean isElementExist(By by) {
int count = driver.findElements(by).size();
if (count>=1) {
Logger.LogMessage("isElementExist: " + by + " | Count: " + count, Priority.Medium);
return true;
}
else {
Logger.LogMessage("isElementExist: " + by + " | Could not find element", Priority.High);
return false;
}
}


public boolean isElementNotExist(By by) {
int count = driver.findElements(by).size();
if (count==0) {
Logger.LogMessage("ElementDoesNotExist: " + by, Priority.Medium);
return true;
}
else {
Logger.LogMessage("ElementDoesExist: " + by, Priority.High);
return false;
}
}


public boolean isElementVisible(By by) {
try {
if (driver.findElement(by).isDisplayed()) {
Logger.LogMessage("Element is Displayed: " + by, Priority.Medium);
return true;
}
}
catch(Exception e) {
Logger.LogMessage("Element is Not Displayed: " + by, Priority.High);
return false;
}


return false;
}
public boolean isElementFound( String text) {
try{
WebElement webElement = appiumDriver.findElement(By.xpath(text));
System.out.println("isElementFound : true :"+text + "true");
}catch(NoSuchElementException e){
System.out.println("isElementFound : false :"+text);
return false;
}
return true;
}


text is the xpath which you would be passing when calling the function.
the return value will be true if the element is present else false if element is not pressent
    try{
if( driver.findElement(By.xpath("//div***")).isDisplayed()){
System.out.println("Element is Visible");
}
}
catch(NoSuchElementException e){
else{
System.out.println("Element is InVisible");
}
}

Short answer: use #visibilityOfElementLocated

None of the answers using isDisplayed or similar are correct. They only check if the display property is not none, not if the element can actually be seen! Selenium had a bunch of static utility methods added in the ExpectedConditions class. Two of them can be used in this case:

Usage

@Test
// visibilityOfElementLocated has been statically imported
public demo(){
By searchButtonSelector = By.className("search_button");
WebDriverWait wait = new WebDriverWait(driver, 10);
driver.get(homeUrl);


WebElement searchButton = wait.until(
visibilityOfElementLocated
(searchButtonSelector));


//clicks the search button
searchButton.click();

Custom visibility check running on the client

This was my answer before finding out about the utility methods on ExpectedConditions. It might still be relevant, as I assume it does more than the method mentioned above, which only checks the element has a height and a width.

In essence: this cannot be answered by Java and the findElementBy* methods and WebElement#isDisplayed alone, as they can only tell you if an element exists, not if it is actually visible. The OP hasn't defined what visible means, but it normally entails

  • it has an opacity > 0
  • it has the display property set to something else than none
  • the visibility prop is set to visible
  • there are no other elements hiding it (it's the topmost element)

Most people would also include the requirement that it is actually within the viewport as well (so a person would be able to see it).

For some reason, this quite normal need is not met by the pure Java API, while front-ends to Selenium that builds upon it often implements some variation of isVisible, which is why I knew this should be possible. And after browsing the source of the Node framework WebDriver.IO I found the source of isVisible, which is now renamed to the more aptly name of isVisibleInViewport in the 5.0-beta.

Basically, they implement the custom command as a call that delegates to a javascript that runs on the client and does the actual work! This is the "server" bit:

export default function isDisplayedInViewport () {
return getBrowserObject(this).execute(isDisplayedInViewportScript, {
[ELEMENT_KEY]: this.elementId, // w3c compatible
ELEMENT: this.elementId // jsonwp compatible
})
}

So the interesting bit is the javascript sent to run on the client:

/**
* check if element is visible and within the viewport
* @param  {HTMLElement} elem  element to check
* @return {Boolean}           true if element is within viewport
*/
export default function isDisplayedInViewport (elem) {
const dde = document.documentElement


let isWithinViewport = true
while (elem.parentNode && elem.parentNode.getBoundingClientRect) {
const elemDimension = elem.getBoundingClientRect()
const elemComputedStyle = window.getComputedStyle(elem)
const viewportDimension = {
width: dde.clientWidth,
height: dde.clientHeight
}


isWithinViewport = isWithinViewport &&
(elemComputedStyle.display !== 'none' &&
elemComputedStyle.visibility === 'visible' &&
parseFloat(elemComputedStyle.opacity, 10) > 0 &&
elemDimension.bottom > 0 &&
elemDimension.right > 0 &&
elemDimension.top < viewportDimension.height &&
elemDimension.left < viewportDimension.width)


elem = elem.parentNode
}


return isWithinViewport
}

This piece of JS can actually be copied (almost) verbatim into your own codebase (remove export default and replace const with var in case of non-evergreen browsers)! To use it, read it from File into a String that can be sent by Selenium for running on the client.

Another interesting and related script that might be worth looking into is selectByVisibleText.

If you haven't executed JS using Selenium before you could have a small peek into this or browse the JavaScriptExecutor API.

Usually, try to always use non-blocking async scripts (meaning #executeAsyncScript), but since we already have a synchronous, blocking script we might as well use the normal sync call. The returned object can be many types of Object, so cast approprately. This could be one way of doing it:

/**
* Demo of a java version of webdriverio's isDisplayedInViewport
* https://github.com/webdriverio/webdriverio/blob/v5.0.0-beta.2/packages/webdriverio/src/commands/element/isDisplayedInViewport.js
* The super class GuiTest just deals with setup of the driver and such
*/
class VisibleDemoTest extends GuiTest {
public static String readScript(String name) {
try {
File f = new File("selenium-scripts/" + name + ".js");
BufferedReader reader = new BufferedReader( new FileReader( file ) );
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
} catch(IOError e){
throw new RuntimeError("No such Selenium script: " + f.getAbsolutePath());
}
}


public static Boolean isVisibleInViewport(RemoteElement e){
// according to the Webdriver spec a string that identifies an element
// should be deserialized into the corresponding web element,
// meaning the 'isDisplayedInViewport' function should receive the element,
// not just the string we passed to it originally - how this is done is not our concern
//
// This is probably when ELEMENT and ELEMENT_KEY refers to in the wd.io implementation
//
// Ref https://w3c.github.io/webdriver/#dfn-json-deserialize
return js.executeScript(readScript("isDisplayedInViewport"), e.getId());
}


public static Boolean isVisibleInViewport(String xPath){
driver().findElementByXPath("//button[@id='should_be_visible']");
}


@Test
public demo_isVisibleInViewport(){
// you can build all kinds of abstractions on top of the base method
// to make it more Selenium-ish using retries with timeouts, etc
assertTrue(isVisibleInViewport("//button[@id='should_be_visible']"));
assertFalse(isVisibleInViewport("//button[@id='should_be_hidden']"));
}
}

If you are using page factory then you can try the below code for your reference:

public static boolean isElementVisible(WebElement webElement, int timeOut) {
try {
WebDriverWait wait = new WebDriverWait(driver, timeOut);
wait.until(ExpectedConditions.visibilityOf(webElement));
return true;
} catch (org.openqa.selenium.NoSuchElementException e) {
return false;
}
}

Answer from oligofren - the method isDisplayedInViewport works. Thanks for that. Helped me. Just have a couple of suggestions -

  1. It is fine to check for opacity, visibility and display for all the parent nodes for the element in question as these when set on parent can hide the element as well, however the dimensions can be checked for the element itself only. In my case, the topmost parent node of the element scrolled out of the viewport and the dimension check was not allowing the method to return true.
  2. For ensuring that the complete element comes to view instead of just the top of it, we can use height and width instead of x and y (top/left)

I have used the following code based on what oligofren provided in his answer. I am passing the WebElement as an argument to the selenium execute_script method and reading it as elem

    var elem = arguments[0]
const dde = document.documentElement
let isWithinViewport = true
var firstTime = true
while (elem.parentNode && elem.parentNode.getBoundingClientRect) {
const elemDimension = elem.getBoundingClientRect()
const elemComputedStyle = window.getComputedStyle(elem)
const viewportDimension = {
width: dde.clientWidth,
height: dde.clientHeight
}
let dimCondition = true
if (firstTime){


dimCondition = dimCondition && (elemDimension.bottom > 0 &&
elemDimension.right > 0 &&
(elemDimension.top + elemDimension.height) < viewportDimension.height  &&
(elemDimension.left + elemDimension.width) < viewportDimension.width)
firstTime = false


}
isWithinViewport = isWithinViewport && dimCondition &&
(elemComputedStyle.display !== 'none' &&
elemComputedStyle.visibility === 'visible' &&
parseFloat(elemComputedStyle.opacity, 10) > 0
)
console.log(isWithinViewport)
console.log(elem.nodeName)


elem = elem.parentNode
}
return isWithinViewport