The test script from the previous post looks better than before:
@Test
public void canFilterByBooksTest() {
HomePage homePage = new HomePage(driver);
homePage.open();
Verify.verify(homePage.isDisplayed(), "home page is not displayed!");
homePage.searchBy(KEYWORD);
ResultsPage resultsPage = new ResultsPage(driver);
Verify.verify(resultsPage.isDisplayed(), "results page is not displayed!");
Verify.verify(resultsPage.getResultsFound().contains("1 to 10"),
"results found label does not include 1 to 10!");
assertFalse(resultsPage.getBookFilterState(), "checked book filter!");
resultsPage.checkBookFilter();
assertTrue(resultsPage.getBookFilterState(), "unchecked book filter!");
resultsPage.checkBookFilter();
assertFalse(resultsPage.getBookFilterState(), "checked book filter!");
}
There is one part though where the code is still misleading. The checkBookFilter() method is being used both for checking and unchecking the filter. It would be nice to have 2 different methods, one for each action.
Also, looking at the ResultsPage class, it is clear that there is a significant amount of code duplication in it. The methods that click the filters are identical. The methods that get the filter statuses are also identical.
public class ResultsPage {
private WebDriver driver;
private static final String RESULTS_PAGE_URL =
"https://vpl.bibliocommons.com";
private static final By RESULTS_FOUND_XPATH =
By.xpath("(//span[@data-key = 'pagination-text'])[1]");
private static final By BOOK_FILTER_XPATH =
By.xpath("//button[@aria-labelledby='label_FORMAT-BOOKS-BK']");
private static final By COMIC_BOOK_FILTER_XPATH =
By.xpath("//button[@aria-labelledby='label_FORMAT-BOOKS-COMIC_BK']");
private static final By E_BOOK_FILTER_XPATH =
By.xpath("//button[@aria-labelledby='label_FORMAT-BOOKS-EBOOK']");
public ResultsPage(WebDriver driver) {
this.driver = driver;
}
public String getUrl() {
return driver.getCurrentUrl();
}
public boolean isDisplayed() {
return getUrl().startsWith(RESULTS_PAGE_URL);
}
public String getResultsFound() {
WebElement label = driver.findElement(RESULTS_FOUND_XPATH);
return label.getText();
}
public boolean getBookFilterState() {
WebElement filter = driver.findElement(BOOK_FILTER_XPATH);
String ariaChecked = filter.getAttribute("aria-checked");
return Boolean.parseBoolean(ariaChecked);
}
public boolean getComicBookFilterState() {
WebElement filter = driver.findElement(COMIC_BOOK_FILTER_XPATH);
String ariaChecked = filter.getAttribute("aria-checked");
return Boolean.parseBoolean(ariaChecked);
}
public boolean getEBookFilterState() {
WebElement filter = driver.findElement(E_BOOK_FILTER_XPATH);
String ariaChecked = filter.getAttribute("aria-checked");
return Boolean.parseBoolean(ariaChecked);
}
public void checkBookFilter() {
WebElement filter = driver.findElement(BOOK_FILTER_XPATH);
filter.click();
}
public void checkComicBookFilter() {
WebElement filter = driver.findElement(COMIC_BOOK_FILTER_XPATH);
filter.click();
}
public void checkEBookFilter() {
WebElement filter = driver.findElement(E_BOOK_FILTER_XPATH);
filter.click();
}
}
The methods that check a filter have all duplicated code.
This is valid also for the methods that get the filter state.
The only difference is that they use different locators.
We can fix this code duplication by creating a private method that gets the filter name as parameter and returns the filter locator as a result:
private By getFilterXpath(String name) {
By xpath;
switch (name) {
case "book":
xpath = BOOK_FILTER_XPATH;
break;
case "comicbook":
xpath = COMIC_BOOK_FILTER_XPATH;
break;
case "ebook":
xpath = E_BOOK_FILTER_XPATH;
break;
default:
throw new RuntimeException("invalid filter name: " + name);
}
return xpath;
}
Then, we can create another private method that clicks a specific filter, with the filter name being provided as a parameter:
private void clickFilter(String name) {
By xpath = getFilterXpath(name);
WebElement filter = driver.findElement(xpath);
filter.click();
}
Similarly, we can create a private method that returns the state of a specific filter, with the filter name provided as a parameter:
private boolean getFilterState(String name) {
By xpath = getFilterXpath(name);
WebElement filter = driver.findElement(xpath);
String attribute = filter.getAttribute("aria-checked");
return Boolean.parseBoolean(attribute);
}
The methods for getting the status of the individual filters become very simple:
public boolean getBookFilterState() {
return getFilterState("book");
}
public boolean getComicBookFilterState() {
return getFilterState("comicbook");
}
public boolean getEBookFilterState() {
return getFilterState("ebook");
}
The methods that click the individual filters become simple as well:
public void checkBookFilter() {
checkFilter("book");
}
public void unCheckBookFilter() {
unCheckFilter("book");
}
public void checkComicBookFilter() {
checkFilter("comicbook");
}
public void unCheckComicBookFilter() {
unCheckFilter("comicbook");
}
public void checkEBookFilter() {
checkFilter("ebook");
}
public void unCheckEBookFilter() {
unCheckFilter("ebook");
}
private void checkFilter(String name) {
if (!getFilterState(name))
clickFilter(name);
}
private void unCheckFilter(String name) {
if (getFilterState(name))
clickFilter(name);
}
We have now a check and uncheck method for each filter. They do not depend on the clickFilter() method but on checkFilter() and unCheckFilter().
checkFilter() only clicks the filter if the filter status is unchecked.
unCheckFilter() only clicks the filter if the filter status is checked.
This is the full ResultsPage class:
public class ResultsPage {
private WebDriver driver;
private static final String RESULTS_PAGE_URL =
"https://vpl.bibliocommons.com";
private static final By RESULTS_FOUND_XPATH =
By.xpath("(//span[@data-key = 'pagination-text'])[1]");
private static final By BOOK_FILTER_XPATH =
By.xpath("//button[@aria-labelledby='label_FORMAT-BOOKS-BK']");
private static final By COMIC_BOOK_FILTER_XPATH =
By.xpath("//button[@aria-labelledby='label_FORMAT-BOOKS-COMIC_BK']");
private static final By E_BOOK_FILTER_XPATH =
By.xpath("//button[@aria-labelledby='label_FORMAT-BOOKS-EBOOK']");
public ResultsPage(WebDriver driver) {
this.driver = driver;
}
public String getUrl() {
return driver.getCurrentUrl();
}
public boolean isDisplayed() {
return getUrl().startsWith(RESULTS_PAGE_URL);
}
public String getResultsFound() {
WebElement label = driver.findElement(RESULTS_FOUND_XPATH);
return label.getText();
}
public boolean getBookFilterState() {
return getFilterState("book");
}
public void checkBookFilter() {
checkFilter("book");
}
public void unCheckBookFilter() {
unCheckFilter("book");
}
public boolean getComicBookFilterState() {
return getFilterState("comicbook");
}
public void checkComicBookFilter() {
checkFilter("comicbook");
}
public void unCheckComicBookFilter() {
unCheckFilter("comicbook");
}
public boolean getEBookFilterState() {
return getFilterState("ebook");
}
public void checkEBookFilter() {
checkFilter("ebook");
}
public void unCheckEBookFilter() {
unCheckFilter("ebook");
}
private void checkFilter(String name) {
if (!getFilterState(name))
clickFilter(name);
}
private void unCheckFilter(String name) {
if (getFilterState(name))
clickFilter(name);
}
private void clickFilter(String name) {
By xpath = getFilterXpath(name);
WebElement filter = driver.findElement(xpath);
filter.click();
}
private boolean getFilterState(String name) {
By xpath = getFilterXpath(name);
WebElement filter = driver.findElement(xpath);
String attribute = filter.getAttribute("aria-checked");
return Boolean.parseBoolean(attribute);
}
private By getFilterXpath(String name) {
By xpath;
switch (name) {
case "book":
xpath = BOOK_FILTER_XPATH;
break;
case "comicbook":
xpath = COMIC_BOOK_FILTER_XPATH;
break;
case "ebook":
xpath = E_BOOK_FILTER_XPATH;
break;
default:
throw new RuntimeException("invalid filter name: " + name);
}
return xpath;
}
}
The test script becomes more clear with the check() and unCheck() methods:
@Test
public void canFilterByBooksTest() {
HomePage homePage = new HomePage(driver);
homePage.open();
verify(homePage.isDisplayed(), "home page is not displayed!");
homePage.searchBy(KEYWORD);
ResultsPage resultsPage = new ResultsPage(driver);
verify(resultsPage.isDisplayed(), "results page is not displayed!");
verify(resultsPage.getResultsFound().contains("1 to 10"),
"results found label does not include 1 to 10!");
assertFalse(resultsPage.getBookFilterState(), "checked book filter!");
resultsPage.checkBookFilter();
assertTrue(resultsPage.getBookFilterState(), "unchecked book filter!");
resultsPage.unCheckBookFilter();
assertFalse(resultsPage.getBookFilterState(), "checked book filter!");
}
This was a lot of work for such a simple change in the test script.
But the biggest benefit is in the ResultsPage class:
we removed a lot of code duplication about clicking a filter
we removed a lot of code duplication about getting the state of a filter
we have check() and unCheck() methods
the check() method only works if the filter state is unchecked
the unCheck() method only works if the filter state is checked