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.

 

Advertisements

Do not store element locators in property files

You have a small test automation project with about 50 test scripts and 10 page object classes.

Each page object class uses approximately 10 web elements.

The project has around 100 web element locators.

Where should the elements’ locators be stored?

Should they be stored in the page object classes, in enumerations, classes with static members or property files?

The last option, property files, seems the best since every time more locators are needed, you just change a text file and not the project’s code.

So for each page object class, you create a property text file and save all element locators in it.

The page classes become smaller this way.

What can be wrong in this case?

The project grew from the initial size to

  • 1000 test scripts
  • 100 page object classes
  • 3000 element locators

Lets say that we are working on a class such as the next one:

public class HomePage {

  WebDriver driver;
  Properties properties;

  public HomePage(WebDriver driver) {
    this.driver = driver;
    this.properties = new Properties();
    InputStream input = new FileInputStream("home.properties");
    this.properties.load(input);
  }

  public void search(String keyword) {

   By searchBoxXpath = By.xpath(properties.get("searchTextBox"));
   WebElement searchTextBox =  driver.findElement(searchBoxXpath);

   searchTextBox.clear();
   searchTextBox.sendKeys(keyword);

   String searchButtonXpath = By.xpath(properties.get("searchButton"))

   WebElement searchButton = driver.findElement(searchButtonXpath);

   searchButton.click();

  }
}

The constructor loads the content of the home.properties file in the properties variable.

The search() method uses the name of the element which is then searched in the properties file.

The home.properties file looks as follows:

searchTextBox = //input[@elementid = 'abc']
searchButton = //input[@elementid = 'def']

Now, we need to change the locators for both elements used in the HomePage class.

How do we do this?

  1. Copy the element name (“searchTextBox”) from the HomePage class
  2. Locate the home.properties file in the project
  3. Open the home.properties file
  4. Search for the element name (“searchTextBox”)
  5. Modify the xpath expression
  6. Save and close the property file

6 steps and a few minutes for changing the value of a locator!
Remember that the project has 3000 locators at this moment.

1st problem of using property files for locators:
it is very time consuming to change the value of a locator.

 

Lets look at the locators situation from a different perspective.

Lots of code has been added, changed and removed from the project in the last year.

How do you know which locators are no longer needed?

With the locators in property files, this is impossible to see immediately.

2nd problem of using property files for locators:
it is not possible to see which locators are no longer used by the page classes.

 

Finally, we are writing test automation code using a language such as Java or C#.

The code that implements the user interactions with each page is organized in classes.

A class should group data and methods together.

By having the locators in the property files, this rule is broken.

3rd problem of using property files for locators:
classes are used incorrectly as they have methods that do not work on class members.

What is the correct approach for saving locators?

Save the locators in the page classes together with the methods.

Doing this allows

  • easy change of a locator in a minimum of steps
  • easy identification of locators not used
  • page classes include locators which are used by class methods

How to work with cookies

Browsers use cookies to store user preferences such as

  • user location
  • number of results per page
  • sort order
  • language

Next time the user visits the site, the user preferences saved in the cookies are set automatically for the site.

Each cookie is defined by the following fields:

  • name
  • value
  • domain
  • path
  • expires
  • size
  • http
  • isSecure
  • sameSite

To take an example, for the Vancouver Public Library – Results Page page, the selected language is saved in the language cookie with the following fields:

  • name = language
  • value = en-CA
  • domain = vpl.bibliocommons.com
  • path = /
  • expires = the expiration date set by the site
  • size = 13
  • http =
  • isSecure = true
  • sameSite =

If the language is changed from english to french, the value of the cookie changes to fr-CA.

It is important to be able to work with cookies in Selenium scripts for scenarios such as

  1. user changes the language and verifies that the change is done correctly
  2. user changes the sort order and verifies that the change is done correctly
  3. user changes the results per page and verifies that the change is done correctly

The cookies support is provided by the WebDriver.Options interface that has the following methods:

void addCookie(Cookie cookie)
Add a specific cookie.

void deleteAllCookies()
Delete all the cookies for the current domain.

void deleteCookie(Cookie cookie)
Delete a cookie from the browser’s “cookie jar”.

void deleteCookieNamed(java.lang.String name)
Delete the named cookie from the current domain.

Cookie getCookieNamed(java.lang.String name)
Get a cookie with a given name.

java.util.Set getCookies()
Get all cookies for the current domain.

An example will clarify how to use these methods.

The following code sample does the following

  1. open a web page: https://vpl.bibliocommons.com/search?q=java&t=keyword
  2. verify that the default language is english (en-CA) by checking the value of the language cookie
  3. verify that the page title is correct for english
  4. change the language to french
  5. verify that the language is french (fr-CA) by checking the value of the language cookie
  6. verify that the page title is correct for french

 

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.util.Date;

public class Class1 {

WebDriver driver;
WebDriverWait wait;

@Before
public void setUp() {
  System.setProperty("webdriver.chrome.driver", 
  "c:/selenium/selenium3/chromedriver.exe");
  
  driver = new ChromeDriver();
  
  wait = new WebDriverWait(driver, 30);
}

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

@Test
public void testChangeLanguage() throws InterruptedException {
  openSite();
  assertEquals(currentLanguage(), "en-CA");
  assertTrue(isTitleCorrect()));

  selectLanguage("fr-CA");

  goToPage(2);
  assertEquals(currentLanguage(), "fr-CA");
  assertTrue(isTitleCorrect());
}

private void openSite() {
  driver.get("https://vpl.bibliocommons.com/search?q=java&t=keyword");
}

