Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange timeout with ScalaTest's Selenium DSL

I'm writing Selenium tests with ScalaTest's Selenium DSL and I'm running into timeouts I can't explain. To make matters more complicated, they only seem to happen some of the time.

The problem occurs whenever I access an Element after a page load or some Javascript rendering. It looks like this:

click on "editEmployee"
eventually {
  textField(name("firstName")).value = "Steve"
}

My PatienceConfig is configured like this:

override implicit val patienceConfig: PatienceConfig =
    PatienceConfig(timeout = Span(5, Seconds), interval = Span(50, Millis))

The test fails with the following error:

- should not display the old data after an employee was edited *** FAILED ***
  The code passed to eventually never returned normally. Attempted 1 times over 10.023253653000001 seconds.
  Last failure message: WebElement 'firstName' not found.. (EditOwnerTest.scala:24)

It makes sense that it doesn't succeed immediately, because the click causes some rendering, and the textfield may not be available right away. However, it shouldn't take 10 seconds to make an attempt to find it, right?

Also, I find it very interesting that the eventually block tried it only once, and that it took almost precisely 10 seconds. This smells like a timeout occurred somewhere, and it's not my PatienceConfig, because that was set to time out after 5 seconds.

With this workaround, it does work:

click on "editEmployee"
eventually {
  find(name("firstName")).value // from ScalaTest's `OptionValues`
}
textField(name("firstName")).value = "Steve"

I did some digging in the ScalaTest source, and I've noticed that all calls that have this problem (it's not just textField), eventually call webElement at some point. The reason why the workaround works, is because it doesn't call webElement. webElement is defined like this:

def webElement(implicit driver: WebDriver, pos: source.Position = implicitly[source.Position]): WebElement = {
  try {
    driver.findElement(by)
  }
  catch {
    case e: org.openqa.selenium.NoSuchElementException =>
      // the following is avoid the suite instance to be bound/dragged into the messageFun, which can cause serialization problem.
      val queryStringValue = queryString
      throw new TestFailedException(
                 (_: StackDepthException) => Some("WebElement '" + queryStringValue + "' not found."),
                 Some(e),
                 pos
               )
  }
}

I've copied that code into my project and played around with it, and it looks like constructing and/or throwing the exception is where most of the 10 seconds are spent.

(EDIT Clarification: I've actually seen the code actually spend its 10 seconds inside the catch block. The implicit wait is set to 0, and besides, if I remove the catch block everything simply works as expected.)

So my question is, what can I do to avoid this strange behaviour? I don't want to have to insert superfluous calls to find all the time, because it's easily forgotten, especially since, as I said, the error occurs only some of the time. (I haven't been able to determine when the behaviour occurs and when it doesn't.)

like image 993
jqno Avatar asked Sep 13 '17 05:09

jqno


1 Answers

It is clear that the textField(name("firstName")).value = "Steve" ends up calling the WebElement as you have found out. Since the issue in the op is happening where ever web elements are involved (which in turn implies that webdriver is involved), I think it is safe to assume that the issue is related to the implicit wait on the Web driver.

implicitlyWait(Span(0, Seconds))

The above should ideally fix the issue. Also, making implicit wait to be 0 is a bad practice. Any web page might have some loading issues. The page load is handled by Selenium outside its wait conditions. But slow element load (may be due to ajax calls) could result in failure. I usually keep 10 seconds as my standard implicit wait. For scenarios which require more wait, explicit waits can be used.

def implicitlyWait(timeout: Span)(implicit driver: WebDriver): Unit = {
driver.manage.timeouts.implicitlyWait(timeout.totalNanos, TimeUnit.NANOSECONDS)
}

Execution Flow:

name("firstName") ends up having value as Query {Val by = By.className("firstName") }.

def name(elementName: String): NameQuery = new NameQuery(elementName)

case class NameQuery(queryString: String) extends Query { val by = By.name(queryString) }

Query is fed to the textField method which calls the Query.webElement as below.

def textField(query: Query)(implicit driver: WebDriver, pos: source.Position): TextField = new TextField(query.webElement)(pos)

sealed trait Query extends Product with Serializable {

    val by: By

    val queryString: String

    def webElement(implicit driver: WebDriver, pos: source.Position = implicitly[source.Position]): WebElement = {
      try {
        driver.findElement(by)
      }
      catch {
        case e: org.openqa.selenium.NoSuchElementException =>
          // the following is avoid the suite instance to be bound/dragged into the messageFun, which can cause serialization problem.
          val queryStringValue = queryString
          throw new TestFailedException(
                     (_: StackDepthException) => Some("WebElement '" + queryStringValue + "' not found."),
                     Some(e),
                     pos
                   )
      }
    }
  }
like image 84
Sighil Avatar answered Sep 30 '22 02:09

Sighil