Posts Tagged ‘Locators’

Why CSS Locators are the way to go vs XPath

May 17th, 2011 by Ashley Wilson

Last week, our own Santiago Suarez Ordoñez gave a presentation to the San Francisco Selenium Meetup group that convinced us all to say no (for the most part) to XPath and yes to CSS Locators in our Selenium tests.

In his role as official Sauce Ninja and as a prolific poster in the Selenium forums, Santi has helped more users solve locator issues than possibly anyone else in the world. He’s previously written a number of blog posts on ways to improve locator performance. As Sauce CEO John Dunham puts it, “If there was a foursquare mayorship for locators, Santi would have it for a lifetime.”

Drawing from this experience, he gave us these four reasons for using CSS Locators:

1. They’re faster
2. They’re more readable
3. CSS is jQuery’s locating strategy
4. No one else uses XPATH anyways!

I can’t speak for everyone, but Santi sure sold me on point number one when he showed off the performance metric script he wrote a script that tested the speed of XPath vs the speed of CSS Locators. There wasn’t much of a difference in Firefox, Safari, or Chrome, but with IE, the results were undeniable. Take a look:

To underscore this even further, he also recorded a video in Sauce OnDemand that uses one heck of a cute kitten to illustrate just how slow XPath can be. The cat’s paw movements represent the test clicking through the different locators. The first batch of clicks uses CSS Locators and completes in under 30 seconds. The second batch, the XPath one, continues on for another eight minutes. Eight minutes!

During the rest of the presentation, Santi dives into writing both basic and more advanced CSS Locators. He also spends some time talking about when you shouldn’t use CSS Locators (yes, there are a few cases where it is not the right tool for the job). To see the talk in its entirety, check out the recording below. And if you’re thinking of switching over from XPath, but unsure of how to go about it, check out the nifty tool Santi wrote called cssify. It does the handy work of translating your XPaths to CSS automatically.

Helpful links
View Santi’s Presentation Slides
Follow him on Twitter
Follow Sauce on Twitter
Join the San Francisco Selenium Meetup group!

Enjoy!

Share

Why jQuery in Selenium CSS Locators Is The Way To Go

January 31st, 2011 by Santiago Suarez Ordoñez

You may not have heard about this, but a while ago, Jason Huggins moved Selenium 1′s CSS locator engine from CSSQuery to Sizzle, jQuery’s CSS selector library.

While this may not sound like a big deal to most users, it actually is, and in this blog post, I’ll explain why and how to start using all the cool features that come with this change.