private void goToPage(int pageNumber) {
  By pageLocator = By.xpath("//a[@testid='link_page" + 
                            pageNumber + "']");
  wait.until(ExpectedConditions
    .elementToBeClickable(pageLocator))
    .click();
}

private void selectLanguage(String language) {
  By changeLanguageMenuLocator = By.id("biblio_language_trigger");
  By languageLocator = By.xpath("//a[contains(@href,'" + 
                                language + 
                                "')]");
  wait.until(ExpectedConditions
     .elementToBeClickable(changeLanguageMenuLocator))
     .click();
  
  wait.until(ExpectedConditions
     .visibilityOfElementLocated(languageLocator));
  
  wait.until(ExpectedConditions
     .elementToBeClickable(languageLocator))
     .click();
}

private boolean isTitleCorrect() {
  if (currentLanguage().equalsIgnoreCase("en-CA") )
   return wait.until(ExpectedConditions
     .titleIs("Search | Vancouver Public Library | BiblioCommons"));
  else
   return wait.until(ExpectedConditions
     .titleIs("Rechercher | Vancouver Public Library | BiblioCommons"));
}

private String currentLanguage() {
  return getCookieValue("language");
}

private String getCookieValue(String cookieName) {
  return driver.manage()
               .getCookieNamed(cookieName)
               .getValue();
}

}

The code should be very easy to understand.

It interacts with cookies in 1 place only to get the value of a cookie (getCookieValue() method.

Is this all we can do with cookies?

In addition to getting the value of a cookie, we can also delete a cookie or even create a new one.

The previous script is very slow as it has to change the language manually.

We could change the language value by modifying the cookie value instead of clicking the language element.

The benefit of doing this is that the script will be faster than before as it has to go through less pages.

See below the changed code.

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.util.Date;

public class Class1 {

WebDriver driver;
WebDriverWait wait;

@Before
public void setUp() {
  System.setProperty("webdriver.chrome.driver", 
                     "c:/selenium/selenium3/chromedriver.exe");
  
  driver = new ChromeDriver();
  
  wait = new WebDriverWait(driver, 30);
}

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

@Test
public void testChangeLanguageByAddingCookie() throws InterruptedException {
  openSite();
  assertEquals(getCookieValue("language"), "en-CA");
  assertTrue(isTitleCorrect());

  addCookie("language", "fr-CA");

  goToPage(2);
  assertEquals(getCookieValue("language"), "fr-CA");
  assertTrue(isTitleCorrect());
}

private void openSite() {
  driver.get("https://vpl.bibliocommons.com/search?q=java&t=keyword");
}

private void goToPage(int pageNumber) {
  By pageLocator = By.xpath("//a[@testid='link_page" + 
                            pageNumber + "']");
  
  wait.until(ExpectedConditions
    .elementToBeClickable(pageLocator))
    .click();
}

private void selectLanguage(String language) {
  By changeLanguageMenuLocator = By.id("biblio_language_trigger");
  By languageLocator = By.xpath("//a[contains(@href,'" + 
                                language + 
                                "')]");
  
  wait.until(ExpectedConditions
      .elementToBeClickable(changeLanguageMenuLocator))
      .click();
  wait.until(ExpectedConditions
      .visibilityOfElementLocated(languageLocator));
  
  wait.until(ExpectedConditions
     .elementToBeClickable(languageLocator))
     .click();
}

private void deleteCookie(String cookieName) {
  driver.manage()
        .deleteCookieNamed(cookieName);
}

private String currentLanguage() {
  return getCookieValue("language");
}

private boolean isTitleCorrect() {
  if (currentLanguage().equalsIgnoreCase("en-CA") )
   return wait.until(ExpectedConditions
    .titleIs("Search | Vancouver Public Library | BiblioCommons"));
  else
   return wait.until(ExpectedConditions
    .titleIs("Rechercher | Vancouver Public Library | BiblioCommons"));
}

private String getCookieValue(String cookieName) {
  return driver.manage()
               .getCookieNamed(cookieName)
               .getValue();
}

private void addCookie(String name, String value) {
  deleteCookie(name);

  Cookie cookie = new Cookie.Builder(name, value)
   .domain(".bibliocommons.com")
   .expiresOn(new Date(2022, 03, 13))
   .isHttpOnly(false)
   .isSecure(true)
   .path("/")
   .build();

  driver.manage().addCookie(cookie);
}

}

The script is very similar with the previous one.

The only change is that instead of using the site to change the language, the language cookie is first deleted and then recreated for the new language.

Where can I find a full test automation project with Selenium WebDriver?

You have been learning test automation with Selenium.

You went through lots of concepts and would like to see how they all work together.

Where can you find a project that uses everything you learned and more?

Here 🙂

What follows is a small project that I built a while ago for a job interview.

It uses many test automation concepts such as:

  • page factory
  • base classes
  • html classes
  • test listeners
  • test ng assertions and fixtures
  • annotations
  • custom locators (javascript and jquery)
  • screenshots
  • saving errors in text files

The exercise consisted in automating the following test case with Java and Selenium WebDriver:

  • Launch bestbuy url (www.bestbuy.ca)
  • Search a product and add it to cart
  • Go all the way through checkout process and place the order with invalid credit card
  • Capture the error message due to invalid credit card

Before downloading the project and checking the source code, a few details about the project.

Project details

Maven project
– all dependencies are managed through the pom.xml file

 Test NG

– unit testing library

Java JDK 8

– used for lambda expressions and streams

Page Factory

– pattern for creating page object and page fragment classes

– the elements of page object/fragment classes have names and locators

– names and locators are implemented using annotations

– available locator types are id, xpath, css, name and javascript

  
   @Name("SEARCH_HEADER")
   @FindBy(className = "main-navigation-container") 
   public class SearchHeader extends HtmlElement{ 
 
   @Name("SEARCH_FIELD")
   @FindBy(id = "ctl00_MasterHeader_ctl00_uchead_GlobalSearchUC_TxtSearchKeyword") 
   private TextInput searchKeywordTxt;
 
   @Name("SEARCH_BUTTON")
   @FindBy(id = "ctl00_MasterHeader_ctl00_uchead_GlobalSearchUC_BtnSubmitSearch")
   private Button searchBtn; 
     
   public void search(String keyword) {
     searchKeywordTxt.click();  
     searchKeywordTxt.clear();
     searchKeywordTxt.sendKeys(keyword);
     searchBtn.click();  
   }
}

 

The project has the automation framework classes in the main folder and all test items in the test folder.

Main folder (framework classes)

annotations classes
– FindBy
– FindByJQUERY
– FindByJS
– Name
– Timeout
 
 package com.bestbuy.demo.annotations;

 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;

 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.TYPE, ElementType.FIELD})
 public @interface Name {
   String value();
 }

 

