Annoucement: New Java and Selenium batch is starting from October 28th Nov, Please enroll ASAP!

Mystery of StaleElementReferenceException in Selenium WebDriver

Mystery of StaleElementReferenceException in Selenium WebDriver


If you are a Selenium developer than you would have surely faced this mysterious exception called “StaleElementReferenceException
Why exactly it occurs? This has been my favorite interview question since last many years and most of the time candidates gets confused it with NoSuchElementException. In case you have never worked on a dynamic ajax based application then there could be a chance that you have never faced it.
Let’s go little deeper and unveils the mystery behind it. When we run a simple code like this:
WebElement searchBox = driver.findElement(By.cssSelector("input[name='q'"));
searchBox.sendKeys("Selenium");
When WebDriver executes the above code then it assigns an internal id (refer the below image) to every web element and it refers this id to interact with that element.
Example image
Now, let’s assume when you have fetched the element and before doing any action on it, something got refreshed on your page. It could be the entire page refresh or some internal ajax call which has refreshed a section of the dom where your element falls. In this scenario the internal id which webdriver was using has become stale, so now for every operation on this WebElement, we will get StaleElementReferenceException.
To overcome this problem, the only choice we have is to re-fetch the element from Dom and this time WebDriver will assign a different Id to this element.
So from the above example what we understood is that if we are working with an AJAX-heavy application where page’s dom can get changed on every interaction then it is wise to fetch the web elements every time when we are operating on them. There are couple of ways to make sure, the element always gets refreshed before we use it:

Page Factory Design Pattern:

Please refer the below code.
GoogleSearchPage page = PageFactory.initElements(driver,GoogleSearchPage.class);
public class GoogleSearchPage {
    @FindBy(how = How.NAME, using = "q")
    private WebElement searchBox;

