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.
I’ve personally never used this.
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 TestObject
s, and Selenium uses WebElement
s…!
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 StaleElementReferenceException
s that I kept randomly facing, but the ElementNotInteractibleException
s as well!