JQuery Ajax and Selenium

Today, I’ll update the selenium automation test to wait on ajax calls. We haven’t seen any problems so far because our web application is too fast - not surprising as it is running with all data in memory on the local server.

Let’s start by slowing things down to see the problem. I’ll do this by adding a query parameter to the getUserMap method and using that parameter to request some sleep before returning the User set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ git diff
diff --git a/src/main/java/com/ideoplex/tutorial/MyResource.java b/src/main/java/com/ideoplex/tutorial/MyResource.java
index 76e7981..a69c8ac 100644
--- a/src/main/java/com/ideoplex/tutorial/MyResource.java
+++ b/src/main/java/com/ideoplex/tutorial/MyResource.java
@@ -3,6 +3,7 @@ package com.ideoplex.tutorial;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
@@ -36,8 +37,13 @@ public class MyResource {
@GET
@Path("user/map")
@Produces(MediaType.APPLICATION_JSON)
- public UserMap getUserMap()
+ public UserMap getUserMap(@QueryParam("sleep") int sleep)
{

+ if ( sleep > 0 ) {
+ try {
+ Thread.sleep(sleep);
+ } catch ( InterruptedException e ) { ; }
+ }
return users;
}

A little javascript to transfer the sleep query parameter from the main page to the user/map method and we’re ready to take it for a spin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ git diff
diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html
index 8fe3e39..4cbbd99 100644
--- a/src/main/webapp/index.html
+++ b/src/main/webapp/index.html
@@ -89,9 +89,13 @@

<script type="text/javascript" class="init">
var modeAdd = true;
+ var userMap = "webapi/myresource/user/map";
+ if ( window.location.search.indexOf("sleep=",0) >= 0 ) {
+ userMap = userMap + window.location.search;
+ }
$(document).ready(function() {
$('#users').dataTable({
- "ajax": "webapi/myresource/user/map",
+ "ajax": userMap,
"columns": [
{ "data": "email" },
{ "data": "surname" },

Now run the automation test twice with a 500 millisecond delay in the Chrome browser (I did not see the problem in FireFox, but your mileage may vary). On the second execution, you’ll see an error trying to add a user that already exists.

1
$ mvn test -Dbaseurl=http://localhost:8080/\?sleep=500 -Dbrowser=chrome
 ...
$ mvn test -Dbaseurl=http://localhost:8080/\?sleep=500 -Dbrowser=chrome
 ...
org.openqa.selenium.WebDriverException: unknown error: cannot focus element
  (Session info: chrome=43.0.2357.124)
 ...

Examination of our automation task exposes the problem:

  • I search on the email address to test if the user already exists.
  • With the delay, I run this search before the browser has loaded the data.
  • Therefore, I try to add a user that already exists

I need to wait for the ajax call to complete. Ideally, the developer will add something to explicitly mark the completion, perhaps by using jQuery ajaxStart and ajaxStop. I wasn’t so obliging when I wrote the application, so I’ll rely upon the internal jQuery.active parameter instead.

The jQuery.active parameter is set to 0 when all jQuery ajax call are complete. I test for this condition with a new JQueryAjaxDone class, that casts the WebDriver to a JavascriptExecutor and then runs a javascript test on jQuery.active in the browser.

1
2
3
4
5
6
7
8
9
10
11
public class JQueryAjaxDone implements ExpectedCondition<Boolean> {

public static final JQueryAjaxDone CONDITION = new JQueryAjaxDone();

public Boolean apply(WebDriver driver)
{

String jscript = "return window.jQuery != undefined && jQuery.active === 0";
return (Boolean)((JavascriptExecutor) driver).executeScript(jscript);
}

}

Now I’ll update the automation task to wait for the ajax calls to complete before searching for the potential new user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ git diff
diff --git a/src/test/java/com/ideoplex/tutorial/SetupTest.java b/src/test/java/com/ideoplex/tutorial/SetupTest.java
index 3049da0..0ae4c89 100644
--- a/src/test/java/com/ideoplex/tutorial/SetupTest.java
+++ b/src/test/java/com/ideoplex/tutorial/SetupTest.java
@@ -18,9 +18,12 @@ import org.testng.annotations.Test;

public class SetupTest {

+ protected boolean ajaxWait = false;
+
public void addUser(WebDriver driver, String email, String givenName, String surname)
{

WebDriverWait block = new WebDriverWait(driver,10);
+ if ( ajaxWait ) { block.until(JQueryAjaxDone.CONDITION); }
WebElement search = block.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#users_filter input")));
search.clear();
search.sendKeys(email);
@@ -72,9 +75,9 @@ public class SetupTest {
addUser(driver, "abraham@example.com", "Abraham", "Lincoln");
}

- @Parameters({"browser","baseurl"})
+ @Parameters({"browser","baseurl","waitajax"})
@Test
- public void userCreate( String browser, String baseurl )
+ public void userCreate( String browser, String baseurl, String waitajax )
throws Exception
{

WebDriver driver = "chrome".equalsIgnoreCase(browser)
@@ -82,6 +85,10 @@ public class SetupTest {
: new FirefoxDriver();
driver.get(baseurl);

+ ajaxWait = waitajax.equalsIgnoreCase("true")
+ || waitajax.equalsIgnoreCase("on")
+ || waitajax.equalsIgnoreCase("yes");
+
addUsers(driver);

Thread.sleep(10000);

And update our pom.xml so that I can enable the wait from the command line.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git diff
diff --git a/pom.xml b/pom.xml
index b6bcc63..3ab2b84 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,7 @@
<systemPropertyVariables>
<baseurl>http://localhost:8080/</baseurl>
<browser>firefox</browser>
+ <waitajax>no</waitajax>
</systemPropertyVariables>
</configuration>
</plugin>

Now I can run the test twice without a hitch:

1
$ mvn test -Dbaseurl=http://localhost:8080/\?sleep=500 -Dbrowser=chrome -Dwaitjax=true
$ mvn test -Dbaseurl=http://localhost:8080/\?sleep=500 -Dbrowser=chrome -Dwaitjax=true

The full source for this version of Jersey, Gson and Datatables is on github.

14 Jun: DataTables and Selenium
12 Jul: DataTables edit Locally