Find child elements with locator chaining

KIT518860_broad_chain_closeup_c_wikimedia.org_[1]

Have you ever had to find an element in another element?

Of course you have.

How did you do it?

 

Maybe you used a locator that searches for the parent element first and then for the child element in the parent.

 

Like in the following test:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By TITLE_LOCATOR = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]" +
    "//span[@class='title']");

  //driver variable, setUp() and tearDown() methods are skipped

  @Test
  public void test1() {
    driver.get(HOME_URL);
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java");

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement title = driver.findElement(TITLE_LOCATOR);
    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }

}

 

In this case, the test opens the site, searches for a keyword and then finds the title of the first result.

The locator of the title includes the locator of the first result:

private final By TITLE_LOCATOR = By.xpath(
   "(//div[contains(@data-js, 'bib-list-item')])[1]" + 
   "//span[@class='title']");

(//div[contains(@data-js, ‘bib-list-item’)])[1]  is the locator of the first result.

//span[@class=’title’] is the locator of the first result’s title.

 

This seems like a decent approach but is it not.

If the code generates an exception when trying to find the title element, is the error about the title not being available or about the first result?

Since the locator combines both, it would be difficult to know.

 

We can change the code so that the parent element is found first and stored in a WebElement variable.

 

The child element is then searched inside of the parent element:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By RESULT_BOX_XPATH  = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]");

  private By TITLE_XPATH = By.xpath(
    ".//span[@class='title']"); 

  //driver variable, setUp() and tearDown() methods are skipped

  @Test
  public void test2() {
    driver.get(HOME_URL);   
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java"); 

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement result = driver.findElement(RESULT_BOX_XPATH);
    WebElement title = result.findElement(TITLE_XPATH);
    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }
}

 

This time, the code finds the result element and stores it in a WebElement variable:

WebElement result = driver.findElement(RESULT_BOX_XPATH);

Then, it searches for the title element inside of the result element:

WebElement title = result.findElement(TITLE_INNER_XPATH);

Notice that the title locator starts with a dot.

TITLE_XPATH = By.xpath(".//span[@class='title']");

This is important when searching an element in another element.

When the code works this way, in case of an error, it is very clear which element caused it.

 

This is better than before but it extends poorly.

What if you need to find an element included in another element included in another element included in another element?

For each parent element, you need to search for the element and store it in a variable.

Which leads to lots of duplication.

This duplication can be removed using the ByChained Selenium class.

See how the code looks now:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By RESULT_BOX_XPATH  = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]");

  //driver variable, setUp() and tearDown() methods are skipped

  private By TITLE_XPATH = By.xpath(".//span[@class='title']");

  @Test
  public void test3() {
    driver.get(HOME_URL);    
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java");  

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement title = driver.findElement(
         new ByChained(RESULT_BOX_XPATH,
                TITLE_XPATH));

    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }  
}

The code to find the title looks rather weird:

WebElement title = driver.findElement(
         new ByChained(RESULT_BOX_XPATH,TITLE_XPATH));

It creates a new object for the ByChained class.

The object gets 2 parameters in the constructor for the locators of the parent and child elements.

Finding the element happens as follows:

  • the element for the 1st locator is searched
  • the element for the 2nd locator is searched in the first element

Using this approach scales well even if we have elements included in many other elements.

There are no additional WebElements that should be created and the code stays approximately the same size.

 


Tips and tricks are great.

Keep coming to this blog for more.

However, if you are interested in better and more detailed learning, this Kindle ebook will help you improve your Selenium code:

Improve Selenium Code with Automation Patterns: Page Object Model, Page Factory , Page Elements, Base Page, Loadable Component

I am using all these patterns daily and so can you.

3 Comments

Leave a comment