html element classes
 
    package com.bestbuy.demo.element;

    import org.openqa.selenium.By;
    import org.openqa.selenium.NoSuchElementException;
    import org.openqa.selenium.WebElement;

    public class CheckBox extends TypifiedElement {
   
      public CheckBox(WebElement wrappedElement) {
        super(wrappedElement);
      }

      public WebElement getLabel() {
        try {
            return getWrappedElement().findElement(By.xpath("following-sibling::label"));
        } catch (NoSuchElementException e) {
            return null;
        }
      }

      public String getLabelText() {
        WebElement label = getLabel();
        return label == null ? null : label.getText();
      }

      public String getText() {
        return getLabelText();
      }

      public void select() {
        if (!isSelected()) 
            getWrappedElement().click();        
      }

      public void deselect() {
        if (isSelected()) 
            getWrappedElement().click();
      }

      public void set(boolean value) {
        if (value) 
           select();
        else 
           deselect();
      }
   }

exceptions classes

 decorator and proxy classes used by the page factory

page class

– used as base class for the page object classes

 page factory classes

miscellaneous classes such as

– custom driver class, screenshot class
– enumerations (used to avoid hardcoding data in the page objects)
– simple logger class
– property class
– TextFile class

 

Test folder (test items)

    • base test class (used as base by the test classes)
    • page objects classes (all page object and page fragment classes)
    • test listeners (class for taking a screenshot and logging exceptions in case of failures)
 
 package com.bestbuy.demotests.testlisteners;

 import java.lang.reflect.Field;
 import org.testng.ITestContext;
 import org.testng.ITestListener;
 import org.testng.ITestResult;
 import com.bestbuy.demo.exceptions.HtmlElementsException;
 import com.bestbuy.demo.utils.Driver.BrowserDriver;
 import com.bestbuy.demo.utils.Driver.Screenshot;
 import org.openqa.selenium.WebDriver;
 import static com.bestbuy.demotests.BaseTest.BaseTestClass.*;

 public class TestListener implements ITestListener {   
   
    @Override
    public void onTestFailure(ITestResult result) {       
       try {
         Screenshot screenshot = 
         new Screenshot(getDriverFromBaseTest(result));
   
         screenshot.capture(result.getName());
       } 
       catch (Exception ex) {
         throw new HtmlElementsException(ex.getMessage());
       }                 
   }  

   @SuppressWarnings("unchecked")
   private WebDriver getDriverFromBaseTest(ITestResult result) 
       throws IllegalAccessException {
   
      WebDriver driver = null;
   
      try { 
         Class< ? extends ITestResult> testClass = 
         (Class< ? extends ITestResult>) result.getInstance().getClass();
  
         Class< ?extends ITestResult> baseTestClass = 
         (Class< ? extends ITestResult>) testClass.getSuperclass();
  
         Field driverField = baseTestClass.getDeclaredField("driver");
    
         driver = (BrowserDriver)driverField.get(result.getInstance()); 
    
         return driver;
     } 
     catch (SecurityException | NoSuchFieldException | IllegalArgumentException ex) {     
         throw new HtmlElementsException("error getting the driver from base test");    
     }         
   
   }
    
   @Override
   public void onTestSuccess(ITestResult result) 
   {}  
  
   @Override
   public void onTestSkipped(ITestResult result) 
   {} 
  
   @Override
   public void onTestFailedButWithinSuccessPercentage(ITestResult result) 
   {} 
  
   @Override
   public void onStart(ITestContext context)     
   {} 
  
   @Override 
   public void onFinish(ITestContext context)    
   {}
  
   @Override
   public void onTestStart(ITestResult arg0)
   {}
    
 }
  • test class

Most interactions with web elements are done through the page factory classes.

Occasionally, Javascript code is used when buttons cannot be clicked through the page factory.

Some of the pages have random popups that are closed if displayed.

The test script does the following:

  1. Open the home page
  2. Execute a keyword search
  3. Select the Online Only filter
  4. Select a product with online availability
  5. Add the product to the cart
  6. Continue checkout on the basket page
  7. Select New Member
  8. Fill in the Ship To info
  9. Select credit card as payment method
  10. Fill in the payment info
  11. Submits transaction
  12. Verify that there is at least an error message
  13. Saves the error messages and the search keyword to a text file

The test script uses a keyword parameter which can take multiple values.
Download source code

