Relative Locators in Selenium 4

Salute, Khabrovsk. In anticipation of the start of the Java QA Engineer course , we have prepared a translation of interesting material for you.





Relative Locators


Selenium 4 brought us relative locators - Relative Locators (originally called Friendly Locators). This functionality has been added to help you find items that are next to other items.

Available options:

  • above () : the search item is above the specified item
  • below () : the searched item is below the specified item
  • toLeftOf () : the search item is located to the left of the specified item
  • toRightOf () : the search item is located to the right of the specified item
  • near () : the item you are looking for is located no more than 50 pixels from the specified item. There is also an overloaded method to specify the distance.

All of these methods are overloaded to accept By or WebElement.

Using relative locators


Using this bookstore app as an example , we want to verify that the book to the left of Advanced Selenium in Java is Java For Testers. Relative locators allow us to do this.


Here is the DOM fragment for the books


“Advanced Selenium in Java” is represented in the DOM by the identifier pid6, and “Java For Testers” is represented by pid5.

A method WebDriver::findElementcan take a method withTagName()that returns an object RelativeLocator.RelativeBy(a descendant of By).

driver.findElement(withTagName("li")


Here I can already specify relative locators. I know that “Java For Testers” is to the left of “Advanced Selenium in Java” (pid6) and is below “Test Automation in the Real World” (pid1). So, I can point out from both:

driver.findElement(withTagName("li")
                .toLeftOf(By.id("pid6"))
                .below(By.id("pid1")));

And I get Java For Testers (pid5).

@Test
public void test_book5_is_left_of_book6_and_below_book1(){
    String id = driver.findElement(withTagName("li")
            .toLeftOf(By.id("pid6"))
            .below(By.id("pid1")))
            .getAttribute("id");
 
    assertEquals(id, "pid5");
}

We can use above()and methods toRightOf()to find Experiences of Test Automation (pid2):

@Test
public void test_book2_is_above_book6_and_right_of_book1(){
    String id = driver.findElement(withTagName("li")
                    .above(By.id("pid6"))
                    .toRightOf(By.id("pid1")))
            .getAttribute("id");
 
    assertEquals(id, "pid2");
}

How it works?


I could not find “Java For Testers” only by calling toLeftOf (By.id (“pid6”)) . Alone, he will return Test Automation in the Real World (pid1). This is because it driver.findElement()searches from the root of the DOM, and the first item to the <li>left of Advanced Selenium in Java is Test Automation in the Real World.

Selenium uses the JavaScript function getBoundingClientRect()to search for relative elements. This function returns element properties, such as right, left, bottom, and top.

Looking at the properties of these three books, we see that both Test Automation in the Real World (pid1) and Java For Testers (pid5) both have the same x-axis position.



Thus, they are both to the left of Java For Testers, with Test Automation in the Real World (pid1) being the first one found.

Another point to keep in mind is changes to the application viewports. If I ran the tests mentioned above on my application in the mobile view, then naturally they would have failed because the books are no longer on the left / right of other books:



I also ran into a problem with friendly locators when I tried to use them in this ToDo app .



I tried using the method toLeftOf()to find the input switch next to the “goodbye world” element. Visually, this input switch is located to the left of the “goodbye world” label. Here is this div in the DOM:

<div class="view" data-reactid=".0.1.2.$645c3b67-884e-4e4f-aecd-8f9367e670f8.0">
    <input class="toggle" type="checkbox" data-reactid=".0.1.2.$645c3b67-884e-4e4f-aecd-8f9367e670f8.0.0">
    <label data-reactid=".0.1.2.$645c3b67-884e-4e4f-aecd-8f9367e670f8.0.1">goodbye world</label>
    <button class="destroy" data-reactid=".0.1.2.$645c3b67-884e-4e4f-aecd-8f9367e670f8.0.2"></button>
</div>

Here is the code I used to find the input element to the left of the label:

driver.findElement(withTagName("input")
      .toLeftOf(By.xpath("//label[text()='goodbye world']")))
      .click();

However, I came across an exception: It seems that although this one is to the left of the visual one, in reality it is not. I called a function for both of these elements, and they actually overlap. Note that they both have position x 838, so technically not to the left of . And when I select the element , I now see that it really overlaps the element . <img src = " habrastorage.org/webt/hj/aa/46/hjaa46ps-of5_hhlplqaldclc7s.png " /

org.openqa.selenium.NoSuchElementException: Cannot locate an element using [unknown locator]

<input><label>getBoundingClientRect()<input><label>



<label><input>


Note : This is an alpha version of Selenium WebDriver. I spoke with Selenium project manager Simon Stewart and found out that the implementation may change depending on the feedback.

That's all. See you on the course !

All Articles