groovyexceptiongebstaleobjectstate

General Problems With Geb (StaleElementReferenceException & Wait Timeouts)


According to the "Book of Geb" I started to map our portal's web pages. I prefer to use variables defined within static content closure block and accessing them afterwards in page methods:

static content = {
    buttonSend { $("input", type: "submit", nicetitle: "Senden") }
}
def sendLetter() {
    waitFor { buttonSend.isDisplayed() }
    buttonSend.click()
}

Unfortunately, sometimes I get an Geb waiting timeout exception (after 60 secs) or even worse I receive the well known "StaleElementReferenceException".

I could avoid the wait timeout when using "isEnabled" instead of "isDisplayed" but for the "StaleElementReferenceException" I could only apply the below solution:

def sendLetter() {
    waitFor { buttonSend.isEnabled() }
    try {
        buttonSend.click()
    } catch (StaleElementReferenceException e) {
        log.info(e.getMessage())
        buttonSend.click()
    }
}

I guess, this solution is not really nice but I could not apply an explicitly wait as described in another article. Thus, I have some general questions:

I would appreciate every hint which helps to understand or to solve this issue. The best would be to have a simple code example since I'm still a beginner. Thank you!


Solution

  • In addition to twinj's answer, I would like to point out a couple of other workarounds in case you encounter a StaleElementReferenceException.

    1. Often times I find it is better to write out your selector manually rather than rely on the contents as defined in the page. Even though your page contents should not be cached by default, they still manage to slip away from me at times. This is particularly prevalent when dealing with dynamic content or iterations.

      Ex: Let's say we want to click an element from a dynamically created dropdown.

      Typically you might want to do something like...

      static content = {
           dropdown { $("#parentDiv").find("ul") }
      }
      
      void clickDesiredElement(String elementName) {
           dropdown.click()
           def desiredElement = dropdown.find("li", text:elementName)
           waitFor { desiredElement.displayed }
           desiredElement.click()
      }
      

      If this doesn't work, try getting rid of the contents altogether, and writing out the selector manually...

      void clickDesiredElement(String elementName) {
           $("#parentDiv").find("ul").click()
           def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName)
           waitFor { desiredElement.displayed }
           desiredElement.click()
      }
      

      In really nasty cases, you may have to use a manual timer, as pointed out in this answer, and your code may look like this...

      void clickDesiredElement(String elementName) {
           $("#parentDiv").find("ul").click()
           sleepForNSeconds(2)
           def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName)
           waitFor { desiredElement.displayed }
           desiredElement.click()
      }
      

      Keep in mind this is a workaround :)

    2. For large iterations and convenient closure methods, such as each{} or collect{}, you may want to add a waitFor{} in each iteration.

      Ex: Let's say we want to get all rows of a large table

      Typically you might want to do something like...

      def rows = $("#table1").find("tr").collect {
         [
            name: it.find("td",0),
            email: it.find("td",1)
         ]
      }
      

      Sometimes I find myself having to do this iteratively, along with a waitFor{} between each iteration in order to avoid a StaleElementReferentException. It might look something like this...

      def rows = []
      int numRows = $("#table1").find("tr").size()
      int i
      for(i=0; i < numRows; i++) {
          waitFor {
            def row = $("#table1").find("tr",i)
            rows << [
                name: row.find("td",0),
                email: row.find("td",1)
            ]
          }
      }