Interested in learning how to build a project like this one?

Get started here

How to create custom expected conditions in Selenium

You need custom expected conditions when the built-in Selenium WebDriver expected conditions are not sufficient for creating complex conditions.

Short review of expected conditions

Explicit waits and expected conditions are a great help for Selenium scripts.

Selenium WebDriver comes with a lot of expected conditions such as

  1. ExpectedCondition < WebElement > elementToBeClickable(By locator)
  2. ExpectedCondition < Boolean > elementToBeSelected(By locator)
  3. ExpectedCondition < WebElement > presenceOfElementLocated(By locator)
  4. ExpectedCondition < Boolean > titleContains(String title)
  5. ExpectedCondition < Boolean > titleIs(String title)
  6. ExpectedCondition < Boolean > urlContains(String fraction)
  7. ExpectedCondition < Boolean > urlToBe(String url)
  8. ExpectedCondition < WebElement > visibilityOfElementLocated(By locator)

There are 2 main expected condition types that can be used with explicit waits:

ExpectedCondition < WebElement >

This type of condition has a web element locator as parameter.

The wait applies the condition which tries finding the web element, depending on its status.

If the condition can find the element, it returns the element as result.

If it cannot find the element, the wait tries the condition again after a short delay.

ExpectedCondition < Boolean >

This type of condition has a string parameter.

The wait applies the condition to the parameter.

If the result of applying the condition is true, true is returned as result.

If the result is false, the wait tries the condition again after a short delay.

While the explicit wait applies the expected condition, the condition code may generate various exceptions.

All these exceptions are ignored by the wait.

Enough theory, lets see some code.

The following short example uses explicit waits and expected conditions to

  • verify if a page title is correct
  • verify if a page url is correct
  • find web elements
public class TestClass {

  WebDriver driver;
  WebDriverWait wait;

  By searchFieldXpath = By.id("globalQuery");
  By searchButtonXpath = By.className("search_button");
  By resultLinkLocator = By.xpath("(//a[@testid='bib_link'])[1]");
  
  String homeUrl = "http://www.vpl.ca";
  String homeTitle = "Vancouver Public Library - Home";

  String resultsTitle = "Search | Vancouver Public Library | BiblioCommons";
  String resultsUrl = "https://vpl.bibliocommons.com/search"; 

  @Before
  public void setUp() }
    driver = new FirefoxDriver();
    wait = new WebDriverWait(driver, 10);
  }

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

  @Test
  public void test1() {
    driver.get(homeUrl);

    if (!wait.until(titleContains(homeTitle)) || 
        !wait.until(urlContains(homeUrl)))
          throw new RuntimeException("home page is not displayed");

    WebElement searchField = wait.until(
                         elementToBeClickable(searchFieldXpath));
    searchField.click();
    searchField.sendKeys(keyword);

    WebElement searchButton = wait.until(
                          elementToBeClickable(searchButtonXpath));
    searchButton.click();

    if (!wait.until(titleContains(resultsTitle)) || 
        !wait.until(urlContains(resultsUrl)))
          throw new RuntimeException("results page is not displayed");
  }
}

We use 2 expected conditions for verifying if a page is displayed.

Selenium does not include by default an expected condition that checks that both the page title and url are correct.

This is where we can create custom expected conditions.

How to create a custom expected condition

A custom expected condition is a class that

  • implements the ExpectedCondition interface
  • has a constructor with the parameters of the expected condition
  • overrides the apply method

Lets rewrite the previous exercise with a custom expected condition that verifies if a page is displayed:

public class TestClass {

  WebDriver driver;	
  WebDriverWait wait;

  By searchFieldXpath = By.id("globalQuery");
  By searchButtonXpath = By.className("search_button");

  By resultLinkLocator = By.xpath("(//a[@testid='bib_link'])[1]");	
	
  String homeUrl = "http://www.vpl.ca"; 
  String homeTitle = "Vancouver Public Library - Home";

  String resultsTitle = "Search | Vancouver Public Library | BiblioCommons";
  String resultsUrl = "https://vpl.bibliocommons.com/search";

  @Before
  public void setUp() }
    driver = new FirefoxDriver();
    wait = new WebDriverWait(driver, 10);
  }
	
  @After
  public void tearDown() {
    driver.quit();
  }
	
  @Test
  public void test1() {
    driver.get(siteUrl);
				
    if (!wait.until(new PageLoaded(homeTitle, homeUrl)))
	throw new RuntimeException("home page is not displayed");
				
    WebElement searchField = wait.until(
               elementToBeClickable(searchFieldXpath));
    searchField.click();           
    searchField.sendKeys(keyword);
			
    WebElement searchButton = wait.until(
               elementToBeClickable(searchButtonXpath));
    searchButton.click();	
			
    if (!wait.until(new PageLoaded(resultsTitle, resultsUrl)))
	throw new RuntimeException("results page is not displayed");
  }
}

public class PageLoaded implements ExpectedCondition {		
  String expectedTitle;
  String expectedUrl;
	
  public PageLoaded(String expectedTitle, String expectedUrl) {
    this.expectedTitle = expectedTitle;	
    this.expectedUrl = expectedUrl;
  }
	
  @Override
  public Boolean apply(WebDriver driver) {		
    Boolean isTitleCorrect = driver.getTitle()
                                   .contains(expectedTitle);
    Boolean isUrlCorrect = driver.getCurrentUrl()
                                 .contains(expectedUrl);				
    return isTitleCorrect && isUrlCorrect;
  } 
}

The PageLoaded custom expected condition is used for verifying if

  • HomePage is displayed
  • ResultsPage is displayed