Update: For those of you using or willing to use Selenium 2/Webdriver, you may want to re-consider the special Sizzle pseudoclases like :contains or :nth that I’m talking about in this blog post. For technical reasons, injecting Sizzle in browsers driven by Selenium 2 is not as cheap as with Selenium 1, so the Devs have decided to rely on the browsers’ implementation of CSS (standard CSS only) and fallback to Sizzle when needed (in case of old browsers which don’t provide native css selecting for JavaScript). In Selenium 2 land, my advice, sadly, is to stick to the standard and avoid most of these shortcuts :,(

Why is Sizzle awesome?

Well, Sizzle is jQuery‘s selector engine, and that means A LOT. For those of you who don’t know, jQuery is the javascript library used by almost 30 percent of all the websites. (As in, “30 percent of the whole effing Internet!”).

Because of that, Sizzle gets an insane amount of usage, and therefore, testing. Sizzle’s code is used by an average of 1 site for every 3 you visit. Its codebase has over a thousand followers on github and more than 80 forks as of today. That’s a lot of eyes to catch bugs and improve performance.

But it’s not just a more robust and faster implementation of CSS that works on every browser. It has removed useless CSS selectors and added extra goodies that turn out to be pretty useful for people writing tests.

Sizzle’s extra features you can use right now

As it says in its docs, Sizzle not only implements virtually all CSS 3 Selectors, but also extends them a little bit and adds its own, including some that are actually pretty useful for writing Selenium tests:

:not

The :not selector will help you filter out elements that are similar, but not exactly what you’re looking for. Let’s imagine the following situation:

<a href="#meh" class="confirmation_link hidden">Confirm</a>
... lots of html ...
<a href="#bleh" class="confirmation_link">Confirm</a>

As you can see here, there are two links in the page you’re trying to test. Since they both have the same text, link=Confirm won’t work because they’re the same class and there is no id you could use to be more specific. In this kind of situation, the :not selector is our perfect weapon. It’s just as simple as writing the following locator:

selenium.click("css=a.confirmation_link:not(.hidden)")

Thanks to Sizzle, complex filters can go inside :not. Here are some other examples:

selenium.click("css=.confirmation_link:not(div)")
selenium.click("css=.submit_button:not(#clear_button)")
selenium.click("css=input[type=button]:not(p#not_this_input > input)")

:contains

Even though :contains was already present in cssQuery and the old Selenium, I thought it was worth mentioning. It can be used to filter elements depending on their inner text, so you can do something like:

selenium.click("css=div#myID > a:contains(Confirm Transaction)")

:eq/:nth

This selector finds all the occurrences and then just filters the nth in the list. If are using the confusing :nth-of-type or :nth-child filters, this may be a great replacement.

selenium.click("css=table a:contains(Change password):nth(5)")

:header

This selector will find you any header element. That is, h1, h2, h3, h4, h5 or h6. Pretty cool, huh? This way you can forget about which type of header your devs choose to use in the page. All you care to know is that it’s going to be a header.

Asserting a header that contains a specific text is the perfect situation for this:

assert selenium.is_element_present(":header:contains(Users Admin)")

Form helpers

Additionally, Sizzle includes some form element shortcuts to save you from having to find out whether the element is a textarea element or an input. Even better, it saves you from writing ugly locators like input[type=checkbox].

  • :input: Finds all input elements (includes textareas, selects, and buttons).
  • :text:checkbox:file:password:submit:image:reset:button: Finds the input element with the specified input type (:button also finds button elements).

I think those are all important and you can check out the Sizzle’s wiki for more info. We also released a CSS selectors quick reference if you’d like to have a cheat-sheet printed and close to your desk while you’re writing your tests ;)

Hope everyone is now writing jQuery Selenium selectors and found this post useful for saving some time and headaches. Of course, all of this is already available in our browsers in the cloud service, Sauce OnDemand, so go try it out for free!

Share

How To Minimize The Pain Of Locator Breakage

September 29th, 2010 by Adam Goucher

In the Agile world, a common saying is ‘never break the build.’ But this thought can actually hinder agility if taken too literally. Sometimes, you want the build to break, as that means things are changing and progressing.

A better phrase is ‘don’t leave the build broken’. The same could be said for Selenium and element locators. They will break, and we want them to…periodically. The measures that count are:

  • How long it takes to go from broken to fixed – this should be small; really, really small
  • How big is the change to fix them – ideally, only one line per element. Total. Throughout the whole code base.

So how do we go about fixing these intermittent breakages?

The easiest locator to use is ‘id.’ To be most successful with this strategy, a static id attribute is put on each html element being interacted with. And since we don’t know what elements will be needed to automate in the future, it’s best to put an id on every element. The scripts will always know how to find the elements on the page uniquely, as WSC standards require that ids be unique in a single page.

While this solves the html structure change problems that often come from using XPath and CSS selectors by finding elements independently of their position, it still doesn’t solve id change issues. Solving that requires discipline and communication among the development and automation teams. One geographically dispersed team I worked with created the following two-part policy for this.

  1. Anyone can add an id to an element that does not have one
  2. If you change an id, the change must be communicated to both the dev and automation teams

Of course, knowing about a change is a bitter pill to swallow when it involves updating hundreds of scripts. A better solution is to separate the locator definitions from the main test code.

