• Jobs
  • About
  • Making Selenium commands using XPaths faster in Internet Explorer February 11, 2011

    Selenium supports several locators, of which XPaths are the most expressive of them all. One of the challenges building helper functions that handle all situations in the app is that the simpler locators like ids and names don’t work everywhere. For these cases, my preferred locator would’ve been XPaths except for the fact that they are really slow in IE, especially in multi-window mode (which is Selenium’s default mode).

    Though I didn’t go through an elaborate exercise to measure how much slower XPaths are in IE compared to FF in Selenium’s default multi-window mode. I happened to have a test case that heavily relied on XPaths, so, I decided to use it as a benchmark. I noticed that in FF it took 27 seconds to execute whereas in IE it took over 180 seconds i.e. 6 times slower!

    So I clearly needed to make the tests faster. Here were some options I then tried to fix the problem:

    Use single-window mode

    To use Selenium in single-window mode, just start selenium with the -singleWindow flag. To see how to use it in ant see the Getting started with Selenium RC post.

    In single window mode, the difference was lesser at IE 8 being somewhere around 2-3 times slower. Single-window didn’t work for several situations in the app I was building automated tests for though. For instance we would loose the session in the popup window in some situations or the javascript on the page interfered with Selenium’s js and a few other problems. For this reason I couldn’t use it, but if you don’t see issues, you could get some performance improvement with this option.

    Using alternate locators

    As described in the post focussed on Selenium locators, there are other alternatives to XPaths that are faster. If you can use id and name attributes, that would be a great option.

    Unfortunately, it is unrealistic to have every element you want to interact with have an id or name. css would work for a larger set of cases, however, it can’t be used for all the elements either.

    Going back to my situation, several elements I needed to target happened to have ids but they were dynamically generated and so we couldn’t use it with Selenium’s out-of-the box capabilities.

    Extending Selenium

    Fortunately, extending Selenium’s capabilities by using a Javascript library that can evaluate XPaths really fast. The idea here is to bypass IE’s XPath evaluation by using a different library that is much faster. One such library is JavaScript-Xpath.

    There are several ways you can extend Selenium but the recommended approach is to use user-extensions.js. The steps are:

    Create user-extensions.js

    Create a file named user-extensions.js that evaluate’s your XPath expression using the JavaScript-Xpath library.

    You can first start with creating some functions that return element count for a given XPath, element attribute values etc so that you do not end up using IE’s javascript engine – which is slow.

    /** Copy paste the javascript-xpath library code here. */
    
    Selenium.prototype.getCount = function(xpath) {
        var result = document.evaluate(xpath, this.browserbot.getCurrentWindow().document, null, 7, null);
        return result.snapshotLength;
    };
    
    Selenium.prototype.getAttributeValue = function(xpath, attribute) {
        var result = document.evaluate(xpath, this.browserbot.getCurrentWindow().document, null, 7, null);
        var obj = result.snapshotItem(0);
        if (!obj) {
            for( var x = 0; x < obj.attributes.length; x++ ) {
                if (obj.attributes[x].nodeName.toLowerCase() == attribute.toLowerCase()) {
                    return obj.attributes[x].nodeValue;
                }
            }
        }
        return null;
    };
    
    Selenium.prototype.getAsText = function(xpath) {
        //var result = document.evaluate("normalize-space(string(" + xpath + "))",
        var result = document.evaluate(xpath,
                this.browserbot.getCurrentWindow().document, null, 2, null);
        return result.stringValue;
    };
    

    If you want to go beyond these simple evaluations, you could also attempt creating a function that converts an XPath to a different, more performant, locator when possible. Look at this simple example:

    Selenium.prototype.convertXpathLocator = function(xpath) {
        // Evaluate a XPath expression string
        var result = document.evaluate(xpath, this.browserbot.getCurrentWindow().document, null, 7, null);
    
        // analyze the result object
        var obj = result.snapshotItem(0);
        if (obj == null) {
            return "xpath=" + xpath;
        }
    
        if (obj.id != "") return "id=" + obj.id;
        if (obj.name && obj.name != "") return "name=" + obj.name;
        return "xpath=" + xpath;
    };
    

    In this case if there were to be some html code had something like:

     <input id="item1473" name="qty" type="text" />
    

    In this case you cannot hardcode the id as the locator assuming the id is indeterministic. But, calling convertXpathLocator("//input[@name='qty']") would retrieve id=item1473
    which now you could use to interact with more efficiently.

    Add this user-extensions.js file to selenium startup

    To have Selenium use this user-extensions.js file,

    1. add  user-extensions.js in a folder, for instance config, and
    2. add -userExtensions config/user-extensions.js to the selenium startup command.

    Call the user-extensions functions from your test framework code

    Modify your java code to call the javascript functions you added.

    For instance, if the html page had something like this:

    <h1>Shopping Cart<h1>
     <table>
        <tr class="item">
            <td>DVD Player</td>
            <td><input id="item1455" name="qty" type="text" /></td>
        </tr>
        <tr class="item">
            <td>DVD movie - Foo</td>
            <td><input id="item1473" name="qty" type="text" /></td>
        </tr>
        ...
    </table>
    

    And you wanted to
    1) check if you are on the “Shopping Cart” page,
    2) you have two items in your cart, and
    3) buy two dvd players

    You can achieve it using the newly added functions by doing:

    int count;
    try {
        count = Integer.parseInt(browser.getEval("this.getCount('//div[normalize-space(.,\"Shopping Cart\")]')"));
    } catch (Exception e) {
        count = 0;
    }
    
    if (count > 0) {
        if (browser.getEval("this.getAttributeValue('//div[contains(.,\"DVD Player\")')").equals("text")') {
            browser.type(browser.getEval("this.convertXpathLocator('//div[contains(.,\"DVD Player\")')"), "2");
        } else {
            throw new MyCustomException("Didn't find DVD Player in the shopping cart");
        }
    } else {
        throw new MyCustomException("Shopping cart page not found.");
    }
    

    Conclusion

    After all the modifications above, the same IE test that took 180 seconds, now takes about 30 secs. This proved that small changes to how XPaths are evaluated, can make IE perfomance almost the same as Firefox.

    Posted by Rahul Poonekar in : Selenium

    5 responses to “Making Selenium commands using XPaths faster in Internet Explorer”

    1. Deepa says:

      Many Many thanks to those who have posted this. I want to improve performance of selenium in IE. I have some queries in using the above given methods.
      Can you please add me in your chat to solve my queries. My email – deepa.kiran1116@gmail.com , deepa.kiran1116@yahoo.com
      Please please help me. your help would be greatly appreciated

    2. Jijo thomas K says:

      Hi Rahul,

      Thanks for this great advices, am facing a problem with the performance of the tool (am working in ie8). But it is shown for all commands (not only xpaths). While checking through the debug window its found that a warn message is shown for all the actions “warn(): _getFrameElementByName couldn’t find a frame; checking every element for the name close”.
      In some actions it is searching more than 4 or 5 times, which makes the scripts get executing very slow. Please help me out from this issue…

      Thanks and Regards,
      Jijo Thomas K

    3. Robert says:

      Hi Rahul,

      I gave it a try, the performance improvement is amazing, would be nice to have it integrated in Selenium. Are there any plans for this integration?

      Cheers,
      Robert

    4. Hi

      Its a great idea for better performance.let me give a try for this

    5. Djameldin says:

      Hi Jigar, I’m not sure what could be wrong with your environment. Here are a few tnihgs that come to my mind:1. Are you sure you packed the content back correctly? You need to make sure you go *inside* the seleium-server folder, select all the files and folders inside it, then archive all of those into selenium-server.zip , change that into selenium-server.jar and copy it under \Lib\site-packages\SeleniumLibrary\lib\2. Does this happen with any browser?

    Leave a Reply

    Your email address will not be published. Required fields are marked *