    public void searchFor(String text) {
        searchBox.sendKeys(text);
    }
In the above example, a proxy would be configured for every Web Element when the page gets initialised. Every time we use a WebElement it will go and find it again so we shouldn’t see StaleElementException. This approach would solve your stale element problem at most of the places except some corner cases which I will cover in the next approach.

Refreshing the Element whenever it gets stale:

When you work on a modern, reactive, real-time application developed in technologies like Angularjs/Reactjs which has hell lot of data and there is a persistent web-socket connection which keep pushing data to your browser and which makes your dom to change. Let’s take an example of a stock exchange where there a data grid which displays real-time information and data keeps changing too frequently. In this case, whenever the data gets changed at server-side, the changes will be pushed automatically to your UI grid and depending on your data your respective rows or cells will get stale.
Here, Page factory can not help as most of your grid elements are dynamic and you cannot configure their locators while initiliazing your page. Also if you have created your own data model to prase data, than it is difficult to configure Page Factory accross all your data model classes.
To deal with this problem I decided to develop a generic method to refresh the element in case it gets stale. To refresh an element, we first need to figure out its By locator but Selenium API has not exposed anything to re-construct the locator from an existing web element. I was fortunate that they have exposed a toString method on WebElement which print all the locators being used to build that element. Let’s see the below example where we are finding an element which is a child of another element:
WebElement elem1 = driver.findElement(By.xpath("//div[@id='searchform']"));
WebElement elem2 = elem1.findElement(By.cssSelector("input[name='q'"));
System.out.println(elem2.toString());
Output of the above code would be:
[[[[ChromeDriver: chrome on XP (bd6a0d83229c67d5f7e6060b1bd768e9)] -> xpath: //div[@id='searchform']]] -> css selector: input[name='q']
Now we have to apply all the reverse-engineering to build the element again from this String. Thanks to Reflection API in Javawhich can help us to dynamically execute the code to build the element.
Here is the final implementation:
WebElement refreshedElement = StaleElementUtils.refreshElement(elem2);
This refreshElement method will check if the element is stale then it will re-fetch the element from the dom. So for all the data grid elements which can get stale anytime, we can use this method as a precautionary measure to avoid stale element exception.
Please feel free to share your thoughts on my approach and would love to know, how you have handled this interesting exception.

import com.sahajamit.selenium.driver.DriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class StaleElementUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(UIUtils.class);
public static WebElement refreshElement(WebElement elem){
if(!isElementStale(elem))
return elem;
Object lastObject = null;
try{
String[] arr = elem.toString().split("->");
List<String> newStr = new ArrayList<String>();
for(String s:arr){
String newstr = s.trim().replaceAll("^\\[+", "").replaceAll("\\]+$","");
String[] parts = newstr.split(": ");
String key = parts[0];
String value = parts[1];
int leftBracketsCount = value.length() - value.replace("[", "").length();
int rightBracketscount = value.length() - value.replace("]", "").length();
if(leftBracketsCount-rightBracketscount==1)
value = value + "]";
if(lastObject==null){
lastObject = DriverManager.getDriver();
}else{
lastObject = getWebElement(lastObject, key, value);
}
}
}catch(Exception e){
LOGGER.error("Error in Refreshing the stale Element.");
}
return (WebElement)lastObject;
}
public static boolean isElementStale(WebElement e){
try{
e.isDisplayed();
return false;
}catch(StaleElementReferenceException ex){
return true;
}
}
private static WebElement getWebElement(Object lastObject, String key, String value){
WebElement element = null;
try {
By by = getBy(key,value);
Method m = getCaseInsensitiveDeclaredMethod(lastObject,"findElement");
element = (WebElement) m.invoke(lastObject,by);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return element;
}
private static By getBy(String key, String value) throws InvocationTargetException, IllegalAccessException {
By by = null;
Class clazz = By.class;
String methodName = key.replace(" ","");
Method m = getCaseInsensitiveStaticDeclaredMethod(clazz,methodName);
return (By) m.invoke(null,value);
}
private static Method getCaseInsensitiveDeclaredMethod(Object obj, String methodName) {
Method[] methods = obj.getClass().getMethods();
Method method = null;
for (Method m : methods) {
if (m.getName().equalsIgnoreCase(methodName)) {
method = m;
break;
}
}
if (method == null) {
throw new IllegalStateException(String.format("%s Method name is not found for this Class %s", methodName, obj.getClass().toString()));
}
return method;
}
private static Method getCaseInsensitiveStaticDeclaredMethod(Class clazz, String methodName) {
Method[] methods = clazz.getMethods();
Method method = null;
for (Method m : methods) {
if (m.getName().equalsIgnoreCase(methodName)) {
method = m;
break;
}
}
if (method == null) {
throw new IllegalStateException(String.format("%s Method name is not found for this Class %s", methodName, clazz.toString()));
}
return method;
}
}


13 comments:

  1. import com.sahajamit.selenium.driver.DriverManager;
    what is this sir?

    ReplyDelete
  2. Can you please share your Drivermanager class which you have imported here.

    ReplyDelete
  3. Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.

    Python Training in electronic city

    DataScience with Python Training in electronic city

    AWS Training in electronic city

    Big Data Hadoop Training in electronic city

    Devops Training in electronic city

    ReplyDelete

    ReplyDelete
  4. AWS Training in Bangalore - Live Online & Classroom
    myTectra Amazon Web Services (AWS) certification training helps you to gain real time hands on experience on AWS. myTectra offers AWS training in Bangalore using classroom and AWS Online Training globally. AWS Training at myTectra delivered by the experienced professional who has atleast 4 years of relavent AWS experince and overall 8-15 years of IT experience. myTectra Offers AWS Training since 2013 and retained the positions of Top AWS Training Company in Bangalore and India.


    IOT Training in Bangalore - Live Online & Classroom
    IOT Training course observes iot as the platform for networking of different devices on the internet and their inter related communication. Reading data through the sensors and processing it with applications sitting in the cloud and thereafter passing the processed data to generate different kind of output is the motive of the complete curricula. Students are made to understand the type of input devices and communications among the devices in a wireless media.

    ReplyDelete
  5. Python is a high-level, interpreted, interactive and object-oriented scripting language. Python is designed to be highly readable. It uses English keywords frequently where as other languages use punctuation, and it h
    as fewer syntactical constructions than other languages.python interview questions and answers


    Hadoop concepts, Applying modelling through R programming using Machine learning algorithms and illustrate impeccable Data Visualization by leveraging on 'R' capabilities.With companies across industries striving to bring their research and analysis (R&A) departments up to speed, the demand for qualified data scientists is rising.
    data science training in bangalore

    ReplyDelete
  6. I am really happy with your blog because your article is very unique and powerful for new reader.
    Selenium Training in Chennai | Selenium Training in Bangalore | Selenium Training in Pune

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. hi, mind sharing com.sahajamit.selenium.driver.DriverManager with us?

    ReplyDelete
  9. Thank you so much for such valuable information sharing. It’s highly appreciated.Interesting and informative article...very useful to me, please keep on updating..

    AWS Training
    AWS Training in Chennai

    ReplyDelete
  10. Thanks you for sharing the article. The data that you provided in the blog is infromative and effectve. Through you blog I gained so much knowledge. Also check my collection at selenium Online Training Blog

    ReplyDelete
  11. Hi Naveen, your explanation is really great, just anted to check, can you explain on StaleElementUtils class and the methods,

    ReplyDelete

Featured Post

How to control Chromedriver using curl

How to control Chromedriver using curl Here is how to use Chromedriver without libraries like  selenium-webdriver . This can be useful ...