Stale Element Reference Exception in Firefox (headless mode)

Hello friends,

i get a “StaleElementReferenceException” in several TestCases that are running on our Jenkins / Linux Environment in Firefox headless mode.
I’m trying to figure out what I can do since days but I still have no solution for this issue.

I found already some Topics over here, like:

@Brandon_Hein wrote the following to avoid these exception:

1.) Ensure that EVERY element reference is 1-time use . In other words, locate the element, use it, and DO NOT use that same reference again. If you want to reference that element again, you should re-locate it in the HTML every single time.
2.) Ensure that you have proper wait conditions in place for every action you take in your application. Always wait for the page to be “static” prior to locating elements and using them.

I think I have already implemented my test case according to these guidelines.

So here is my error log:


Here the rest that didn’t fit in the first screenshot:

As you can see in the first screenshot, there are 3 (sub-) steps that are passed, before the step failed, where it tries to click the element.
This comes from my own click-Method I wrote:

class TestUtilities {

    static void click(TestObject testObject) {
    	WebUI.scrollToElement(testObject, GlobalVariable.timeout)
    	WebUI.waitForElementClickable(testObject, GlobalVariable.timeout)
    	WebUI.waitForElementPresent(testObject, GlobalVariable.timeout)
    	WebUI.click(testObject)
    }

    static TestObject button(String buttonText) {
    	TestObject testObject = new TestObject()
    	testObject.addProperty("tag", ConditionType.EQUALS, "button")
    	//testObject.addProperty("type", ConditionType.EQUALS, "button")
    	testObject.addProperty("text", ConditionType.EQUALS, buttonText)
    	return testObject
    }

    static TestObject tab(String tabName) {
	    TestObject testObject = new TestObject()
	    String xpath = new String()
    	xpath = "//div[starts-with(@id, 'mat-tab-label-')]"
    	testObject.addProperty("text", ConditionType.EQUALS, tabName)
    	testObject.addProperty("xpath", ConditionType.EQUALS, xpath)
    	return testObject
    }
}

In the second method “button” you can see how I create button-Objects and or tab-Objects (third method).

So the test step looks like this:

TestUtilities.click(TestUtilities.button('Submit'))

or

TestUtilities.click(TestUtilities.tab('Person'))

These steps always pass in GUI-mode on my local machine. And also passes in about 99/100 cases in headless mode. But at some points it crashes into this StaleElement exception.
And I have no more Idea what I can do.

As you can see I use already several “waitFor”-commands.
I also set up the xpath for my tab-Element containg the “starts-with”-function because the id is newly generated at every session and looks like mat-tab-label-2, mat-tab-label-5, mat-tab-label-1 or whatever… so the number at the end is not always the same.

In the html-test-report I get via Mail, there is also a screenshot attached right under the Exception-log.
There I can see that the Tab or Button is actually visible at this point.

Any Idea on what I can do?
Thanks a lot and have a nice day!

Simon

Btw. as our web-application uses angular I also had included the WebUI.waitForAngularLoad() Keyword in my click-method. This is why you can read in the error-log:

WARNING This page doesn't use Angular.

But I removed it again, cause it’s not working.

Firstly, thanks for writing a very clear report. :clap:

Taken at face value, I agree this problem is confounding. It certainly seems as though the test should work 100%. That said…

You are falling foul of Brandon’s first point – you’re reusing the same reference for at least 3 steps. But having said that, what you’re doing looks fine to me.

There is however an odd sequence in that there’s not much point checking for presence after checking for clickability. You can probably remove the presence check (though that is unlikely to help with your issue).

I am concerned about the “caused by” statement in the error. It’s not talking about a button, it mentions a <div> element. Does that div host the button? Does it perhaps contain the text “Submit” (implying you’re finding that div, not the button) ? Doubtful, but I can’t see your HTML.

I could certainly “fix” this by using JavaScript, but that would cause quite some upheaval in your (well-written) code. For now, I’ll leave this and invite @Brandon_Hein to take a look.

1 Like

You’re right. The example error-log was not about a button. I run a TestSuite with several Tests.
In this case it’s about the “tab” - in other cases it was about a “button”. But same exception. I thought it would be too much to publish every log of every TestCase.

I checked the html with my browser developer-tools and it looks like this:

<div class="mat-tab-label-container">
        <div role="tablist" class="mat-tab-list" style="transform: translateX(0px);">
            <div class="mat-tab-labels">
                <div role="tab" mattablabelwrapper="" mat-ripple="" cdkmonitorelementfocus="" class="mat-ripple mat-tab-label mat-focus-indicator ng-star-inserted" id="mat-tab-label-0-0" tabindex="-1" aria-posinset="1" aria-setsize="4" aria-controls="mat-tab-content-0-0" aria-selected="false" aria-disabled="false">
                    <div class="mat-tab-label-content"><!---->Tab-1-Name<!----></div>
                </div>
                <div role="tab" mattablabelwrapper="" mat-ripple="" cdkmonitorelementfocus="" class="mat-ripple mat-tab-label mat-focus-indicator mat-tab-label-active ng-star-inserted" id="mat-tab-label-0-1" tabindex="0" aria-posinset="2" aria-setsize="4" aria-controls="mat-tab-content-0-1" aria-selected="true" aria-disabled="false">
                    <div class="mat-tab-label-content"><!---->Tab-2-Name<!----></div>
                </div>
                <div role="tab" mattablabelwrapper="" mat-ripple="" cdkmonitorelementfocus="" class="mat-ripple mat-tab-label mat-focus-indicator ng-star-inserted" id="mat-tab-label-0-2" tabindex="-1" aria-posinset="3" aria-setsize="4" aria-controls="mat-tab-content-0-2" aria-selected="false" aria-disabled="false">
                    <div class="mat-tab-label-content"><!---->Tab-3-Name<!----></div>
                </div>
                <div role="tab" mattablabelwrapper="" mat-ripple="" cdkmonitorelementfocus="" class="mat-ripple mat-tab-label mat-focus-indicator ng-star-inserted" id="mat-tab-label-0-3" tabindex="-1" aria-posinset="4" aria-setsize="4" aria-controls="mat-tab-content-0-3" aria-selected="false" aria-disabled="false">
                    <div class="mat-tab-label-content"><!---->Tab-4-Name<!----></div>
                </div><!---->
            </div><mat-ink-bar class="mat-ink-bar" style="visibility: visible; left: 135px; width: 146px;"></mat-ink-bar>
        </div>
  </div>

“Tab-1-Name” is the label / name of the tab and the String that I pass into my method:

TestUtilities.click(TestUtilities.tab('Tab-1-Name'))

(Because of contracts with our customer I need to be careful with publishing information - so I changed the labels over here)

Here is the error-log of a TestCase where the same exception was thrown, but because of a button:


At the point, that I marked with the yellow “1”, was called a method that summarized several TestSteps,
including a click on a tab (to switch the tab) and a click on a button afterwards.
So what I covered with black, looked like this:

...katalon.utils.Class.method()

public class Class {
    
    /**
     * method to cover a bunch of TestSteps
     */
    method() {
        TestUtilities.click(TestUtilities.tab('Tab-1-Name'))
        TestUtilities.click(TestUtilities.button('a button'))
    }
}

That’s why you can see the 4 passed steps in the beginning of the log.
These are the 4 keywords of my click-method, what means the tab has been clicked successfully this time. But not the button in the next step.
tab-success

Make sure you wait for any server activity (or even browser JavaScript activity) to finish AFTER you click the tab. Only THEN should you create the testObject for the button.

Same for the tab - whatever comes before it, make sure the browser is ready before you click the tab.

By the way, if you’re calling your method method() I advise you to change the name. It would be like me calling you “person”, instead of Simon.

1 Like

Ah sorry, I thought, this would be clear because of my mention, that I have to hide information.
Of course I’m using a better name for this method. I only changed it for this post.

Any hint how I can do this? I don’t wanna use a delay everytime.
(Of course I’m already about to google this… but any help would be nice)

1 Like

It’s like the advice that Brandon already gave above, get a reference to the object, again and again, every time.

My concern is, after switching tab, there is something “happening” (server call, JavaScript call, whatever) that takes a little time. That little time is easily swamped by the speed of the test.

i agree about adding fixed delays, but right now, you have a problem. Your first task is to find out where that problem is. Add a stupid delay, like five seconds.

// wait 5 seconds OR wait until tab is visible
// click the tab
// wait 5 seconds OR wait until button is visible
// Click button

something like that…

If the problem goes away, then we can look at optimizing it to something more sensible.

1 Like