For ResultsPage, we would like to verify not only that the page title and url are correct but also that

  • there are 25 results loaded in the page
  • total result count is > 0

The following custom expected condition does it all:

public class ResultsPageLoaded implements ExpectedCondition {		
  By resultLocator = By.xpath("//div[contains(@data-analytics, 'SubFeature')]");	
  By resultCountLocator = By.xpath("//span[@class='items_showing_count']");
	
  String expectedTitle;
  String expectedUrl;
	
  public ResultsPageLoaded(String expectedTitle, String expectedUrl) {		
    this.expectedTitle = expectedTitle;	
    this.expectedUrl = expectedUrl;		
  }
	
  @Override
  public Boolean apply(WebDriver driver) {					
    Boolean isTitleCorrect = driver.getTitle()
                                   .contains(expectedTitle);
    Boolean isUrlCorrect = driver.getCurrentUrl()
                                 .contains(expectedUrl);
		
   int resultPerPageCount = driver.findElements(resultLocator)
                                  .size();
		
   WebElement resultCountElement = driver.findElement(resultCountLocator);		
		
   return 
    isTitleCorrect && 
    isUrlCorrect && 
    resultPerPageCount == 25 &&
    count(resultCountElement) > 0;			   
 } 
	
  private int count(WebElement element) {		
    String resultCountText = element.getText();
    int index1 = resultCountText.indexOf("of") + 3;
    int index2 = resultCountText.indexOf("items") - 1;
    resultCountText = resultCountText.substring(index1, index2);
    return Integer.parseInt(resultCountText);		
  }

}

Custom expected conditions can return not only a boolean value but also a WebElement.

Lets define a custom condition that

  • finds an element from its parent element
  • returns the element if it is displayed
public class ResultField implements ExpectedCondition {		
  By resultLocator;	
  By parentLocator;
	
  public ResultField(By resultLocator, By parentLocator) {
    this.resultLocator = resultLocator;
    this.parentLocator = parentLocator;			
  }
	
  @Override
  public WebElement apply(WebDriver driver) {		
    WebElement parent = driver.findElement(parentLocator);				
    WebElement result = parent.findElement(resultLocator);
		
    return (result.isDisplayed() ? result : null);			
  } 
}	

How do you use it?

WebElement titleLink = wait.until(new ResultField(resultLocator, titleLocator)); 
titleLink.click();

How to handle random modal windows in Selenium

Many sites use modal windows for sales events, announcements and user surveys.

Especially in the last 2 months of the year when there are so many holidays (Christmas, New Year’s Eve, Boxing Day).

1

A modal is an HTML element that is displayed above the current page.

It includes

  • title
  • description
  • action button (take a survey, visit on sale section, etc)
  • close button

The user cannot interact with the main page until the modal is closed.

Modals can be a problem for test automation scripts.

If the modal is displayed consistently on a page, it is easy for the automation script to interact with it (take the action suggested by the modal or close it).

But if the modal is displayed randomly, the test automation script needs to be able to deal with the modal when displayed and not displayed.

How do we interact with random modal windows?

Let’s see how to do it for a simple web page.

The web page displays initially a button:

2

When clicking the button, the modal is not displayed some times.

In these cases, the page displays the “popup not displayed” and “done” messages:

3

Other times, when clicking the button, the modal is displayed:

4

After closing the modal, the page displays “done”.

5

The sample code consists in 3 classes:

  • test class – includes setUp() and tearDown() methods and the test script
  • main class – implements the interactions with the main page
  • popup class – implements the interaction with the popup

TEST CLASS

import static org.testng.AssertJUnit.assertTrue;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class TestClass {

WebDriver driver;

@BeforeMethod
public void setUp()
{
driver = new ChromeDriver();
System.setProperty("webdriver.chrome.driver",
"C:\\Selenium\\chromedriver.exe");
}

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

@Test(invocationCount = 10)
public void testRandomPopup() {
MainPage mainPage = new MainPage(driver);
mainPage.open();

Popup popup = mainPage.displayPopup();
popup.close();

assertTrue(mainPage.done().equalsIgnoreCase("done"));
}

}

The test script does the following

  • opens the main page
  • clicks the button that may display the popup
  • closes the popup; if the popup is displayed, then it is closed; if the popup is not displayed, nothing happens
  • asserts that the work is done

MAIN PAGE CLASS

 
import org.openqa.selenium.By;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
 
public class MainPage {
 
  private final WebDriver driver;
  private final By doneStatusId = By.id("done");
  private final By displayPopupId = By.id("displayPopup");
 
  public MainPage(WebDriver driver) {
    this.driver = driver;
  }
 
  public void open() throws InterruptedException {
    driver.get("file:///C:/Users/home/Desktop/modal.html");
 
    if (driver.getTitle().contains("Test Random Popup") == false)
        throw new NotFoundException("main page not displayed");
  }
 
  public String done() {
    WebElement doneLabel = driver.findElement(doneStatusId);
    return doneLabel.getText();
  }
 
  public Popup displayPopup() {
    WebElement displayButton = driver.findElement(displayPopupId);
    displayButton.click();
 
    return new Popup(driver);
  }
 
}

The MainPage class has the following methods:

  • open() – opens the page and checks that the opened page is the correct one
  • displayPopup() – clicks the button that may display the popup
  • done() – returns the message displayed after the popup is handled

POPUP CLASS

 
import org.openqa.selenium.By;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;
import org.openqa.selenium.support.ui.WebDriverWait;
 
public class Popup {
 
