Monday, April 2, 2012

Integrating jQuery With Selenium

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

...but first let me introduce you to a new concept.

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.

19 comments:

  1. great post, thanks a lot

    ReplyDelete
  2. Thanks for appreciation ;). I hope it was helpful.

    ReplyDelete
  3. Now I have started to include Jquery in our Webdriver based framework. Thanks for the post.

    ReplyDelete
  4. Hi,

    I followed the steps u have mentioned to click a button using Jquery..but it didnt worked for me..

    My Dom Structure:

    Div class=abc_button_bar
    Button class=def_button, onclick(.......), New/Button

    code i have written:

    String jQuerySelector = "#abc_button " +
    "button:contains('New')";
    String findButton = "return $(\"" + jQuerySelector + "\").get(0);";
    JavascriptExecutor js = ((JavascriptExecutor)driver);
    WebElement newButton = (WebElement) js.executeScript(findButton);
    newButton.click();


    can u please help me on this...

    ReplyDelete
    Replies
    1. Hello, sorry for this late response. I would be grateful to help you, if you still need it.

      I am not sure I understand it correctly, but it looks like you are trying to run this jQuery selector:

      #abc_button button:contains('New')

      against this HTML code:
      <div class="abc_button">
      <button class="def_button">New</button>
      </div>

      I think what you are looking for here is the class selector, not the id selector. In other words you should use this:

      .abc_button button:contains('New')

      If you have any more problems, please contact me by email.

      Delete
  5. I have small doubt , how this will avoid ElementNotVisibleException

    ReplyDelete
    Replies
    1. I wish you were right. Unfortunately, in a project, that I am currently involved in, this is actually the case. We are working on a legacy application with loads of Javascript using proprietary Internet Explorer API and there are situations where you cannot simulate a use-case with Selenium alone (let it be as simple as opening a pop-up in IE).

      Selenium like any software is not bug-free and it is sometimes useful to know how to get around certain issues. That was my intention when writing the article.

      Delete
  6. G8 article!! how to use mouseover function in selenium where moving on webelement like drop down box where after mouse over element to be clicked can be seen? Can it be done on IE as well?

    ReplyDelete
    Replies
    1. Firstly, I would try to create a "standard" solution with either the WebDriver class:

      WebElement element = driver.findElement(By.id("source"));
      WebElement target = driver.findElement(By.id("target"));
      (new Actions(driver)).dragAndDrop(element, target).perform();

      or with the Selenium class:

      selenium.dragAndDropToObject("css=#source", "css=#target");

      If neither of these have worked, I would simulate the mouseover in my automatic tests, by calling the function that is fired on mouse over event. For example, if you had a div element like this:

      <div onmouseover="drop_func()"></>

      Then in my test I would execute the drop_func() programmatically:

      JavascriptExecutor js = (JavascriptExecutor) driver;
      js.executeScript("drop_func();");

      Ofcourse, this leaves your application with a feature that needs to be verified also manually by your QA team, but sometimes it is the least you can do.

      Delete
  7. The problem i am facing currently with selenium 2 is that i can find an option in the drop down list but cant select it.
    This does not work in IE.. It works fine with FF and google chrome.. Is it possible to run tests on all the browsers and inject jquery only for IE..

    ReplyDelete
    Replies
    1. Yes, you can do this. It all depends how you are creating the WebDriver instance. For example, if you are manually creating the WebDriver it is as simple as that:

      if(driver instanceof InternetExplorerDriver) {
      ...
      }

      I assume you are probably using the RemoteWebDriver. In this case you can do the check with this snippet:

      String IE = DesiredCapabilities.internetExplorer().getBrowserName();
      String browserName = remoteDriver.getCapabilities().getBrowserName();
      if(browserName.equals(IE)) {
      ...
      }

      Delete
  8. My team just finished a library that wraps jquery functions for use with Selenium. We just finished our first release, but plan on wrapping ALL of jquery's functions. This library makes it easy to use jquery from your Selenium tests in C#. It makes for MUCH cleaner looking tests. Here's the source code: https://github.com/AcklenAvenue/JQSelenium

    ReplyDelete
  9. This post is really helpful. I appreciate the post.

    ReplyDelete
  10. Where to keep the file "jquery.min.js"? I ran the following code:

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

    But, unfortunately there was occurred exception as below:
    java.lang.IllegalArgumentException: resource jquery-1.8.2.min.js not found.

    What's the configuration? or, what's the location to keep the file "jquery-1.8.2.min.js"? Please guide me

    ReplyDelete
    Replies
    1. It depends on the building tool you use. If you use Maven, probably the easiest way to get it working is to check the github project with the examples. Please, let me know, if this helps.

      Delete
  11. Mathew Bukowicz, Great Explanation. I just(3-4 weeks back)recently started using JavaScript to improve the performance of the scripts(sometimes selenium response is taking like anything on IE and on latest FF versions ), also most importantly we can avoid element not found exceptions. I did not use Jquery yet, I thought it may be bit complex, but after reading this i got confidence that we can easily implement Jquery also, once again thanks for the post. Soon...(right after the holidays) i will start implementing Jquery and let you know if any i face any issues.

    ReplyDelete
  12. Raju, thanks for the information. Your conclusions and recommendations will certainly be very helpful.

    ReplyDelete
  13. I am not patient enough to wait for another two days to test Jquery with selenium, so today morning I opened the system and tested with different conditions ( Drop down selection , Click , Type Text , get Text with different selector combinations) all these are working fine.I will start using it in my project.Once again thanks for your post. I am not sure how it works for mouse events(Mouse hover,Mouse right click/left click,doubleclick,drag etc ), I am not technically expert in Jquery so may be i need to learn a bit to start testing the events part. If you can come up with small example for events handling with Jquery it would be very helpful. Thanks again.

    ReplyDelete
  14. Hi Mathew,
    First of all thank you for your post. It's working like a chum. It opens exciting opportunities for my project. But the the problem I tried to solve going by this route is still unresolved and going by your skills visible in this post, I thought to ask you. I will explain my problem.
    Basically I have this code in my HTML
    a href="" onclick="window.open('https://kspd.retailcloud.net/core/application/pPassPolicy/public.cfm?spid=change_password&action=reset&rtk=i32aSUdeTQKjldtqESk5xj0J4OYNb5X0%2F0oLgkUvNbU%3D&pref=internal','passwordChangeWindow','height=440,width=850'); return false;" class="SelectA">Click here to change the password </a

    What it does is, when you click on this element a new window/popup opens and you can change your password in it. It's working fine on all other browsers but can't open the window in SAFARI( can locate the element and click on it) . I thought jquery will solve the problem but still no difference. I checked internet and it seems to be a selenium bug/safari limitations.
    http://code.google.com/p/selenium/issues/detail?id=3693
    http://developer.apple.com/library/safari/#documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
    Problem seems to be with onclick="window.open" in safari.

    I would be really great full if you can suggest a workaround. I hav emailed you the HTML code seperately.
    Thanks
    Deepankur Tyagi
    07846744804


    ReplyDelete