C# Programming > Miscellaneous

jQuery Selector for
Selenium WebDriver C#

Selenium

Selenium 2 is a powerful framework written to allow developers to programmatically simulate user interactions with popular browsers such as Chrome, Firefox, Internet Explorer, Opera, and Safari, making tasks such as automated web application testing a breeze. One of the many supported languages Selenium has is C#, giving .NET developers easy access to the powerful tool.

Although Selenium is constantly updated and refined, we are going to look at extending the framework ourselves to add even more powerful functionality.

Update: Firefox bug is fixed. The jQuery objects needed to be unwrapped with a call to get(). Note that toArray() is similar, but unlike get, it is not supported in all versions of jQuery.

Selectors

Programming with Selenium can get relatively complex and intricate. However, at the very core, the most basic concept is that Selenium must find elements within a web page and then perform actions upon those objects.

There are quite a few built in selectors, for example, to find an element with a particular ID we would do something like:

driver.FindElement(By.Id("lst-ib"));

Where driver is some instance of IWebDriver. The By class is what specifies which selector Selenium uses to find a particular item. Here is a list of the built-in selectors:

  • ClassName
  • CssSelector
  • Id
  • LinkText
  • PartialLinkText
  • Name
  • TagName
  • XPath

With this set of selectors programmers can find pretty much any element in a page. CSS and XPath selectors allow for complex queries to find elements that might not have identifying attributes such as ID or name.

If we want to use a selector from a library like jQuery, we have to implement the interface ourselves.

jQuery Selector

Most web developers have heard of jQuery, an extremely popular javascript library that makes writing javascript much easier. One of the corner stones of jQuery is how easy it is select elements using a combination of Sizzle and jQuery's own traversal functions.

Let's try to bring jQuery selectors to Selenium in C# without modifying any Selenium files. The last bit is the fun part since other solutions that add jQuery selectors to Selenium require modifying the server file.

Loading jQuery

First step is to have jQuery available in the given web page. Luckily for us more than 24 million sites use jQuery, so chances are good that a given site will have jQuery already loaded. However to be safe we need to a) check if jQuery is available and b) load it if it's not.

The two functions are implemented as extension functions of RemoteWebDriver. Why not IWebDriver? Because we need to execute javascript and only RemoteWebDriver has an ExecuteScript function.

Here is where other methods recommend modifying the Selenium server to include a version of jQuery. For our method we load jQuery from an external source. Although slower, the difference is negligible on decent internet connections and we do not have to modify the Selenium server.

Rather than post the whole code, here are the function declarations and a brief description of what each does. For the full code check the bottom of this page. Note that these functions were inspired by an article on using Sizzle with Selenium.

public static bool jQueryLoaded(this RemoteWebDriver driver)
Run a small javascript snippet to check if the jQuery function is defined
public static void LoadjQuery(this RemoteWebDriver driver, TimeSpan? timeout = null)
Check if jQuery is loaded, if its not run a javascript snippet to add jQuery to the current page and verify the load was successful
Update: One important thing to consider is which version of jQuery is being used. If we load jQuery, we can use Google's CDN service to load a specific version. However, if it is already loaded, we can potentially have problems if the version used by the site doesn't support some selectors we use. As an option, you might want to force the page to have a specific version of jQuery. To do this, we can call jQuery.noConflict(true) to "unload" the current version of jQuery and replace it with our version.

"By" Class

Next we need to extend the By class to add support for a jQuery selector. Luckily Selenium's By class is inheritable so we can just inherit it and declare a jQuery function. If we name the class the same thing also we can add the class to existing code without breaking anything.

public class By : OpenQA.Selenium.By
{
    public static By jQuery(string selector)
    {
       //...
    }
}

However, jQuery not only allows CSS-style selector queries, but also includes functions that further filter and traverse the DOM tree. Some of these functions are redundant and some are not. For example, $("a:first") and $("a").first() return the same thing. On the other hand, $("a:contains('Something')").next() cannot be written as just a query (correct me if I'm wrong).

Thus it is a good idea to include an interface for these extra functions (full list here). However we can omit functions that modify the DOM tree since we are only interested in searching.

To implement them we need a helper class within the By class. This way we can chain multiple calls in one line. Here is a small subset of the class to show the structure:

public class By : OpenQA.Selenium.By
{
    public static jQueryBy jQuery(string selector)
    {
        return new jQueryBy("jQuery(\"" + selector + "\")");
    }

    public class jQueryBy
    {
        public string Selector
        {
            get;
            set;
        }

        public jQueryBy(string selector)
        {
            this.Selector = selector;
        }

        public jQueryBy Find(string selector = "")
        {
            return this.Selector + ".find(\"" + selector + "\")";
        }
    }
}
          

So now we can call something like this:

By.jQuery("a").Find(":contains('Home')")

FindElement and FindElements Overloads

The final step to make this work is to once again use an extension functions to overload FindElement and FindElements in a RemoteWebDriver object.

The code itself is not too interesting so here is the general idea instead. Each function is basically going to execute the javascript that the By class has under its Selector property. So for the example above we would execute the code: return jQuery("a").find(":contains('Home')").The result is then cast into a ReadOnlyCollection.

FindElement throws an exception if no elements are found, FindElements returns an empty list if no elements are found.

FindElement could call FindElements and then return only the top element. This is not a good idea because we force Selenium to return and parse every single element the selector returned. Instead FindElement adds a ".first()" filter to the end of the selector and runs that instead. This way Selenium only parses one element.

Finally, we can call something like this:

driver.FindElement(By.jQuery("a").Find(":contains('Home')").Next())

Final Notes

As promised the full source code can be downloaded below. The C# source includes the By class and a SeleniumExtensions class for the jQuery loading functions and the FindElement/FindElements overloads. These can be dropped into existing Selenium 2 Webdriver C#.NET projects without breaking anything (at least in theory it shouldn't).

Alternatively, a better design would be to extend the RemoteWebDriver and RemoteWebElement classes to include the new functionality. The downside is of course, that it requires modifying a potentially large amount of existing code.

Note: For the strangest reason, this doesn't work in Firefox (5)! If anyone knows why send me an email.
Update (9/13/11):
Fixed, see top of page for information and download the code below for the new code.

Back to C# Article List