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();

8 Comments