  private final WebDriverWait wait;
  private final By closeElementLocator = By.id("closePopup");
  private final By popupIdLocator = By.id("popupId");
 
  public Popup(WebDriver driver) {
    this.wait = new WebDriverWait(driver, 5);
  }
 
  public void close() throws InterruptedException {
    if (isDisplayed()) {
        WebElement closeElement = wait.until(
               visibilityOfElementLocated(closeElementLocator));
        closeElement.click();
    }
  }
 
  private Boolean isDisplayed() throws InterruptedException {
     try {
        WebElement popup = wait.until(
                visibilityOfElementLocated(popupIdLocator));
        return popup.isDisplayed();
     }
     catch (Exception ex) {
       return false;
     }
 }
}

The popup class has 2 methods:

1. isDisplayed()

This is the most important method for managing the random modal.

It tries finding the modal element.

If the modal element is found, isDisplayed() returns true if modal is displayed and false otherwise.

If the popup is not displayed, an exception is thrown.

The exception is caught and isDisplayed() returns false;

2. close()

It closes the popup if it is displayed.

Thanks for reading.

Want to get started with Selenium WebDriver and Java in 10 short lessons?

How to interact with sliders in Selenium WebDriver

Sliders allow users to select a value by dragging and dropping a handle.

This value can be a price, a quantity, a year.

The web page could use a textbox for getting the same information from users.

But with sliders, the page becomes much more interesting.

Actually, many web pages do not use the slider or the text box but both of them as in the following image:

slider

Here, the user has plenty of options for changing the price:

    • drag and drop the slider handle
    • click the plus button multiple times
    • input the price in the textbox; the slider will update itself to match the new price

The slider stores its current position (as percentage) at all times.

The current position can be found in one of the slider’s attributes.

The current slider position corresponds to a value.
For example, if the slider is at 50%, this corresponds to a price of 500000.
If the slider is at 10%, the corresponding price is 100000.

The value that corresponds to the slider’s position is usually saved in a hidden element.

So, for a typical slider, we can have 4 different elements.

    • the slider

slider_html

    • the plus button

plus_button_html

    • the input element

purchase_price_html

    • the slider hidden element

hidden_slider_element_html

Lets see how the code looks for each of the slider scenarios already discussed.

Move Slider’s Handle To End

The first test does the following

  • open the site
  • find the slider element and assert that it is displayed
  • drag the slider handle to the end so that the slider percentage is 100%

Use the Actions class to build the list of actions to be done on the slider

(move to slider, click, drag and drop)

  • check that the value of the hidden element is 1000000;

The price is stored in the value attribute of the hidden element.

  • check that the slider percentage is 100%
public class TestSlider {
WebDriver driver;
WebDriverWait wait;
String url = "http://www.abc.com"; 

By priceSliderLocator = By.xpath("//div[@class='slider-handle min-slider-handle custom']");
By hiddenPriceLocator = By.id("sliderPrixPropriete");
By enterPriceLocator = By.id("PrixPropriete");
By plusLocator = By.id("PrixProprietePlus");

@Before
public void setUp() {
  driver = new FirefoxDriver();
  wait = new WebDriverWait(driver, 10);
}

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

public WebElement findElement(By locator) {
  return
    wait.until(
      ExpectedConditions.
        elementToBeClickable(locator));
}

public WebElement findHiddenElement(By locator) {
  return
    wait.until(
      ExpectedConditions.
        presenceOfElementLocated(locator));
}

@Test
public void moveSliderToEnd() {

  driver.get(url);

  WebElement priceSlider =
    findElement(priceSliderLocator);

  assertTrue(priceSlider.isDisplayed());

  Dimension sliderSize = priceSlider.getSize();
  int sliderWidth = sliderSize.getWidth();
  int xCoord = priceSlider.getLocation().getX();

  Actions builder = new Actions(driver);
  builder.moveToElement(priceSlider)
         .click()
         .dragAndDropBy(priceSlider,xCoord+sliderWidth, 0)
         .build()
         .perform();

  WebElement hiddenPrice =
     findHiddenElement(hiddenPriceLocator);
  int priceValue = Integer.parseInt(
  hiddenPrice.getAttribute("value"));
  assertEquals(priceValue, 1000000);

  priceSlider = findElement(priceSliderLocator);
  String sliderPercent = priceSlider.getAttribute("style");
  assertTrue(sliderPercent.contains("left: 100"));
}
}

Update Slider By Typing Price

The second test does the following:

  • open the site
  • type the price into the textbox

Use the Keys.chord() method to type a sequence of characters into the textbox.

The sequence includes the price value and the tab key.

The tab key is needed for updating the slider.

  • check that the slider updated correctly

The slider percentage is stored in the style attribute of the slider element.

@Test 
public void updateSliderByTypingPrice() { 

  driver.get(url); 

  WebElement enterPrice = 
        findElement(enterPriceLocator); 
  enterPrice.clear(); 
  enterPrice.sendKeys(Keys.chord("500000", Keys.TAB)); 

  WebElement priceSlider = 
        findElement(priceSliderLocator); 
  String sliderPercent = priceSlider.getAttribute("style"); 
  assertTrue(sliderPercent.contains("left: 50")); 
}

 

Move Slider by Clicking the Plus Button

The third script does the following:

  • open the site
  • find the slider element
  • check that the slider percentage is 0
  • check that the hidden price value is 0
  • find the plus element
  • click the plus element 3 times
  • check that the slider percentage is 75%
  • check that the value of the hidden element is 750000
