Advanced techniques of element lookup in Selenium (relative Bys, chained Bys, etc.)

The majority of test automation engineers use trivial lookup capabilities in their scripts. Usually the complexity of lookup locator means just the complexity of used locator syntax (like complicated XPath queries or CSS locators).

However Selenium offers something more powerful what might simplify your tests and improve readability of your locators. We’ll look into such facilities in my today post.

Let’s now look at everything in more details.

Sample page that we’ll be testing our locators against

Before we start we need to have a page everyone could test the code with. I have prepared one for you. Save the code below to some folder on your hard drive. Say, the file name would be test.html:

<html>
 <head>
   <meta charset="utf-8"/>
 </head>
 <style>
    .c {
    	position: absolute;
    	border: 1px solid black;
    	height: 50px;
    	width: 50px;
    }
 </style>
 <body style="position: relative;">
   <div id= "a" class="c" style="left:25;top:0;">El<span>-A</span>
   </div>
   <div id= "b" class="c" style="left:78;top:30;">El-B</div>
   <div id= "c" class="c" style="left:131;top:60;">El-C</div>
   <div id= "d" class="c" style="left:0;top:53;">El-D</div>
   <div id= "e" class="c" style="left:53;top:83;">El-E</div>
   <div id= "f" class="c" style="left:106;top:113;">El-F</div>
 </body>
</html>

That is expected to be rendered by your browser as:

relative by selenium

So, having your file at, say, /home/user/test.html (if you are using OS Linux or Mac OS) you would load this page as:

driver.get("file:///home/user/test.html");

For OS Windows, having such file at the folder, say, C:\pages you would start with:

driver.get("file://C/pages/test.html");

Assuming you have all boilerplate code set up let’s proceed with the examples.

The last thing to mention is Selenium version. To successfully reproduce all the examples, make sure you are using the bindings 4.0.0-rc-1 or later.

RelativeBy - a way to lookup the elements by their relative positions

Selenium offers the way to look up for the element by given relative position to some other element. It also ranges them by how well they meet the criteria. See the picture above.

Let’s say we need to take all div elements which are above element El-E. Then we do:

WebElement base = driver
        .findElement(By.id("e"));
List<WebElement> cells = driver
        .findElements(
                RelativeLocator
                        .with(By.tagName("div"))
                        .above(base));

First - we look up a base element. Then we’re saying like "Give me all div elements which are above my base element". Let’s now print out what we fetched:

cells
        .stream()
        .map(webElement -> webElement.getText())
        .forEach(System.out::println);

Looking at the result

Take a look at our console. What we can see there:

El-B
El-A

Let’s go through some remarkable points here:

  • El-B is going before El-A. This is because El-B is closer to El-E than El-A.

  • Despite El-A is shifted to the left from El-E it is still above it, hence listed.

  • El-C and El-D are not listed because they are not strictly above, i.e. their bottom border is not above the top border of El-E.

Another example

Let’s now look at one more example:

WebElement base = driver
        .findElement(By.id("e"));
List<WebElement> cells = driver
                    .findElements(
                            RelativeLocator
                                    .with(By.tagName("div"))
                                    .toRightOf(base));

Here we’re taking elements which are to the right of the base element. Hence, considering what we’ve discussed earlier, the output would be:

El-F
El-C

Another handy feature of RelativeBy is looking for objects around base element with near method. It also allows to limit the distance you want to cover with your lookup.

ByAll - "logical or" analog for By locators

Sometimes you have several By locators and you need to combine them. Like "this By or that By or that one". Having that you do not actually need to use anything but ByAll class. Let’s look at our sample page again.

For example we have two locators: for id="a" and for id="c". We might want to look up either one of them or both. Given that we can approach with ByAll like I’m showing below.

By superBy = new ByAll(
    By.id("a"),
    By.id("c")
);

List<WebElement> cells = driver
        .findElements(superBy);

ByChained - best solution for containerized UI

This facility especially useful when you store locator of a container and locator of an element that has to reside inside that container decoupled. If you look at our sample page code, you’ll see that A-cell content breaks down into two parts. We might need to access span that is inside A-cell. That means we need to look up the cell itself and then look up an element inside it. Using ByChained the code could look like this (I do not use lookup by id deliberately for the sake of demonstration):

By superBy = new ByChained(
    By.tagName("div"),
    By.tagName("span")
);

This is much like when you look up an element and use it as a search context to look up inner element. However there is some difference.

What is the difference between ByChained and using existing element as SearchContext

You might be wondering what is the difference from if we would do the following:

WebElement a = driver.findElement(By.tagname("div"));
WebElement span = a.findElement(By.tagName("span"));

First of all there might be several different elements on phase 1 (when you look up the outer element). Method findElement returns the first one found. But what if span resides in the second or in several divs?

ByChained solves this issue for you. It goes by the element tree’s all branches at once like if you would use //div/span xPath so you can combine the benefit of having element locators decoupling and crawling power of xPath queries.

Mix the approaches

Feel free to use different Bys together with the help of ByAll and ByChained. Any trivial By, those two Bys or any of your custom By are still Bys so that having a super-By built you can use it wherever a By can be used.

For example you can have the composition like this:

By superBy = new ByAll(
    By.id("d"),
    By.id("f"),
    new ByChained(By.tagName("div"), By.tagName("span"))
);

If you still have the questions please send them to me using this form. I will amend the article according to your feedback.