Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refind WebElement which class removed at click event

I have an element with a class a. So, in Selenium code I am getting with this:

WebElement element = driver.findElement(By.cssSelector(".a"));

Afterwards I am clicking on it with element.click();. The click event removes the class a from the element - which exactly is the test case I am trying to execute.

So, now I wanted to ask the element if it already owns this class:

element.getAttribute("class").contains("a");

But this did not work because the WebElement tried to find the element again by the given selector which was not clear to me. I thought the WebElement, once found, is internally copied throughout the scope. But obviously, it calls the linked selector everytime it is called in the code.

So, how can I retrieve an element more persistantly? How can I avoid the WebElement being refreshed on every call to track the changes of the already selected element?

Of course, I could use a work-around using the DOM, the parents or a list id. But I really want to avoid this, because I do not want to get too much information about the DOM structure into my test code. This is the reason why I added classes and ids.


Edit: Adding the log output:

WebElement element = driver.findElement(By.cssSelector(".a"));

1564042692783   webdriver::server   DEBUG   -> POST /session/2d7cce7d-bd10-4814-b619-b4c8dc212fac/elements {"value":".a","using":"css selector"}
1564042692787   Marionette  TRACE   0 -> [0,10,"WebDriver:FindElements",{"using":"css selector","value":".a"}]
1564042692793   Marionette  TRACE   0 <- [1,10,null,[{"element-6066-11e4-a52e-4f735466cecf":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a","ELEMENT":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a"}]]
1564042692794   webdriver::server   DEBUG   <- 200 OK {"value":[{"element-6066-11e4-a52e-4f735466cecf":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a"}]}

As you can see, the received element is 517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a.

element.click();

1564042703055   webdriver::server   DEBUG   -> POST /session/2d7cce7d-bd10-4814-b619-b4c8dc212fac/elements {"value":".a","using":"css selector"}
1564042703058   Marionette  TRACE   0 -> [0,11,"WebDriver:FindElements",{"using":"css selector","value":".a"}]
1564042703065   Marionette  TRACE   0 <- [1,11,null,[{"element-6066-11e4-a52e-4f735466cecf":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a","ELEMENT":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a"}]]
1564042703066   webdriver::server   DEBUG   <- 200 OK {"value":[{"element-6066-11e4-a52e-4f735466cecf":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a"}]}
1564042703142   webdriver::server   DEBUG   -> POST /session/2d7cce7d-bd10-4814-b619-b4c8dc212fac/element/517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a/click {"id":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a"}
1564042703145   Marionette  TRACE   0 -> [0,12,"WebDriver:ElementClick",{"id":"517f4e9c-5d09-4fe0-8c34-4d8c153a9c4a"}]
1564042703380   Marionette  DEBUG   Canceled page load listener because no navigation has been detected
1564042703382   Marionette  TRACE   0 <- [1,12,null,{}]
1564042703384   webdriver::server   DEBUG   <- 200 OK {"value":null}

And now the check:

element.getAttribute("class");

1564042714064   webdriver::server   DEBUG   -> POST /session/2d7cce7d-bd10-4814-b619-b4c8dc212fac/elements {"value":".a","using":"css selector"}
1564042714067   Marionette  TRACE   0 -> [0,13,"WebDriver:FindElements",{"using":"css selector","value":".a"}]
1564042714070   Marionette  TRACE   0 <- [1,13,null,[]]
1564042714071   webdriver::server   DEBUG   <- 200 OK {"value":[]}

As you can see, no element has been returned now.


Edit: After evaluating the solution of @RahulL (which seems to work; at the click execution no further WebDriver:FindElements call is logged - in contrast to my log) I believe that the problem lies somewhere in the Aquillian Graphene implementation which wraps my Selenium. The findElement() call does not call the Selenium class directly. That's why I added these tags. It could be relevant for finding the problem.

So, the class definition:

import static org.junit.Assert.assertFalse;

import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

@RunWith(Arquillian.class)
@RunAsClient
public class MyTests {

    @Drone
    WebDriver driver;

    @Test
    public void test_removeClassFromElement() {
        driver.navigate().refresh();
        driver.get("my.application");

        WebElement element = driver.findElement(By.className("a"))
        element.click();

        assertFalse(
            element .getAttribute("class").contains("a")
        );
    }
}

and the arquillian.xml:

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/schema/arquillian
        http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

    <extension qualifier="webdriver">
        <property name="browser">firefox</property>
        <property name="firefoxLogLevel">FINEST</property>
    </extension>

</arquillian>
like image 939
S-Man Avatar asked Jul 22 '19 13:07

S-Man


People also ask

What is the return type of a WebElement click?

The return type of findElements is a list whereas the return type of findElement is a WebElement. If there is no matching element on the page, an exception is thrown by the findElement method. In this scenario, an empty list id returned by the findElements method.

What is difference between WebElement findElement and findElement?

There are differences between findElement and findElements in the Selenium webdriver. Both of them can be used to locate elements on a webpage. The findElement points to a single element, while the findElements method returns a list of matching elements.

Which is a class WebElement?

Selenium Webdriver represents all the HTML elements as WebElements. This class provides a mechanism to represent them as objects & perform various actions on the related elements. Typically, the findElement method in remoteDriver returns an object of class webElement.

What is WebElement class or interface?

Represents an HTML element. Generally, all interesting operations to do with interacting with a page will be performed through this interface. All method calls will do a freshness check to ensure that the element reference is still valid.


1 Answers

Tested your scenario in JAVA binding + Firefox driver . Click remove the 'mystyle' from div and able to get the attribute class without 'mystyle'

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.mystyle {
  width: 100%;
  padding: 25px;
  background-color: coral;
  color: white;
  font-size: 25px;
  box-sizing: border-box;
}
</style>
</head>
<body>

<p>Click the "Try it" button to toggle between adding and removing the "mystyle" class name of the DIV element:</p>



<div id="myDIV" onclick="myFunction()" class="mystyle">
This is a DIV element.

</div>

<script>
function myFunction() {
   var element = document.getElementById("myDIV");
   element.classList.remove("mystyle");
}
</script>

</body>
</html>

JAVA Code :

         WebElement element = driver.findElement(By.className("mystyle"));
         element.click();
         System.out.println(element.getAttribute("class"));

Enabled the FirefoxDriverLogLevel.TRACE . WebDriver logs

  1. Find Element

    WebElement element = driver.findElement(By.className("mystyle"));

    webdriver::server   DEBUG   -> POST /session/81662ee0-1195-4ff3-8687-667f3607ea89/element {
      "value": ".mystyle",
      "using": "css selector"
    }
    
  2. Got the element

    webdriver::server DEBUG <- 200 OK {"value":{"element-6066-11e4-a52e-4f735466cecf":"c15725f8-89f9-4fec-af08-be1b9487defe"}}

  3. Now click on the element

    element.click();

    webdriver::server   DEBUG   -> POST /session/81662ee0-1195-4ff3-8687-667f3607ea89/element/c15725f8-89f9-4fec-af08-be1b9487defe/click {
      "id": "c15725f8-89f9-4fec-af08-be1b9487defe" }
    
  4. Now send 'getAttribute'

    element.getAttribute("class")

    webdriver::server DEBUG -> POST /session/81662ee0-1195-4ff3-8687-667f3607ea89/execute/sync { "script": "here selenium sends the getAttribute script", "args": [ { "element-6066-11e4-a52e-4f735466cecf": "c15725f8-89f9-4fec-af08-be1b9487defe" }, "class" ] }

5 Response :

webdriver::server   DEBUG   <- 200 OK {"value":""}

Got the value of class as "" . ".mystyle" is not present

From above logs once element is found using selenium and assigned to 'element' It does not send post request to find element again. c15725f8-89f9-4fec-af08-be1b9487defe remains same in click as well as getAttribute. (Webdriver Protocol)

If you are using the @FindBy annotations in JAVA binding then Selenium will try to find the element again and again on every action using proxy .This will avoid the staleelemnt exception in most of the cases.

To avoid flakiness and retrieve an element more persistently you will need to use attributes that do not change.

like image 142
Rahul L Avatar answered Oct 25 '22 19:10

Rahul L