@Test 
public void moveSliderByClickingPlus() { 

  driver.get(url); 
  
  WebElement priceSlider = 
      findElement(priceSliderLocator); 

  String percent = priceSlider.getAttribute("style"); 
  assertTrue(percent.contains("left: 0%")); 

  WebElement hiddenPrice = 
        findHiddenElement(hiddentPriceLocator); 
  int priceValue = Integer.parseInt( 
        hiddenPrice.getAttribute("value")); 
  assertEquals(priceValue, 0); 

  WebElement plusButton = 
        findElement(plusLocator); 
  plusButton.click(); 
  plusButton.click(); 
  plusButton.click(); 

  priceSlider = findElement(priceSliderLocator); 
  percent = sliderHandle.getAttribute("style"); 
  assertTrue(percent.contains("left: 75%")); 
  
  hiddenPrice = findHiddenElement(hiddentPriceLocator); 
  priceValue = Integer.parseInt(
         hiddenPrice.getAttribute("value")); 
  assertEquals(priceValue, 750000); 
}

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.

How to execute JavaScript code in Selenium WebDriver

javascript

 

WebDriver tests need to be executed in multiple browsers.

And sometimes, you get different results.

A button can be clicked correctly in Firefox but not in Chrome.

What do you do in this case?

Changing the locator may work.

Executing JavaScript may work as well.

Let’s investigate Javascript in this article.

What can you do with Javascript?

You can do with Javascript pretty much everything you can do through the WebDriver library:

  • click an element
  • type a keyword in a textbox
  • select an option from a listbox
  • get the value of an element
  • find an element

But Javascript allows you also to do other things such as modify web elements by changing their color, or font size.

Or even removing them from the  page.

How to … in Javascript

Lets start with a simple example:

public class TestClass {

WebDriver driver;
By searchTextBoxLocator = By.id("globalQuery");
By searchButtonLocator = By.xpath("//input[@class='search_button']");
By firstResultLocator = By.xpath("(//a[@testid='bib_link'])[1]");
By resultTitleLocator = By.id("item_bib_title");

@Before
public void setUp() {
  System.setProperty("webdriver.chrome.driver",
     "C:\\Selenium\\BrowserDrivers\\chromedriver.exe");

  driver = new ChromeDriver();
}

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

@Test
public void testWithSelenium()
{
   driver.get("http://www.vpl.ca");

   WebElement searchField;
   searchField = driver.findElement(searchTextBoxLocator);
   searchField.click();
   searchField.sendKeys("java");

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

   delay(5);

   WebElement searchResultLink;
   searchResultLink = driver.findElement(firstResultLocator);
   searchResultLink.click();

   delay(5);

   WebElement bookTitleElement;
   bookTitleElement = driver.findElement(resultTitleLocator);
   String bookTitleValue;
   bookTitleValue = bookTitleElement.getText();
   assertTrue(bookTitleElement.isDisplayed() == true);
   assertTrue(bookTitleValue.length() > 0);

}

 

The test script does the following:

  1. open the site
  2. execute a search
  3. click on the 1st result from the results page
  4. check if the book element is displayed in the details page
  5. check if the book value is not empty on the details page

 

Lets re-write it and use Javascript instead of WebDriver methods:

public class TestClass {

WebDriver driver;
JavascriptExecutor jsExecutor;

@Before
public void setUp() {
  System.setProperty("webdriver.chrome.driver",
     "C:\\Selenium\\BrowserDrivers\\chromedriver.exe");

  driver = new ChromeDriver();
  jsExecutor = (JavascriptExecutor) driver;
}

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

@Test
public void testWithJavaScript()  {

  driver.get("http://www.vpl.ca");

  //type a keyword in the search text box
  String typeKeywordJS = 
  "document.getElementById('globalQuery').value='java'";
  jsExecutor.executeScript(typeKeywordJS);

  //click the search button
  String clickSearchJS = 
  "document.querySelector(\"[class='search_button']\")" +
  ".click()";
  jsExecutor.executeScript(clickSearchJS);

  delay(5);


  //click the 1st result from the results page
  String clickResultJS = 
  "document.querySelector(\"a[testid='bib_link']\")" + 
  ".click()";
  jsExecutor.executeScript(clickResultJS);

  delay(5);


  //gets the book title value from the details page
  String getTitleJS = "function getTitle() {" +
  "var title=document.getElementById('item_bib_title')" +
  ".innerHTML;"+
  "return title; }; "+
  "return getTitle()";

  String bookTitleValue = 
  (String)jsExecutor.executeScript(getTitleJS);

  System.out.println(bookTitleValue.trim());
  assertTrue(bookTitleValue.length() > 0);


  //gets the book element from the details page
  String getTitleElementJS = "function getTitleElement(){"+
  "var title = document.getElementById('item_bib_title');"+
  "return title; };"+
  "return getTitleElement()";

  WebElement bookTitleElement = (WebElement)jsExecutor
       .executeScript(getTitleElementJS);

  assertTrue(bookTitleElement.isDisplayed() == true);
  System.out.println(bookTitleElement.getText());
  System.out.println(bookTitleElement.getTagName());

}

 

How easy was this?

Every time I execute a Javascript query, 2 things are needed

  • prepare the Javascript query in a String variable
  • execute it with a Javascript Executor

The Javascript queries that interact with a web element are very simple.

But if the Javascript query returns a value, the query should include a return statement:

String getTitleElementJS = "function getTitleElement(){"+
" var title = document.getElementById('item_bib_title');"+
" return title; }; "+
" return getTitleElement()";

When executing the query, its result should be casted to the type of expected result.

The previous query returns an element so the result of the executeScript() method is casted with (WebElement):

WebElement bookTitleElement = 
(WebElement)jsExecutor.executeScript(getTitleElementJS);

What else can we do with Javascript?

 