I agree with @Russ_Thomas’s suggestion of adding a hard wait just to verify you have a timing issue. A lot of headache and confusion can be avoided simply by narrowing the field of suspects, and this will tell you whether your issue is a timing issue or not. If waiting 5 seconds causes the issue to magically disappear, you know that you need to add a proper wait condition somewhere, and to not spend time researching other possible causes. I suspect it is a timing issue, as is usually the case with intermittent issues like this.

A couple other notes:

static void click(TestObject testObject) {
    WebUI.scrollToElement(testObject, GlobalVariable.timeout)
    WebUI.waitForElementClickable(testObject, GlobalVariable.timeout)
    WebUI.waitForElementPresent(testObject, GlobalVariable.timeout)
    WebUI.click(testObject)
}

You are not actually violating the 1 time use rule here. When you call these built-in Katalon keywords, the studio actually re-locates these elements for each of these calls (if you’re at all curious, here’s the source for these keywords: https://github.com/katalon-studio/katalon-studio-testing-framework/tree/master/Include/scripts/groovy/com/kms/katalon/core/webui/keyword/builtin, although this documentation is not current). For example, here’s Katalon’s scroll keyword:

public void scrollToElement(TestObject to, int timeOut, FailureHandling flowControl) throws StepFailedException {
        WebUIKeywordMain.runKeyword({
            boolean isSwitchIntoFrame = false
            try {
                WebUiCommonHelper.checkTestObjectParameter(to)
                timeOut = WebUiCommonHelper.checkTimeout(timeOut)
                isSwitchIntoFrame = WebUiCommonHelper.switchToParentFrame(to)

                // ***** element reference created here!!! *****
                WebElement webElement = WebUIAbstractKeyword.findWebElement(to)

                logger.logDebug(MessageFormat.format(StringConstants.KW_LOG_INFO_SCROLLING_TO_OBJ_X, to.getObjectId()))
                ((JavascriptExecutor) DriverFactory.getWebDriver()).executeScript("arguments[0].scrollIntoView();", webElement)
                logger.logPassed(MessageFormat.format(StringConstants.KW_LOG_PASSED_SCROLLING_TO_OBJ_X, to.getObjectId()))
            } finally {
                if (isSwitchIntoFrame) {
                    WebUiCommonHelper.switchToDefaultContent()
                }
            }
        }, flowControl, true, (to != null) ? MessageFormat.format(StringConstants.KW_MSG_CANNOT_SCROLLING_TO_OBJ_X, to.getObjectId())
        : StringConstants.KW_MSG_CANNOT_SCROLLING_TO_OBJ)
    }

As you can see, it’s getting the element reference with your test object using:

WebElement webElement = WebUIAbstractKeyword.findWebElement(to)

The same thing happens when you then subsequently call other WebUI keywords, including click():

public void click(TestObject to, FailureHandling flowControl) throws StepFailedException {
        WebUIKeywordMain.runKeyword({
            boolean isSwitchIntoFrame = false
            try {
                WebUiCommonHelper.checkTestObjectParameter(to)
                isSwitchIntoFrame = WebUiCommonHelper.switchToParentFrame(to)

                // ***** element reference created here!!! *****
                WebElement webElement = WebUIAbstractKeyword.findWebElement(to)

                logger.logDebug(MessageFormat.format(StringConstants.KW_LOG_INFO_CLICKING_ON_OBJ, to.getObjectId()))
                webElement.click()
                logger.logPassed(MessageFormat.format(StringConstants.KW_LOG_PASSED_OBJ_CLICKED, to.getObjectId()))
            } finally {
                if (isSwitchIntoFrame) {
                    WebUiCommonHelper.switchToDefaultContent()
                }
            }
        }, flowControl, true, (to != null) ? MessageFormat.format(StringConstants.KW_MSG_CANNOT_CLICK_ON_OBJ_X, to.getObjectId())
        : StringConstants.KW_MSG_CANNOT_CLICK_ON_OBJ)
    }

@duyluong if you can confirm this is still how these keywords basically function, it would be appreciated. I don’t think you expose your source code publicly anymore, but I may just be missing it.

In any case, 2 subpoints regarding your keyword:

1.) I agree with Russ, no need to do waitForElementClickable() and waitForElementPresent(). An element must be present to be clickable, so the second wait is redundant.

