6

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.

Floella
  • 1,279
  • 1
  • 22
  • 41
  • 2
    You need to make sure the page is loaded before finding the the element and calling any action on it. – lauda Jan 25 '18 at 18:46
  • 1
    Are you getting the exception in the code block you posted or elsewhere? If it's elsewhere, you should also post that code. My guess is that there's another way to write it so that you can avoid the exception. Never reuse variables that were fetched before the language switch. I'm guessing that's where the issue lies without seeing all your code. – JeffC Jan 25 '18 at 19:23
  • Possible duplicate of [StaleElementReference Exception in PageFactory](https://stackoverflow.com/questions/44838538/staleelementreference-exception-in-pagefactory) – undetected Selenium Jan 25 '18 at 19:24
  • Which statement is failing with the stale element exception? I don't see how any of the ones you posted would do that. – Bryan Oakley Jan 25 '18 at 20:40
  • I'm not sure how to check which statement is the one throwing the exception... This is a keyword implementation, not a test case, so I only get a message saying that the test that uses this keyword failed. – Floella Jan 25 '18 at 21:37

4 Answers4

6

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.

Floella
  • 1,279
  • 1
  • 22
  • 41
4

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.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks for your answer :) According to your steps above, I get that I'm doing 1 and 2 when I get the `${LANGUAGE SWITCH}` locator (get a reference to the element) and select value from list (perform action that causes page to change). I'm not exactly sure how to wait for element to go stale. So I tried replacing my "Wait Until Page Contains Element" with `Wait For Condition | return document.readyState=="complete"`, but the result is the same (I get the "stale element" exception). – Floella Jan 25 '18 at 21:52
  • 1
    @Floella: no, you are definitely _not_ doing 1 and 2. You must get a reference (not just wait for it to exist), and do it _before_ changing the language. After changing the language, you then need to wait for the html element to go stale. I don't think it's possible to wait for the element to go stale without writing a custom keyword in python. – Bryan Oakley Jan 25 '18 at 22:03
  • Thanks for the clarification. Then I'm a bit lost regarding how to actually get a reference to an element (I'm using Hi-Fi's Java SeleniumLibrary version). I'm not sure if this is possible, but is there a way I can get a reference to the `` element before refreshing, then getting it again after refreshing and then comparing them both to see if they changed? (when we talk about "references" here I can't help to think about a pointer or at least some kind of unique ID the element has, but maybe I'm mistaken). – Floella Jan 25 '18 at 22:43
  • 1
    @Floella: The python version of the library has a `Get webelement` keyword. It doesn't look like the java version has something similar. You'll probably have to write your own keyword. And yes, the term "reference" in python is somewhat analogous to a pointer. – Bryan Oakley Jan 25 '18 at 23:01
2

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.

A. Kootstra
  • 6,827
  • 3
  • 20
  • 43
  • Thanks for the explanation. As for page loading, I thought that Webdriver automatically waited for it (or that's what I got from reading this https://github.com/robotframework/SeleniumLibrary/issues/107). And since the language attribute I need to check is actually in the html tag, I'm not sure I understand why I need to wait for the whole page to finish loading... Thanks again :) – Floella Jan 25 '18 at 21:36
0

I have created a custom keyword when ever i want to click an element or perform and action i would call this custom keyword . This custom keyword uses the built-in keyword 'Wait Until Keyword Succeeds' which runs the specified keyword and retries if it fails. through this built-in keyword the number of retry's and the time to wait before trying to run the keyword again after the previous run has failed can be configured.

This custom keyword would call another custom keyword where three SeleniumLibrary keywords would be called. The first one would Waits until element locator is enabled keyword, which would wait until the element is not disabled nor read-only. Once the element is enabled then focus would be taken to the element where we are going to perform action. Finally perform the action. All these precautions has prevented me from Stale Element error.

HighLevelKeyword_Identify And Click Element
        [Arguments]    ${locator}
        Wait Until Keyword Succeeds    ${RETRY_ATTEMPTS}    ${RETRY_AFTER}    Identify And Click Element    ${locator}

Identify And Click Element
    [Arguments]    ${locator}
    Wait Until Element Is Enabled    ${locator}
    Set Focus To Element    ${locator}
    Click Element    ${locator}