  • highlight elements:
String jsQuery = 
String.format("%s.style.backgroundColor='red'", locator);
jsExecutor.executeScript(jsQuery);

 

  • wait until the whole page is loaded:
private void waitUntilPageLoaded() {
  Boolean isLoaded = false;
  while (!isLoaded) {
    isLoaded = isPageLoaded();
    delay(1);
  }
}

private Boolean isPageLoaded() {
  String jsQuery = "function pageLoaded() "
  + "{var loadingStatus=(document.readyState=='complete');
  + "return loadingStatus;};"
  + "return pageLoaded()";

  return (Boolean)jsExecutor.executeScript(jsQuery);
}
  • zoom in the page:
String zoomInJS = "document.body.style.zoom='50%'";
jsExecutor.executeScript(zoomInJS);

 

  • scroll until an element is in view:
String scrollToElementJS = "element = document.getElementById("divId");
element.scrollIntoView(true);";
jsExecutor.executeScript(scrollToElementJS);

 

We can do many things with JavaScript if they cannot be done with the Selenium WebDriver framework.

Your automation project should have Javascript methods for common ways of interacting with web element:

  1. find element
  2. type text into textbox
  3. click element
  4. get the value of an element
  5. highlight an element

 

See below the initial Selenium code updated for Javascript methods:

String searchKeywordJSLocator = 
"document.getElementById('globalQuery')";

String searchButtonJSLocator = 
"document.querySelector(\"[class='search_button']\")";

String firstResultJSLocator = 
"document.querySelector(\"a[testid='bib_link']\")";

String resultTitleJSLocator = 
"document.getElementById('item_bib_title')";

@Test
public void testWithJavaScript()  {

  driver.get("http://www.vpl.ca");

  waitUntilPageLoaded();

  highlightElement(searchKeywordJSLocator);
  typeJS(searchKeywordJSLocator, "java");

  highlightElement(searchButtonJSLocator);
  clickJS(searchButtonJSLocator);

  waitUntilPageLoaded();

  highlightElement(firstResultJSLocator);
  clickJS(firstResultJSLocator);

  waitUntilPageLoaded();

  String bookTitleValue = getValueJS(resultTitleJSLocator);
  assertTrue(bookTitleValue.length() > 0);

  highlightElement(resultTitleJSLocator);

  WebElement bookTitleElement = 
  getElementJS(resultTitleJSLocator);

  assertTrue(bookTitleElement.isDisplayed() == true);

}

private void highlightElement(String locator) {
  String jsQuery = 
  String.format("%s.style.backgroundColor='red'", locator);

  jsExecutor.executeScript(jsQuery);
}

private void typeJS(String locator, String keyword) {
  String jsQuery = 
    String.format("%s.value='%s'", locator, keyword);

  jsExecutor.executeScript(jsQuery);
}

private void clickJS(String locator) {
  String jsQuery = String.format("%s.click()", locator);
  jsExecutor.executeScript(jsQuery);
}

private String getValueJS(String locator) {

  String jsQuery = 
      String.format("function getValue() " +
      "{var value=%s.innerHTML;return value;};" +
      "return getValue()", locator);

  return (String) jsExecutor
         .executeScript(jsQuery);

}

private WebElement getElementJS(String locator) {

  String jsQuery = 
    String.format("function getElement()" +
          "{var element = %s; return element; }; " +
          "return getElement()", locator);

  WebElement element = 
    (WebElement) jsExecutor.executeScript(jsQuery);

  return element;
}

private void delay(int seconds) {
   Thread.sleep(seconds * 1000);
}

private void waitUntilPageLoaded() {
  Boolean isLoaded = false;
  while (!isLoaded) {
    isLoaded = isPageLoaded();
    delay(1);
  }
}

private Boolean isPageLoaded() {
   String jsQuery = "function pageLoaded() "
                       + "{var loadingStatus = "
                       + (document.readyState == 'complete');"
                       + "return loadingStatus; }; "
                       + "return pageLoaded()";

    return (Boolean)jsExecutor
        .executeScript(jsQuery);
 }
}

 

Cannot click element? Execute JavaScript or use the Actions class

Have you ever seen this error in Chrome when trying to click on an element?

org.openqa.selenium.WebDriverException: 
Element is not clickable at point (411, 675)

Clicking the specific element works fine in Firefox but not at all in Chrome.

The code is very straightforward:

By locator = By.xpath("//a[attribute='value']");

WebElement element = driver.findElement(locator);

element.click();

So this does not work sometimes in Chrome.

What do you do?

 

Use an explicit wait

WebDriverWait wait = new WebDriverWait(driver, 25);

By locator = By.xpath("//a[attribute='value']");

WebElement element = wait.until(ExpectedConditions.
                     elementToBeClickable(locator));

element.click();

Unfortunately, this does not work either.

 

 Execute JavaScript

JavascriptExecutor jsExecutor;
jsExecutor = (JavascriptExecutor) driver;

String jsQuery;

jsQuery = "document.querySelector(\"[attribute='value']\").click()"; 

jsExecutor.executeScript(javaScriptCommand);

This code works sometimes only.

So, what do we do next?

 

Use the Actions class to chain methods

WebDriverWait wait = new WebDriverWait(driver, 25);

By locator = By.xpath("//a[attribute='value']");

WebElement element = wait.until(ExpectedConditions.
                                elementToBeClickable(locator));

new Actions(driver()).moveToElement(element)
                     .click()
                     .perform();