Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Selenium wait for JavaScript to complete?

I am testing a JavaScript API using Java/Selenium.

I issue these commands on Java side,

executor.executeScript("setName('Hello');");
// Thread.sleep(2000);
String output = (String)executor.executeScript("return getName()");
assertEquals(output, "Hello");

In JavaScript side, this is an async function, so it is supposed to take some time and set the variable.

function setName(msg) {
    // simulate async call 
    setName(function(){name = msg;},1000);
}

I need to wait for this async function to complete before moving to the next line in Java, where the assertEquals() is executed.

Is there any way to accomplish this without using Thread.sleep() on Java side.

Thanks

like image 431
ecthelion84 Avatar asked Apr 14 '14 01:04

ecthelion84


1 Answers

You can easily ask Selenium to wait until a particular condition is true; in exactly what you have, one alternative would be:

new FluentWait<JavascriptExecutor>(executor) {
  protected RuntimeException timeoutException(
      String message, Throwable lastException) {
    Assert.fail("name was never set");
  }
}.withTimeout(10, SECONDS)
.until(new Predicate<JavascriptExecutor>() {
  public boolean apply(JavascriptExecutor e) {
    return (Boolean)executor.executeScript("return ('Hello' === getName());");
  }
});

However, then you're basically testing exactly what you just coded, and that has the disadvantage that if name were set before you called setName, you haven't necessarily waited setName to finish. One thing I've done in the past for similar things is this:

In my testing library (which replaces real async calls with setTimeout shims), I have this:

window._junit_testid_ = '*none*';
window._junit_async_calls_ = {};
function _setJunitTestid_(testId) {
  window._junit_testid_ = testId;
}
function _setTimeout_(cont, timeout) {
  var callId = Math.random().toString(36).substr(2);
  var testId = window._junit_testid_;
  window._junit_async_calls_[testId] |= {};
  window._junit_async_calls_[testId][callId] = 1;
  window.setTimeout(function(){
    cont();
    delete(window._junit_async_calls_[testId][callId]);
  }, timeout);
}
function _isTestDone_(testId) {
  if (window._junit_async_calls_[testId]) {
    var thing = window._junit_async_calls_[testId];
    for (var prop in thing) {
      if (thing.hasOwnProperty(prop)) return false;
    }
    delete(window._junit_async_calls_[testId]);
  }
  return true;
}

In the rest of my library, I use _setTimeout_ instead of window.setTimeout whenever I need to set something up to happen later. Then, in my selenium test, I do something like this:

// First, this routine is in a library somewhere
public void waitForTest(JavascriptExecutor executor, String testId) {
  new FluentWait<JavascriptExecutor>(executor) {
    protected RuntimeException timeoutException(
        String message, Throwable lastException) {
      Assert.fail(testId + " did not finish async calls");
    }
  }.withTimeout(10, SECONDS)
  .until(new Predicate<JavascriptExecutor>() {
    public boolean apply(JavascriptExecutor e) {
      return (Boolean)executor.executeScript(
          "_isTestDone_('" + testId + "');");
    }
  });
}

// Inside an actual test:
@Test public void serverPingTest() {
  // Do stuff to grab my WebDriver instance
  // Do this before any interaction with the app
  driver.executeScript("_setJunitTestid_('MainAppTest.serverPingTest');");
  // Do other stuff including things that fire off what would be async calls
  // but now call stuff in my testing library instead.
  // ...
  // Now I need to wait for all the async stuff to finish:
  waitForTest(driver, "MainAppTest.serverPingTest");
  // Now query stuff about the app, assert things if needed
}

Note that you can call waitForTest multiple times if needed, any time you need that test to pause until all async operations are finished.

like image 57
Daniel Martin Avatar answered Oct 02 '22 11:10

Daniel Martin