Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Robot Framework+Selenium: how to avoid "stale element" error (flaky test)?

I'm using Robot Framework and Selenium to test a website that has a language selector. I need to be able to select a language and then verify the page has actually changed to that language.

Since after choosing a new language the value in the lang attribute of the <html> tag changes, I decided I would use that to validate the language has been successfully changed. But I'm getting flaky results, as my test sometimes passes and sometimes doesn't.

This is the definition of the keyword I'm using:

CHANGE LANGUAGE  
  [Arguments]                        ${lang}
  Select From List By Value          ${LANGUAGE SWITCH}  ?hl=${lang}
  Wait Until Page Contains Element   css=html
  ${doc lang}                        Get Element Attribute  css=html@lang
  Should Be True                     '${doc lang}'=='${lang}'   timeout=15s

Since I have to execute this keyword quite a few times (one per each available language), I keep getting pretty often the dreaded "stale element" error: | FAIL | stale element reference: element is not attached to the page document.

I read through this article and a few other questions in here and understand this could happen if an element reference is updated after obtaining it. However, I'm not sure how exactly I should modify my keyword to avoid getting this error.

like image 444
Floella Avatar asked Jan 25 '18 18:01

Floella


People also ask

What causes Stale element reference Exception?

The stale element reference error is a WebDriver error that occurs because the referenced web element is no longer attached to the DOM. Every DOM element is represented in WebDriver by a unique identifying reference, known as a web element.

What is stale element exception in selenium Python?

It means the element is no longer in the DOM, or it changed. The following code will help you find the element by controlling and ignoring StaleElementExceptions and handling them just like any other NoSuchElementException. It waits for the element to NOT be stale, just like it waits for the element to be present.


3 Answers

Using the information that everyone has so kindly provided, I may have found a potential fix (not sure if it's robust enough not to throw the exception anymore, but after a few test runs they have all passed): I added a "Wait Until Keyword Succeeds" and moved the language validation to a new keyword:

VALIDATE PAGE LANGUAGE
  [Arguments]                        ${lang}
  ${doc lang}                        Get Element Attribute  css=html@lang
  Should Be True                     '${doc lang}'=='${lang}'

CHANGE LANGUAGE  
  [Arguments]                        ${lang}
  Select From List By Value          ${LANGUAGE SWITCH}  ?hl=${lang}
  Wait For Condition                 return document.readyState=="complete"
  Wait Until Keyword Succeeds        5  5s  VALIDATE PAGE LANGUAGE  ${lang}

Then I call this "CHANGE LANGUAGE" keyword as many times as languages I need to test.

I added this as an answer instead of a comment so I could show the code in a more readable way.

like image 163
Floella Avatar answered Oct 09 '22 04:10

Floella


In order to wait for a page to be ready to test after a user action (clicking a link or button, for example), the algorithm I've found that seems to be almost bulletproof is this:

  1. get a reference to the html element
  2. perform the action that will cause the page to change (eg: click a link or button)
  3. wait for the html element to go stale - this signals that the refresh has started
  4. wait for document.readyState to be "complete"

Step 4 may not be necessary, but it doesn't hurt.

This has worked extremely well for my team. This can still fail since you might have some async javascript that runs after document.readyState has been set, but there's simply no generic solution to that.

If your pages have a bunch of async javascript, you'll have to come up with your own scheme to know when the page is finally ready to be tested. For example, the last job to complete could set a flag, or you could wait until there are no pending async jobs, etc.

I'm not sure if you can do the above with robot keywords since it relies on the selenium staleness_of condition. It's pretty easy to implement in python, though.

The inspiration for this solution came from this blog post: How to get Selenium to wait for page load after a click

If you use my page object library for robot, this is built-in as a context manager.

like image 33
Bryan Oakley Avatar answered Oct 09 '22 03:10

Bryan Oakley


As highlighted, a Stale Element error typically means that between the element retrieval and the corresponding action the element changed. Often this is due to a page refresh.

For this reason it is important to invest in a robust waiting approach. Not guessing that your page/application has completed loading, but instead knowing it has completed. This will not only prevent Stale Element errors but also speed up your tests as you're not unnecessarily waiting.

As the Get Element Attribute ${DOCUMENT}@lang is causing the stale element error and the Select From List By Value ${LANGUAGE SWITCH} ?hl=${lang} is causing the page refresh, then that leaves the Wait Until Page Contains Element html as your waiting approach.

As the <html> tag is always present and the first to be loaded in the DOM, this is not the best tag to wait for. I'd advise something unique for the loaded page or the last element of that page. Though I have to stress that this still constitutes to guessing the page has loaded.

It is better to invest in a robust waiting approach. Especially if your application is using a framework like Angular, React or jQuery then you have several Javascript markers to help you with this. For some frameworks there are even custom Robot Framework Libraries that support their specific markers.

Should your application not use a framework, then talk to your developers and have them develop this for you. Simplest would be a visible spinner, but a Javascript function that returns True will work just as well.

like image 2
A. Kootstra Avatar answered Oct 09 '22 04:10

A. Kootstra