2.) Can I ask why you need to scroll to the element before clicking? As far as your test execution is concerned, you shouldn’t need to do this. I could understand if you are doing it for the purpose of getting screenshots, but other than that I wouldn’t use it. It’s just another place where things may go wrong. For example, if you view the HTML for the button/tab you intend to click when the element is NOT in the visible window, is it different when you then scroll it into view? Can you try commenting the scroll out and running it?

Try adding in the waits as mentioned, and comment out the scroll, and let us know if either of those help…

Finally, as a nuclear “solution”, you can do something like this:

static void click(TestObject testObject) {
    try {
        WebUI.scrollToElement(testObject, GlobalVariable.timeout)
        WebUI.waitForElementClickable(testObject, GlobalVariable.timeout)
        WebUI.waitForElementPresent(testObject, GlobalVariable.timeout)
        WebUI.click(testObject)
    } catch (StaleElementReferenceException e) {
        click(testObject)
    }
}

But this is a simply a bandage for the problem, not a fix.

1 Like

Also, nice job documenting your issue. I wish more users were this thorough with describing their problem.

2 Likes

@Russ_Thomas & @Brandon_Hein
Wow! Thank you so much for your effort so far.

Of course you’re totally right about the delay. I will try all of this.
But right now I have to wait until a bug in our application has been fixed :smiley:

1 Like

Thanks. But really, there’s no “wow” about it. The level of help you’ve received is a direct reflection of the level of detail you provided above (a lesson for other users, as I’m sure Brandon would agree).

:heart:

After finally implmenting some delays at specific points and also this try / catch bandage :smiley: I have no more StaleElementReferenceExceptions in these TestCases.
Big thanks again to both of you!!

1.) I agree with Russ, no need to do waitForElementClickable() and waitForElementPresent(). An element must be present to be clickable, so the second wait is redundant.

@Brandon_Hein I was just not sure which of these wait-methods I should use. So which is working better, or perhaps there is even one, that is not working properly, like the “whaitForAngularLoad”.
That’s why I used both of them, and in fact I forgot that there is even another “waitForElementVisible()”.
And I’m still not sure which of them to use, when I want to wait for an Element to be accessible, instead of using delays. Probably none of them is working, as I still got the Exception. (for our angular-application, perhaps for other web-apps these are working better)

Can I ask why you need to scroll to the element before clicking?

We had many elements in our Application where we had to scroll to reach them.
And as I couldn’t see the screen in headless mode, I just build in a scrollTo before every click to be sure I reach the element. I thought it wouldn’t be problem even if scrolling isn’t necessary.

Thanks. But really, there’s no “wow” about it. The level of help you’ve received is a direct reflection of the level of detail you provided above (a lesson for other users, as I’m sure Brandon would agree).

:heart:

There’s nothing wrong with your reasoning, so as a bonus, you could go this route:

static void click(TestObject testObject, boolean withScroll = false) { 
  if(withScroll) {
   WebUI.scrollToElement(testObject, GlobalVariable.timeout) 
  }
 ...
}

And make the call click(to, true) when scrolling is required and just click(to) when it’s not.

Lastly, to aid others reading your code (or even your future self) who might wonder about that odd true that appears sometimes and not other times which forces them to lookup the method to figure out what’s going on…

Add a static “global” somewhere:

static boolean WITHSCROLL = true

Then call your click method like this:

click(to, WITHSCROLL)

The benefit of course is twofold – you’re moving the decision to scroll (or not) to the calling context, (the “call site”) and out of the called method, and you can see what the call is doing at a glance.

enjoy :slight_smile:

1 Like

That’s a cool Idea!
I will take it. :slight_smile:
Wish I could mark more than one post as solution, as you both helped me a lot!

And if I use the boolean as the first argument, I can even replace the concerning method-calls easily with notepad++'s replace function.

TestUtilities.click(OneOfManyDifferentTestObjects)

image

With the TestObject in first place this wouldn’t be possible, as I am calling this too often, with many different objects.

That’s fine, but for me that would “read weird” as I am used to “old” tech where the optional args start from the right (but don’t let my baggage lumber you :wink: ).

Don’t worry about it. We like the challenges first; the solutions come along when they do – they’re not the “why we do this thing”, at all :slight_smile: And anyway, Brandon gave you code, I didn’t (you’d hate my code – would cause you to rewrite your entire code-set since I don’t use WebUI for clicks or waits :scream:)