How to use locators with parameters

Sometimes, you need to interact in automation scripts with web elements that have similar HTML code.

For example, you may want to automate the following test case:

  1. open site
  2. search for keyword
  3. check that the results page is displayed
  4. select the 2nd page of results
  5. check that the 2nd page is displayed
  6. select the 3rd page of results
  7. check that the 3rd page is displayed

 

The elements for the 2nd and 3rd pages have similar HTML code (A tag, HREF and DATE-PAGE attributes):

page 2
<a href=”/catalog/category.aspx?category=29059;page=2″ data-page=”2″>2</a>

page 3
<a href=”/catalog/category.aspx?category=29059;page=3″ data-page=”3″>3</a>

 

There are a few options for selecting similar page elements:

  1. use a locator for each page element
  2. use a method that creates the locator for each page locator
  3. use a locator pattern with a parameter

 

The code for option 1 is below:

import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.openqa.selenium.By;
import static org.openqa.selenium.By.*;
import org.openqa.selenium.WebElement;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;

public class BestBuyTest extends BaseTest {

String homePageUrl = "http://www.bestbuy.ca/";

By searchTextBoxLocator = By.id("ctl00_MasterHeader_ctl00_uchead_GlobalSearchUC_TxtSearchKeyword");

By searchButtonLocator = By.className("icon-search");

String resultsPageUrl = "http://www.bestbuy.ca/en-CA/category/";

By page2Locator = By.xpath(
"//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" + "//a[@data-page='2']");

By page3Locator = By.xpath(
"//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='3']");

@Test
public void TestPaging1() throws Exception {

  openSite();
  searchBy("ipad");

  WebElement page = wait.until(
                     elementToBeClickable
                      (page2Locator));

  page.click();

  assertTrue(isPageDisplayed(2));

  page = wait.until(
          elementToBeClickable
           (page3Locator));

  page.click();

  assertTrue(isPageDisplayed(3));

}

private void openSite() {
  driver.get(homePageUrl);

  if (wait.until(
       urlToBe(homePageUrl))== false )

    throw new RuntimeException
      ("The home page is not loaded correctly!");
}

private void searchBy(String keyword) {
  WebElement searchTextBox = wait.until(
                               elementToBeClickable
                                (searchTextBoxLocator));

  searchTextBox.sendKeys("ipad");

  WebElement searchButton = wait.until(
                             elementToBeClickable
                              (searchButtonLocator));

  searchButton.click();

  if (wait.until(
       urlContains
        (resultsPageUrl))== false )

     throw new RuntimeException
     ("The results page is not loaded correctly!");
}

private boolean isPageDisplayed(int pageNumber) {
  return wait.until(
          urlContains
           ("page=" + pageNumber));
}

}

This option is not good because of the duplicated page locators.

 

Option 2 uses a method that generates the page locator:

@Test
public void TestPaging2() throws Exception {

  openSite();
  searchBy("ipad");

  WebElement page = wait.until(
                     elementToBeClickable
                      (pageLocator(2)));

  page.click();

  assertTrue(isPageDisplayed(2));

  page = wait.until(
           elementToBeClickable
            (pageLocator(3)));

  page.click();

  assertTrue(isPageDisplayed(3));

}

private By pageLocator(int page) {
String locatorText = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='" +page"']";

return By.xpath(locatorText);
}

The pageLocator() method creates the locator using a page number parameter.

The locator is created by concatenating 3 parts:

  • the beginning of the xpath expression (before the page number)
  • the page number
  • the ending of the xpath expression

 

private By pageLocator(int page) {
String locatorText = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='"page"']";

return By.xpath(locatorText);
}

With the pageLocator() method, there is no need of having a locator for each page element.

But the locator text was moved inside of the method.

And the method’s code is pretty complicated.

 

Option 3 is using a locator pattern with a parameter:

String pageLocatorTemplate = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='%d']";

@Test
public void TestPaging3() throws Exception {

  openSite();
  searchBy("ipad");

  WebElement page = wait.until(
                     elementToBeClickable
                      (pageLocatorFromTemplate(2)));
  page.click();

  assertTrue(isPageDisplayed(2));

  page = wait.until(
          elementToBeClickable
           (pageLocatorFromTemplate(3)));

  page.click();

  assertTrue(isPageDisplayed(3));

}

private By pageLocatorFromTemplate(int page) {
  return By.xpath(
          String.format
           (pageLocatorTemplate, page));
}

