Ways to fix the StaleElementReferenceException (and similar exception)

What is StaleElementReferenceException?

StaleElementReferenceException is exactly what it sounds like: an exception that gets raised when a reference to an element becomes stale . This can happen multiple ways:

  • you navigate away from the page, or the element just completely disappears from the DOM
  • (more commonly) some process removed the element from the DOM, and re-creates it with same selectors (e.g. id, class, data-* attributes) but with more stuff

This exception has been a pain in my ass since, well, forever… I couldn’t reliably reproduce it, couldn’t effectively remedy it, and have largely been forced to live with it…

…until now!

OK, now how do I put a stop to it?!

Case: The element disappears completely from the DOM

This one is trivial: just WebUI.waitForElementNotPresent(testObject, timeOut).

If you need to reference it again, create a function (method or callback function) that will return it. This is straightforward with the Page Object Model: you may also create a new instance of your page class, when you need to reference anything on it again for good measure…

Case: Element briefly disappears from the DOM and then re-appears (with more/different stuff but same selectors)

This one is more complicated.

The lazy way is to do some hard-coded WebUI.delay() or sleep(), however you probably should avoid doing that.

One strategy: waiting for the element to disappear and then appear, using the WebUI keywords

In the post I just linked, there is one way, in my custom wait util method, that we wait for the element:

WebUI.waitForElementNotPresent(testObject, 1, FailureHandling.OPTIONAL);

WebUI.waitForElementPresent(testObject, timeOut);

This approach looks robust, but could be iffy…What if your element stays present after one second, and then some process in the AUT “flickers” it?

Second strategy: use Smart Wait feature

I haven’t had to explicitly use this, but there’s this guy way smarter than me who shows how it is used. He essentially turns it on when the issue may arise, and then turns it back off when he’s done with it.

Third strategy: go back to basics (and use Selenium WebDriver)

Katalon Studio is built on top of Selenium WebDriver (and many other open-source technologies). In fact, if you look under the hood at many of their WebUI keywords, you will find some Selenium code.

Motivation

For over a year, I have been randomly facing Stale Element Reference Exception while interacting with a Zoho Creator page. The page is the Create Retainer Schedule page (part of the app under test that I am mainly tasked with testing/maintaining test code infrastructure on). It has this section as follows:

Selecting a Retainer Model Type, will control which section view spawns. We have selection strategy for the first text field (or the nth one for that matter), no matter what section view spawned…

	public TestObject getSMDShareAmountField(RetainerModelType type, int n) {
		return new TestObject("Page_Retainer Schedule/SMD Share Details section/#${n} Share Amount field")
				.addProperty("xpath",
				ConditionType.EQUALS,
				"(//input[@id='${this.getFieldIDPrefix(type)}_Details-${this.getFieldIDSuffix(type)}'])[${n}]");
	}

	public TestObject getCapAmountField(RetainerModelType type, int n) {
		return new TestObject("Page_Retainer Schedule/SMD Share Details section/#${n} Cap Amount field")
				.addProperty("xpath",
				ConditionType.EQUALS,
				"(//input[@id='${this.getFieldIDPrefix(type)}_Details-Cap_Amount'])[${n}]");
	}

	private String getFieldIDPrefix(RetainerModelType type) {
		switch(type) {
			case RetainerModelType.MEMBER_FIXED:
			case RetainerModelType.PERCENT_SINGLE:
				return "zc-Cap";
			case RetainerModelType.PERCENT_MULTIPLE:
			case RetainerModelType.MEMBER_VAR:
				return "zc-Tier";
		}

		throw new IllegalArgumentException("ID Suffix for retainer model type '${type.textValue}' not yet implemented");
	}

	private String getFieldIDSuffix(RetainerModelType type) {
		switch(type) {
			case RetainerModelType.PERCENT_MULTIPLE:
				return "Organization_Share";
			case RetainerModelType.PERCENT_SINGLE:
				return "SMD_Share_Percent";
			case RetainerModelType.MEMBER_VAR:
				return "Organization_Amount";
			case RetainerModelType.MEMBER_FIXED:
				return "SMD_Share_Amount";
		}

		throw new IllegalArgumentException("ID Suffix for retainer model type '${type.textValue}' not yet implemented");
	}

however, no matter what strategy I tried (except the Smart Wait feature), I would still face the issue from time to time…!

Getting some inspiration from this article, I have decided that, maybe, the answer lies in getting down to Selenium WebDriver brass tacks…!

But we’re dealing with TestObjects, and Selenium uses WebElements…!

I know. Selenium also works with By selectors, which is what we’ll use here…

To skip this doozy, I ask Phind AI how we can get xpath selector from the Test Object, and to write the method for me:

package customKeywords

import org.openqa.selenium.By
import org.openqa.selenium.WebDriver
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait

import com.kms.katalon.core.testobject.TestObject
import com.kms.katalon.core.webui.driver.DriverFactory

class ExplicitWaitKeyword {
    /**
    * Wait for the element to be present in the DOM, and displayed on the screen
    * @param to TestObject
    * @param timeOutInSeconds The time in seconds to wait until returning a failure
    * @return Boolean true if the element is found and visible, false otherwise
    */
    @Keyword
    def waitForElement(TestObject to, int timeOutInSeconds) {
        WebDriver driver = DriverFactory.getWebDriver()
        String xpathValue = to.findPropertyValue('xpath', false)
        WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds)
        return wait.until(ExpectedConditions.refreshed(ExpectedConditions.presenceOfElementLocated(By.xpath(xpathValue)))) != null
    }
}

NOTE: we use TestObject.findPropertyValue('selectorStrategy', false) to get the property-based selector strategy from the TestObject.

When I went to try this, however, it still didn’t work for me…

…so I dusted myself off, adapt the code to fit my style of coding, and try a different strategy:

…waiting for the expected condition of the element being clickable

This is great for:

  • text fields
  • buttons
  • links
  • …anything clickable…

I present the following from my GeneralWebUIUtils class:

	/**
	 * SOURCE: https://toolsqa.com/selenium-webdriver/what-is-stale-element
	 * Wait for the element to be present in the DOM, and displayed on the screen
	 * @param to TestObject
	 * @param timeOutInSeconds The time in seconds to wait until returning a failure
	 * @return Boolean true if the element is found and visible, false otherwise
	 */
	 @Keyword
	 public static boolean ExplicitlyWaitForElement(TestObject to, int timeOutInSeconds) {
		 String xpathValue = to.findPropertyValue('xpath', false)
		 return new WebDriverWait(DriverFactory.getWebDriver(), timeOutInSeconds)
		 	.ignoring(StaleElementReferenceException.class)
		 	.until(ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpathValue)))) != null
	 }

I use this GeneralWebUIUtils.ExplicitlyWaitForElement(), and it seems to handle not only the StaleElementReferenceExceptions that I kept randomly facing, but the ElementNotInteractibleExceptions as well!

6 Likes

Awesome! Thank you for your great contribution. I believe that this can help lots of others. Happy new year!

Hi Michael, :wave:

Thank you very much for your informative and helpful topic. We will be appending some tags to it to make it easier for others to search for your topic :+1: