Found my own solution for WebDriver "StaleElementReferenceException" problem

Currenlty i'm working on a WebDriver framework using Java bindings and i kept facing one problem pretty often - "StaleElementReferenceException". I think this is a big pain in the ass for every UI Automation Engineer who's working with WebDriver. I tried several approaches to fix this problem and finally made one my own that reduced number of this exceptions dramatically.
From official Selenium docs website the problem is described like this :

The most frequent cause of this is that page that the element was part of has been refreshed, or the user has navigated away to another page. A less common, but still common cause is where a JS library has deleted an element and replaced it with one with the same ID or attributes. In this case, although the replacement elements may look identical they are different; the driver has no way to determine that the replacements are actually what's expected.

My case was connected to JS that was updating elements. I decided that in my default methods that i use to interact with website, i need to catch this type of exception and try to find this element again. Unfortunatelly simple

while(i<n) {
try {
findElement
break
}catch StaleElementException{
i++
findElement again
}

principle didn't work for me. So, i had to create some retry mechanism that would store properly number of failures and retrials in the object to be able to properly process try - catch scenario. After some googling and my manual modifications i came up to RetryStrategy class :

public class RetryStrategy {
    public static final int DEFAULT_RETRIES = 3;
    public static final long DEFAULT_WAIT_TIME_IN_MILLI = 1000;

    private int numberOfRetries;
    private int numberOfTriesLeft;
    private long timeToWait;

    public RetryStrategy() {
        this(DEFAULT_RETRIES, DEFAULT_WAIT_TIME_IN_MILLI);
    }

    public RetryStrategy(int numberOfRetries, long timeToWait) {
        this.numberOfRetries = numberOfRetries;
        numberOfTriesLeft = numberOfRetries;
        this.timeToWait = timeToWait;
    }

    /**
     * @return true if there are tries left
     */
    public boolean shouldRetry() {
        return numberOfTriesLeft > 0;
    }

    public void errorOccured() throws Exception {
        numberOfTriesLeft--;
        if (!shouldRetry()) {
            throw new Exception("Retry Failed: Total " + numberOfRetries
                    + " attempts made at interval " + getTimeToWait()
                    + "ms");
        }
        waitUntilNextTry();
    }

    public void errorOccured(Throwable throwable) throws Exception {
        numberOfTriesLeft--;
        if (!shouldRetry()) {
            throw new Exception("Retry Failed: Total " + numberOfRetries
                    + " attempts made at interval " + getTimeToWait()
                    + "ms.\n"
                    + "Error message is : "+throwable.getMessage()
                    + "\n"
                    +"Caused by : "+throwable.getCause());
        }
        waitUntilNextTry();
    }

    public long getTimeToWait() {
        return timeToWait;
    }

    private void waitUntilNextTry() {
        try {
            Thread.sleep(getTimeToWait());
        } catch (InterruptedException ignored) {
        }
    }
}

Now i could write proper retrial process for my default methods and make sure that if StaleElementException is thrown, i will retry several times finding element before surrending for good and all. Here are several examples of interaction with website using retry strategy :

public void clickById(String id) throws Exception {
        RetryStrategy retry = new RetryStrategy();
        while (retry.shouldRetry()){
            try {
                manager.getDriver().findElement(By.id(id)).click();
                break;
            } catch (StaleElementReferenceException e) {
                logger.warning("Got into StaleElementException with id: " + id);
                retry.errorOccured(e);
            }
        }
    }
    
    public void fillFieldById(String id, String value) throws Exception {
        RetryStrategy retry = new RetryStrategy();
        while (retry.shouldRetry()){
            try {
                manager.getDriver().findElement(By.id(id)).clear();
                manager.getDriver().findElement(By.id(id)).sendKeys(value);
                break;
            } catch (StaleElementReferenceException e) {
                logger.warning("Got into StaleElementException with id: " + id);
                retry.errorOccured(e);
            }
        }
	}
    
    public String getValueFromId(String id, String attribute) throws Exception {
        RetryStrategy retry = new RetryStrategy();
        WebElement element = null;
        String result = null;
        while(retry.shouldRetry()) {
            try {
                element = manager.getDriver().findElement(By.id(id));
                result = element.getAttribute(attribute);
                break;
            } catch (StaleElementReferenceException e){
                logger.warning("Got into StaleElement exception with id " + id);
                retry.errorOccured(e);
            }
        }
	return result;
    }

After i started using this methods in my test, i started to get warning messages that StaleException was catched, but after that webdriver was trying to find element again and succeded!. Eventually, tests started to take a little bit more time for execution, but now i got ridd of random StaleElementExceptions which made my tests more reliable in general. Here's what pros and cons i see from current implementation :

  • Pros:
    • Tests become way more reliable - number of random errors dropped dramatickaly in my case
    • Less distraction on failed jobs in case of random errors. Test retries 3 times to find an element before quiting
    • Less frustration about things that i cannot control( it's like you know that some nasty sound will pop next to your ear, but you don't know when and that makes you to remain in stress state)
  • Cons :
    • I don't like that i have to create in each method new instanse of RetryStrategy class. But i don't see right now how i could improve it
    • Time of tests running got bigger. Again, can't come up with any improvement right now to this problem. There are a lot of javascript in the project that makes post request on click and therefore makes my life harder :)
    • This improvement added a lot more lines of code into the framework, but from prospective of the problem i think it is reasonable.

If anyone has some ideas on how to improve this approach or maybe you know better way of handling StaleElementException - let me know and i will definately try it out