Identifying HTML elements

There are about as many unique ways for web pages to be implemented as there are web pages. While the HTML language is well defined, it can be used in an endless number of ways to achieve the end result. One challenge in test automation is that the test script needs to be able to identify and access the same elements as the user would, to be able to simulate the end-user actions.

In most cases the elements on the page contain some identifiers that one can hook into. Styling of the webpage already requires such hooks as most (in practice all) of the web pages now use CSS. Test automation can use the same information as CSS selectors that are used for styling the page.

watir-webdriver methods for finding elements

The webdriver methods can be divided into three main categories:

  • methods for controlling the browser
  • methods for accessing HTML elements
  • methods for acting on the elements

In this post I'm concentrating on the methods available for accessing HTML elements although some other methods are shown for completeness. In the first step, we'll start a new browser instance, which falls into the first category.

Starting the browser and navigating to a page

require 'watir-webdriver'

browser = Watir::Browser.new :firefox
browser.goto "http://testpage.local"

Basic methods for HTML elements

Once we have a browser instance to work with, we can try some of the methods provided by the API. watir-webdriver has two methods for each HTML element type: a singular and a plural form:

browser.image
browser.images

browser.div
browser.divs

browser.footer
browser.footers

The singular form always returns one element, if one of the specific type is available. It returns the first one that matches the options provided to the method call.

In the example above, browser.image is used without any options, which isn't actually very useful. We'll go through some of the options that can be used in the next section. It is important to know that the method exists and that it returns the first element when options are not provided.

The plural form returns all of the elements of the specified type that match the options provided to the call. No options are provided in this example either and images returns all images on the page.

Options for methods

As mentioned before, the singular format of one of the HTML element methods returns the first instance of the specified element type and the plural form returns all elements of this type. When implementing test automation one needs to be more specific about the element to act upon. If the test case is about a menu option we need to be sure to click that exact option and not just the first link on the page.

How can we find the specific element then? We can use similar methods for finding it then a front end developer uses for styling the elements. We can use element id, class or name. There are also other options like text, which is particularly useful for finding links. In general, about any attribute an HTML element has can be used for finding it.

In addition to the attributes of a specific element, one can also take advantage of the attributes of a parent / child element. We'll look at some examples of this later.

Example scenarios

Elements without identifiers

Let's look at the following example. Here you can see a situation without anything specific to this piece of HTML.

<div>
    <ul>
        <li>option 1</li>
        <li>option 2</li>
        <li>option 3</li>
    </ul>
</div>

In this example it is assumed that options 1-3 are dynamic data coming from a database and cannot be used to reliably find the element.

Say we want to find the first li element in the unordered list. We could use one the following methods to find it by using the li methods:

browser.li

browser.lis.first

watir-webdriver allows for using element methods on a parent element to find it's child elements. We can take advantage of that feature with the following methods calls:

browser.ul.li

browser.ul.lis.first

While these work in this example, they are very error prone due to not being specific enough. If the page would change to the following, our method would already return the wrong element:

<ul>
    <li>problematic option</li>
</ul>

<div>
    <ul>
        <li>option 1</li>
        <li>option 2</li>
        <li>option 3</li>
    </ul>
</div>

This example tries to explain the need for enough specificity when finding the elements. There has to be something on the page to anchor our finder method. If there isn't anything suitable, you can always ask if the front end team is willing to help in the name of a more efficient testing process.

Element with an ID

In this example, the element we are trying to find has an id attribute.

<div>
    <ul>
        <li id="nav-1">option 1</li>
        <li id="nav-2">option 2</li>
        <li id="nav-3">option 3</li>
    </ul>
</div>

Having an id is the easiest situation for us, as for the HTML to be valid, the id has to be unique. The element can be found in the following way:

browser.li(:id => 'nav-1')

This is the first time in this post that our method contains options for finding the element. As mentioned before, id is not the only available option. While it always returns the correct element, it's not feasible for all the elements on the page to have an id. The next example uses the attribute class instead.

Element with a class

class differs from id in that the same class can be applied to several elements on the page. In this example, the three li elements all have the same class.

<div>
    <ul>
        <li class="nav">option 1</li>
        <li class="nav">option 2</li>
        <li class="nav">option 3</li>
    </ul>
</div>

This situation is not substantially better then not having any class if we just consider this part of the page HTML.

browser.li(:class => 'nav')

This still returns the first li element. It does improve the situation compared to the case where we have other li elements on the page (remember problematic option) as long as the same class is not used on other lis on the page.

Parent element with an ID or class

This is the most typical case when working with real websites. The key elements on the page tend to have either id or class defined. These elements can be used as anchors to make sure we're accessing the right part of the page.

<div>
    <ul id="navigation">
        <li>option 1</li>
        <li>option 2</li>
        <li>option 3</li>
    </ul>
</div>

Accessing the first li in the navigation can then be done in the following way:

browser.ul(:id => 'navigation').lis.first

Here we are again filtering the li elements to only the ones that are child elements of the specified ul.

Conclusion

watir-webdriver provides many options for find the element that needs to be accessed for the purpose of a test case. Some of the options have been explored in this post. One needs to be careful and make sure that the correct element is returned in every situation. When there isn't a direct method for finding an element available, ruby methods can be used to dig deeper. There are several useful methods, for example, in the array class for that purpose.