When testing JavaScript-heavy websites, at some point you may reach Selenium limits. A simple use case will fail. You will get ElementNotVisibleException, even though you can clearly see that element. Other times the same selector will return different results depending on the type of the browser. You can lose a lot of time searching for workarounds and hacks. Fortunately, a simple solution exists...

<p>…but first let me introduce you to a new concept.</p>

Not familiar with Selenium? You can grasp some knowledge here and here.

JavaScript in Selenium tests

Did you know you can execute JavaScript code inside a Selenium test? Here is how you can accomplish this using the WebDriver object:

WebDriver driver;
// create driver, i.e. driver = new FirefoxDriver();
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("alert('Hello from Selenium!');");

The result is this pop-up:

You can go further and return a JavaScript object. It will then be transformed into a Java WebElement object, on which you can execute standard Selenium methods. If on a page there was a DIV element with ID equals "example":

<div id="example">Hello from Selenium!</div>

Then you could fetch that element with this piece of code:

JavascriptExecutor js = (JavascriptExecutor) driver;
String script = "return document.getElementById('example');";
WebElement exampleDiv = (WebElement) js.executeScript(script);
exampleDiv.getText(); // returns "Hello from Selenium!"

jQuery to reduce verbosity

Using pure JavaScript alone may be very verbose. jQuery can help here with its concise and elegant syntax. With jQuery you can:

  • use a great deal of selectors that work pretty much the same on every browser
  • use jQuery functions for interaction with a page (for example to do double click use the .dblclick() function)
  • easily bypass any Selenium bug by putting JavaScript code in your tests

Example scenario

Assuming we have a page for editing a list of users which looks like this:

...with HTML code looking like this:

<table id="users">
    <tr>
        <th>Name</th>
        <th>Remove</th>
    </tr>
    <tr>
        <td>John</td>
        <td>
            <button onclick="removeUser('John')">
                Remove
            </button>
        </td>
    </tr>
    <tr>
        <td>Bob</td>
        <td>
            <button onclick="removeUser('Bob')">
                Remove
            </button>
        </td>
    </tr>
    <tr>
        <td>Frank</td>
        <td>
            <button onclick="removeUser('Frank')">
                Remove
            </button>
        </td>
    </tr>
</table>

Lets try to test the following scenario:

  1. Web browser is opened.
  2. Page with a list of users is shown.
  3. User 'Bob' is removed from the list by clicking the 'Remove' button in front of his name.

Want to try the example code? You can download this github project (check the selenium3 package).

This short video demonstrates what we are trying to accomplish:

Clicking a button with jQuery

First we should come up with a proper jQuery selector:

#users tr:has(td:contains('Bob')) button:contains('Remove')

Here is an explanation how this selector works:

To click the button that is returned as a result of our selector we can use this piece of code:

String jQuerySelector = "#users " +
                        "tr:has(td:contains('Bob')) " +
                        "button:contains('Remove')";
js.executeScript("$(\"" + jQuerySelector + "\").click();");

If we want to do the clicking part on the Selenium side we should write this instead:

String jQuerySelector = "#users " +
                        "tr:has(td:contains('Bob')) " +
                        "button:contains('Remove')";
String findButton = "return $(\"" + jQuerySelector + "\").get(0);";
WebElement removeButton = (WebElement) js.executeScript(findButton);
removeButton.click();

The jQuery get method is used to unwrap the jQuery decorator object and return the native DOM object. Without parameters this method returns a list of unwrapped objects. Only one BUTTON is expected to be returned, so it is more appropriate to use the get method with an integer parameter. This parameter specifies which element from the list should be returned. In our case it is the first element, so we can use the method call: .get(0).

jQuery selectors using the Selenium class

If you want to use the jQuery selectors alone, a sensible solution is to use the com.thoughtworks.selenium.Selenium class. It provides lots of helper methods for interacting with a website. Creating the Selenium object is as simple as wrapping the WebDriver object:

String baseUrl = ...; // i.e. "http://google.com"
Selenium selenium = new WebDriverBackedSelenium(driver, baseUrl);

Again, if we want to remove the user named Bob, we should prepend the previous selector with the type "css=" and put the result as a parameter to the click method of the Selenium object:

selenium.click("css=#users " +
               "tr:has(td:contains('Bob')) " +
               "button:contains('Remove')");

Things to consider

We reached the outcome we wanted, but there are still some points we should consider:

  • What to do if we are not using jQuery on our website and are unable to use it? (we are either constrained by requirements or have no access to the sources)
  • What are the drawbacks of this technique?

Loading jQuery from within a test

If your site does not use jQuery and you do not want or cannot put the appropriate script tag, you can still use it by:

  • loading the contents of the jQuery code into a String from a JavaScript file (jquery.js, jquery.min.js or similar)
  • ...and executing this String as JavaScript code using the WebDriver object
To load the jQuery file into a String you can use the Guava library. In case you were not using it already, here is the Maven dependency:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>11.0.2</version>
</dependency>

To fetch the contents of a file the com.google.common.io.Resources class proves to be useful. Here is how you can use it to load jQuery from within a test:

URL jqueryUrl = Resources.getResource("jquery.min.js");
String jqueryText = Resources.toString(jqueryUrl, Charsets.UTF_8);
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript(jqueryText);

Avoid the side effects

Using jQuery with Selenium is not always a bed of roses. It can significantly simplify your tests or solve your problem, but you should take into consideration the impact of injecting JavaScript code into a tested website. When using this technique think carefully whether it:

  • changes the website's flow
  • may hide a bug
  • enables access to an element that is otherwise not visible
  • tides your test with the implementation of the website

Final thoughts

You have aquired a new tool in your toolbox. It can improve the development of Selenium tests. On the other hand it should not be overused. Understand the potential side effects and try to avoid them.