Selenium IDE

Separation of scripts from locators is achievable in Selenium IDE through the use of user-extensions. A previous Sauce Labs blog post, Parametrizing Selenese tests, explains how to make use of these extensions, but rather than provide the ‘Value’ column, the ‘Target’ is what could [should] be provided by the user-extension.

storedVars["registrationFirstname"] = "first";
storedVars["registrationLastname"] = "last";
storedVars["registrationSubmit"] = "register";

A script that interacts with a registration form might look like:

open /registration
type ${registrationFirstname} fred
type ${registrationFirstname} flintstone
click ${registrationSubmit}

Going back and updating Selenese scripts is a pain, but using user-extensions to provide the locators to your scripts makes it as simple as updating an external JS file.

Selenium RC

Most Selenium RC languages have a way of reading run-time properties from external files in some native manner:

  • Java – Properties
  • Python – ConfigParser or maybe just python dictionaries
  • Ruby – YAML
  • C# – App.Config

If you’re writing your scripts in Java, you should create a locators.properties file, which would include something along the lines of:

# uses the id locator
registration.firstname=firstname
# xpath
registration.lastname=//form[@name='lastname']/input[@type='text'][2]
# css
registration.submit=css=form submit

Here is a Java class that interacts with our very simplistic registration form, and uses the external properties file (above) for its locators.

import java.io.InputStream;
import java.util.Properties;

import org.junit.Before;
import org.junit.Test;
import org.junit.After;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;

public class TestRegistration.
{
    private Selenium selenium;
    public static Properties locators = new Properties();

    @Before
    public void setUp() throws Exception
    {
        InputStream is = TestRegistration.class.getResourceAsStream("/locators.properties");
        locators.load(is);

        selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://localhost:3000");
        selenium.start();
        selenium.setTimeout("90000");
        selenium.windowMaximize();
    }

    @Test
    public void testVisibleElement() throws Exception
    {
        selenium.open("/registration");
        selenium.waitForPageToLoad("30000");
        selenium.type(locators.getProperty("registration.firstname"), "fred");
        selenium.type(locators.getProperty("registration.lastname"), "flintstone");
        selenium.click(locators.getProperty("registration.firstname");
        selenium.waitForPageToLoad("30000");
    }

    @After
    public void tearDown() throws Exception
    {
        selenium.stop();
    }
}

Notice there are no hardcoded locator strings in the test, so we can change locators without changing the script. And if a consistent, well-thought-out naming convention is used for the property names, it CAN actually increase the readability of the scripts.

By using the same locators.properties throughout the whole suite of scripts, a single change will cascade to all scripts. It also means that as ids become available on elements that didn’t have them previously, they can quickly be replaced with more structurally dependent methods, such as XPath and CSS. A further benefit is that, by quickly scanning the file, one can determine where in the suite XPath is still being used; converting from XPath to CSS is currently a recommended practice for performance reasons.

Page Objects

Page Objects are a way of representing a web page as a class (or number of classes) to bring Object Oriented techniques and patterns to Selenium. Most Page Object patterns have the locators abstracted out of the actual Page Objects and into a higher level parent class. But even then, those locators should be loaded from an external mechanism to allow for locator changes without recompilation.

Page Objects are beyond the scope of this article, but for an introduction and a sample implementation pattern of them in Python, see Page Objects in Python – Automating Page Checking without Brittleness.

Next Steps

Separating scripts from the locators being used is an important step towards reducing the amount of time teams invest in keeping their automation running. Too often, though, teams jump on this idea and invest valuable time converting their whole suite, which sets them further behind what the features development is producing. A better solution for the team is to collectively decide that scripts and locators will be separate, and from then on, only create scripts in this model, and only backport the abstraction of locator string when they break in existing code. The scripts will be ‘hybrid’ for a time, but ultimately it’s a much faster approach.

Share