9

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.)

jqno
  • 15,133
  • 7
  • 57
  • 84
  • Is the page still loading when the timeout occurs? Have you checked the browser console to see if there are any errors? My guess is that the page has a resource which is failing to load, thus preventing the page from reaching the `complete` state expected by the driver. – Florent B. Sep 15 '17 at 22:49
  • But even if that's the case, the `driver.findElement` call should just fail (since the implicit wait is set to 0), it should go to the catch block, and then `eventually` would make it try again, right? – jqno Sep 16 '17 at 09:52
  • the implicit wait setting has no impact and is not related to the waiting for the page to be complete. The driver waits for the page [complete state](https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState) on each command unless the capability `pageLoadingStrategy` is set to `none`. – Florent B. Sep 16 '17 at 12:14
  • If I understood your question correctly, i suggest providing explicit wait set to 0 when checking for the element, because selenium will by default wait for few seconds until it decides that element is not there. So if you don't want the delay best way is to catch timeout exception for the explicit wait ( set to 0) in catch block. – Kushal Bhalaik Sep 16 '17 at 15:09
  • @kushal There is no explicit wait setting to set; this is all handled by ScalaTest. – jqno Sep 18 '17 at 05:47

2 Answers2

1

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
                   )
      }
    }
  }
StrikerVillain
  • 3,719
  • 2
  • 24
  • 41
  • Increasing the `implicitlyWait` timeout does help! Thanks! Sadly I was unable to get to this answer before the bounty expired though. – jqno Sep 25 '17 at 07:20
0

I don't know ScalaTest's specifics, but such strange timeouts usually occur when you're mixing up implicit and explicit waits together.

driver.findElement uses implicit waits internally. And depending on specified explicit waits timeout, you may face with summing both together.

Ideally, implicit waits should be set to 0 to avoid such issues.

Serhii Korol
  • 843
  • 7
  • 15