We use now a locator pattern with a parameter (%d):

String pageLocatorTemplate = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='%d']";

 

A method is still needed for creating the locator.

The method has a pageNumber parameter which is substituted to the parameter (%d) of the locator pattern by the String.format() method:

private By pageLocatorFromTemplate(int page) {
  return By.xpath(
          String.format
           (pageLocatorTemplate, page));
}

The 3rd solution is the best of the 3 presented.

 

Advertisement

How to use regular expressions in Selenium WebDriver tests

 

Regular expressions are a very useful technique for improving Selenium WebDriver tests.

They can be used for

  • extracting text from the value of a webelement
  • validating if a value matches a specific pattern
  • validating if a url matches a pattern

Let’s look into regular expressions using a specific test case.

Our test case does the following:

  1. open the home page of a site (http://www.vpl.ca)
  2. execute a keyword search (example: keyword = java)
  3. the first results page is displayed
  4. validate that the url is correct (https://vpl.bibliocommons.com/search?q=java&t=keyword)
  5. validate that results count is greater than 0 (1 – 25 of 905 items; the results count is 905)
  6. select page 2 of results
  7. the second results page is displayed
  8. validate that the url is correct (https://vpl.bibliocommons.com/search?display_quantity=25&page=2&q=java&t=keyword)
  9. validate that the results count is greater than 0 (26 – 50 of 905 items; the results count is 905)

For the first validation, we should verify that the following urls are correct:

https://vpl.bibliocommons.com/search?q=java&t=keyword

https://vpl.bibliocommons.com/search?display_quantity=25&page=2&q=java&t=keyword

It would be great to use the same code to validate both urls since they are similar.

The second url just adds 2 additional parameters.

For the second validation, we should extract the results count from the following texts:

1 – 25 of 905 items

26 – 50 of 905 items

Again, we should try to use the same code to extract the result count from both texts since they have the same pattern.

For both validations, we could use String methods and variables.

But regular expressions do the same things better.

First, some details about the project

The project uses 3 classes:

  1. test class
  2. home page class
  3. results  page class

The code of the TEST CLASS is

public class Tests {
WebDriver driver;

@Before
public void setUp() {
  driver = new FirefoxDriver();
}

@After
public void tearDown() {
  driver.quit();
}

@Test
public void testResultsDetails() throws Exception {
  HomePage homePage = new HomePage(driver);
  homePage.open();

  ResultsPage resultsPage = homePage.search("java");
  assertTrue(resultsPage.resultsCount() > 0);

  resultsPage = resultsPage.selectPage(2);
  assertTrue(resultsPage.resultsCount() > 0);
}

}

The code should be very easy to understand:

  1. An object is created for the home page using the HomePage class.
  2. The homePage object uses the open() method to open the site
  3. The homePage object uses the search() method to search for a keyword

The code of the HOMEPAGE class is

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class HomePage {

WebDriver driver;
WebDriverWait wait;

By searchTextBoxLocator = By.id("globalQuery");
By searchButtonLocator = By.className("search_button");

String url = "http://www.vpl.ca/";
String title = "Vancouver Public Library - Home";

public HomePage(WebDriver driver) throws Exception {
  this.driver = driver;
  wait = new WebDriverWait(driver, 20);
}

public void open() throws Exception {
  driver.get(url);

  if (urlIs(url) == false || 
      titleIs(title) == false)
   throw new Exception("home page is not displayed");
}

private Boolean urlIs(String url) {
  return wait.until(ExpectedConditions.urlContains(url));
}

private Boolean titleIs(String title) {
  return wait.until(ExpectedConditions.titleIs(title));
}

public ResultsPage search(String keyword) throws Exception {
  WebElement searchTextBox = wait.until(ExpectedConditions.
            elementToBeClickable(searchTextBoxLocator));
  searchTextBox.sendKeys(keyword);

  WebElement searchButton = wait.until(ExpectedConditions.
            elementToBeClickable(searchButtonLocator));
  searchButton.click();

  return new ResultsPage(driver, keyword);
}

}

The HomePage class has the following methods:

  1. constructor – saves the driver object into a class field; instantiates the explicit wait object
  2. open() – opens the site and verifies that the title and url of the home page are correct
  3. search() – executes a keyword search

The last class is RESULTSPAGE.

All interesting things happen here.

Validate that the urls are correct

The code of the class is below.

All lines related to regular expressions are highlighted:

public class ResultsPage {

WebDriver driver;
WebDriverWait wait;

String keyword;

String urlRegularExpression = "https:(\\/\\/)vpl(\\.)bibliocommons(\\.)com" +
"(\\/)search(\\?)(display_quantity=(\\d+)&page=(\\d+))*(\\&)*" +
"q=([a-z]+)&t=keyword";

String title = "Search | Vancouver Public Library | BiblioCommons";

By resultsCountLocator = By.className("items_showing_count");
String pageLinksLocator = "(//a[@class='page_link '])";

public ResultsPage(WebDriver driver, String keyword) throws Exception {
  this.driver = driver;
  wait = new WebDriverWait(driver, 20);
  this.keyword = keyword;

  if (!urlContains(keyword) || 
      !correctUrlPattern()  || 
      !titleIs(title) == false)
    throw new Exception("results page is not displayed");
}

private Boolean correctUrlPattern() {
  return matchFound(urlRegularExpression, 
                    driver.getCurrentUrl());
}

private Boolean urlContains(String keyword) {
  return wait.until(ExpectedConditions.urlContains(keyword));
}

private Boolean titleIs(String title) {
  return wait.until(ExpectedConditions.titleIs(title));
}

public Boolean matchFound(String patternValue, String value) {
  Pattern pattern = Pattern.compile(patternValue);
  Matcher matcher = pattern.matcher(value);
  return matcher.find();
}

public ResultsPage selectPage(int pageNumber) throws Exception {
  WebElement page = wait.until(ExpectedConditions.
                     elementToBeClickable(pageLocator(pageNumber)));
  page.click();
  return new ResultsPage(driver, keyword);
}

public By pageLocator(int pageNumber) {
  String locator = pageLinksLocator + "[" + pageNumber + "]";
  return By.xpath(locator);
}

}

The url validations are being done by the matchFound() method which has 2 parameters:

  • regular expression to be applied to a value
  • value that will be evaluated by the regular expression

The method uses the Pattern class to create a pattern object from a regular expression.

It also uses the Matcher class to match the pattern to a value.

If the pattern matches the value, the find() method returns true.

Otherwise, the find() method returns false.

The matchFound() method is then used in the correctUrlPattern() method with 2 parameters:

  • url regular expression; the regular expression matches the urls of both pages
  • url value for the results page

We have a few problems with this code.

First, the url regular expression looks very complicated.

Second, it does not take into consideration the search keyword.

We do check if the keyword is included in the url but not through the regular expression.

Both problems are solved in the code that follows:

public class ResultsPage {

WebDriver driver;
WebDriverWait wait;

String keyword;

String title = "Search | Vancouver Public Library | BiblioCommons";

By resultsCountLocator = By.className("items_showing_count");
String pageLinksLocator = "(//a[@class='page_link '])";

public ResultsPage(WebDriver driver, String keyword) throws Exception {
  this.driver = driver;
  wait = new WebDriverWait(driver, 20);
  this.keyword = keyword;

  if (correctUrlPattern() == false || titleIs(title) == false)
    throw new Exception("results page is not displayed");
}

private Boolean correctUrlPattern() {
  return matchFound(urlRegularExpression(), 
                    driver.getCurrentUrl());
}

public String urlRegularExpression() {

  String protocol = "https:(\\/\\/)";
  String domain = "vpl(\\.)bibliocommons(\\.)com";
  String pageName = "(\\/)search(\\?)";
  String displayQuantityParameter = "(display_quantity=(\\d+)&)*";
  String pageNumberParameter = "(page=(\\d+)\\&)*";
  String keywordParameter = "q=" + keyword + "&";
  String searchTypeParameter = "t=keyword";

  String pattern = composePattern(protocol,
                                  domain,
                                  pageName,
                                  displayQuantityParameter,
                                  pageNumberParameter,
                                  keywordParameter,
                                  searchTypeParameter);

  return pattern;
}

private String composePattern(String... patterns) {
  StringBuilder completePattern = new StringBuilder();

  for (String pattern: patterns)
    completePattern.append(pattern);

  return completePattern.toString();
}

private Boolean titleIs(String title) {
return wait.until(ExpectedConditions.titleIs(title));
}

public Boolean matchFound(String patternValue, String value) {
  Pattern pattern = Pattern.compile(patternValue);
  Matcher matcher = pattern.matcher(value);
  return matcher.find();
}

public ResultsPage selectPage(int pageNumber) throws Exception {
  WebElement page = wait.until(ExpectedConditions.
             elementToBeClickable(pageLocator(pageNumber)));
  page.click();
  return new ResultsPage(driver, keyword);
}

public By pageLocator(int pageNumber) {
  String locator = pageLinksLocator + "[" + pageNumber + "]";
  return By.xpath(locator);
}

}

Instead of having the url regular expression as a very long string value, we decompose it in small parts which are then put together by the composePattern() method.

Building the regular expression this way makes the expression much easier to understand and change.

In the same urlRegularExpression() method, we use the keyword as part of the regular expression value.

By doing this, the regular expression validates not only the url template but also if the keyword value is included in it.

So we dont need an additional method for checking if the url contains the keyword.

Extract the results count

For the results count, we will need

  1. a new regular expression that matches the result count element text (1 – 25 of 905 items or 26 – 50 of 905 items)
  2. a new method that extract the result count from the result count element text (905)
public class ResultsPage {

WebDriver driver;
WebDriverWait wait;

String keyword;

String resultsCountRegularExpression = "of (\\d+) items";

String title = "Search | Vancouver Public Library | BiblioCommons";

By resultsCountLocator = By.className("items_showing_count");
String pageLinksLocator = "(//a[@class='page_link '])";

public ResultsPage(WebDriver driver, String keyword) throws Exception {
  this.driver = driver;
  wait = new WebDriverWait(driver, 20);
  this.keyword = keyword;

  if (correctUrlPattern() == false ||
      titleIs(title) == false)
   throw new Exception("results page is not displayed");
}

private Boolean correctUrlPattern() {
  return matchFound(urlRegularExpression(), 
                    driver.getCurrentUrl());
}

public String urlRegularExpression() {

  String protocol = "https:(\\/\\/)";
  String domain = "vpl(\\.)bibliocommons(\\.)com";
  String pageName = "(\\/)search(\\?)";
  String displayQuantityParameter = "(display_quantity=(\\d+)&)*";
  String pageNumberParameter = "(page=(\\d+)\\&)*";
  String keywordParameter = "q=" + keyword + "&";
  String searchTypeParameter = "t=keyword";

  String pattern = composePattern(protocol,
                                  domain,
                                  pageName,
                                  displayQuantityParameter,
                                  pageNumberParameter,
                                  keywordParameter,
                                  searchTypeParameter);

  return pattern;
}

private String composePattern(String... patterns) {

  StringBuilder completePattern = new StringBuilder();

  for (String pattern: patterns)
     completePattern.append(pattern);

  return completePattern.toString();
}

private Boolean titleIs(String title) {
  return wait.until(ExpectedConditions.titleIs(title));
}

public int resultsCount() {
  return Integer.parseInt(
           matchedGroupValue(resultsCountRegularExpression,
                             resultsCountText(),
                             1));
}

public String resultsCountText() {
  WebElement resultsCountLabel = wait.until(ExpectedConditions.
            visibilityOfElementLocated(resultsCountLocator));
  return resultsCountLabel.getText();
}

public Boolean matchFound(String patternValue, String value) {
  Pattern pattern = Pattern.compile(patternValue);
  Matcher matcher = pattern.matcher(value);
  return matcher.find();
}

public String matchedGroupValue(String patternValue, String value, int index) {
  Pattern pattern = Pattern.compile(patternValue);
  Matcher matcher = pattern.matcher(value);
  if (!matcher.find())
    throw new NoSuchElementException("couldnt find the matched group value");

  return matcher.group(index);

}

public ResultsPage selectPage(int pageNumber) throws Exception {
  WebElement page = wait.until(ExpectedConditions.
           elementToBeClickable(pageLocator(pageNumber)));
  page.click();
  return new ResultsPage(driver, keyword);
}

public By pageLocator(int pageNumber)
{
  String locator = pageLinksLocator + "[" + pageNumber + "]";
  return By.xpath(locator);
}

}

A new method, matchedGroupValue(),  is created for extracting the result count value.

It works by

  1. creating a pattern from a regular expression
  2. matching the pattern with a value
  3. if the pattern does not match the value, throw an exception
  4. if the pattern matches the value, it extracts a group from the value

The matchedGroupValue() method is then used in the resultCount() method for extracting the resultCount value from the result count element text.