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>
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.
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.
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.
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.
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
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"
}
Got the element
webdriver::server DEBUG <- 200 OK {"value":{"element-6066-11e4-a52e-4f735466cecf":"c15725f8-89f9-4fec-af08-be1b9487defe"}}
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" }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With