Java tutorial
/* * Copyright 2014 Hotwire. All Rights Reserved. * * This software is the proprietary information of Hotwire. * Use is subject to license terms. */ package com.hotwire.selenium.mobile.results; import static org.fest.assertions.Assertions.assertThat; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.hotwire.selenium.mobile.MobileAbstractPage; import com.hotwire.testing.ZeroResultsTestException; import com.hotwire.util.webdriver.functions.AllExpectedConditions; import com.hotwire.util.webdriver.functions.InvisibilityOf; import com.hotwire.util.webdriver.functions.VisibilityOf; import com.hotwire.util.webdriver.po.PageObjectUtils; /** * This represents the page object for the mobile hotel results page * * @author prbhat */ public class MobileHotelResultsPage extends MobileAbstractPage { public static final AllExpectedConditions HOTEL_RESULTS_LOADED = new AllExpectedConditions( Arrays.<Function<WebDriver, ?>>asList(new InvisibilityOf(By.cssSelector(".loading img")), new VisibilityOf(By.id("results")), new VisibilityOf(By.name("hotelResult0")))); private static Logger LOGGER = LoggerFactory.getLogger(MobileHotelResultsPage.class.getSimpleName()); private static final String RESULTS_CONTAINER = "div[id='results']"; private static final String RESULTS_HREF_LIST = RESULTS_CONTAINER + " div.result"; @FindBy(css = "div#results .result") private WebElement result1; @FindBy(css = "div[id='hotel'] .hwRef") private WebElement reference; @FindBy(css = "div[id='hotel' div[id='footer-buttons'] a[id='footer-feedback']") private WebElement feedback; @FindBy(css = "div#results .result .price") private List<WebElement> hotelResults; @FindBy(xpath = "//*[@data-bdd='date']") private WebElement date; @FindBy(css = "span[id='sort-button'], div[id='sort-button']") private WebElement sortButton; @FindBy(css = "div[id='sort-bar']") private WebElement sortBar; @FindBy(css = "span[id='filter-button'], div[id='filter-button']") private WebElement filterButton; @FindBy(css = "div[id='hotel'] #submitFilter") private WebElement filterDoneButton; @FindBy(css = ".hoods .hood-names li input.hood") private WebElement hood1; @FindBy(xpath = "//*[@data-bdd='sort-price']") private WebElement sortByPrice; @FindBy(xpath = "//*[@data-bdd='sort-star-rating']") private WebElement sortByStarRating; @FindBy(xpath = "//*[data-bdd='sort-distance']") private WebElement sortByDistance; @FindBy(xpath = "//*[@data-bdd='sort-best-value']") private WebElement sortByBestValue; @FindBy(xpath = "//*[@data-bdd='currency-selection']") private WebElement currencySelection; @FindBy(css = "div#results .result span.rating, div#results .result div.tag img") private List<WebElement> resultRatings; @FindBy(css = "div#results .result span.rate") private List<WebElement> resultRates; @FindBy(xpath = "//*[@data-bdd='result-distance']") private List<WebElement> resultDistances; @FindBy(xpath = "//*[@data-bdd='bv']") private List<WebElement> bestValueResultContainers; @FindBy(xpath = "//*[@data-bdd='retailhero']") private WebElement retailHeroModule; @FindBy(xpath = "//*[@data-bdd='hotel-discount-banner']") private WebElement discountBanner; @FindBy(xpath = "//*[@data-bdd='filter-button']") private WebElement clickFliter; @FindBy(xpath = "//*[@class='hood']") private List<WebElement> neighborhoods; @FindBy(xpath = "//*[@data-bdd='filter-done-button']") private WebElement doneFliter; @FindBy(xpath = "//*[@data-bdd='change-search']") private WebElement changeSearch; @FindBy(css = RESULTS_HREF_LIST) private List<WebElement> resultsHrefList; @FindBy(css = "div[id='starButtons'] span") private List<WebElement> starRatingFilters; public MobileHotelResultsPage(WebDriver webdriver) { super(webdriver, "tile.hotel.results.*", HOTEL_RESULTS_LOADED, TIME_TO_WAIT, MAX_SEARCH_PAGE_WAIT_SECONDS); waitForTemplateLoad(this.date); } public void waitForTemplateLoad(WebElement elem) { new WebDriverWait(getWebDriver(), TIME_TO_WAIT) .until(PageObjectUtils.webElementVisibleTestFunction(elem, true)); } public void select(Integer resultNumberToSelect) { this.hotelResults.get(resultNumberToSelect).click(); new WebDriverWait(getWebDriver(), TIME_TO_WAIT) .until(PageObjectUtils.webElementVisibleTestFunction(By.cssSelector(".loading img"), false)); } public List<WebElement> getClickableHrefList() { return resultsHrefList; } /** * On the mobile hotel results page, click on the "See areas" button */ public void clickAreaMap() { WebElement areaMap = getWebDriver().findElement(By.xpath("//*[@data-bdd='area-map']")); areaMap.click(); } public void filterByStarRating(String rating) { filterButton.click(); for (WebElement element : starRatingFilters) { if (element.getAttribute("data-star").equals(rating)) { element.click(); break; } } filterDoneButton.click(); } /** * If there is more than one hood, filter by the first one */ public void filteredByHoodName(String hoodName) { filterButton.click(); List<WebElement> hoodsNames = getWebDriver().findElements(By.xpath(".//*[@class='hood-names']//li")); int i = 0; for (i = 0; i < hoodsNames.size(); i++) { if (hoodsNames.get(i).getText().equals(hoodName)) { break; } } hoodsNames.get(i).findElement(By.tagName("input")).click(); filterDoneButton.click(); } public void filterByFirstHood() { LOGGER.info("Open filter screen"); int resultsTotalBefore = getWebDriver().findElements(By.xpath("//*[contains(@data-bdd,'priceLockup-')]")) .size(); this.filterButton.click(); new WebDriverWait(getWebDriver(), TIME_TO_WAIT) .until(PageObjectUtils.webElementVisibleTestFunction(this.filterDoneButton, true)); List<WebElement> hoods = getWebDriver().findElements(By.xpath("//*[contains(@data-bdd,'hood-')]")); int hoodsTotal = hoods.size(); if (hoodsTotal > 0) { this.hood1.click(); } this.filterDoneButton.click(); new MobileHotelResultsPage(getWebDriver()); if (hoodsTotal > 1) { List<WebElement> filteredResults = getWebDriver() .findElements(By.xpath("//*[contains(@data-bdd,'priceLockup-')]")); boolean resultsAreFiltered = filteredResults.size() < resultsTotalBefore; if (!resultsAreFiltered) { throw new RuntimeException("Whoops, results were not filtered."); } else { LOGGER.info(resultsTotalBefore + " results were filtered to " + filteredResults.size()); } } else { LOGGER.info("Only one hood available, not enough to try filtering."); } LOGGER.info("Close filter screen"); } /** * You are on the results page only if you have atleast one hotel result */ public void assertResultOnPage() { new WebDriverWait(getWebDriver(), MAX_SEARCH_PAGE_WAIT_SECONDS) .until(PageObjectUtils.webElementVisibleTestFunction(this.result1, true)); assertThat(this.hotelResults.isEmpty()).as( "Expected to be on mobile hotel results page with atleast one result. Number of results is zero") .isFalse(); this.assertReferenceNumberExists(); } public void sortByCriteria(String sortCriteria) { String xp; if ("price".equalsIgnoreCase(sortCriteria)) { xp = "//*[@data-bdd='sort-price']"; } else if ("distance".equalsIgnoreCase(sortCriteria)) { xp = "//*[@data-bdd='sort-distance']"; } else if ("star rating".equalsIgnoreCase(sortCriteria)) { xp = "//*[@data-bdd='sort-star-rating']"; } else if ("best value".equalsIgnoreCase(sortCriteria)) { xp = "//*[@data-bdd='sort-best-value']"; } else { throw new RuntimeException("Unrecognized sort criteria."); } if (!this.sortBar.isDisplayed()) { // The sorting button is hidden. this.sortButton.click(); new MobileHotelResultsPage(getWebDriver()); } new WebDriverWait(getWebDriver(), TIME_TO_WAIT).until( PageObjectUtils.webElementVisibleTestFunction(getWebDriver().findElement(By.xpath(xp)), true)); // Now the actual button should be visible getWebDriver().findElement(By.xpath(xp)).click(); new MobileHotelResultsPage(getWebDriver()); // @TO DO, TECHDEBT: Piggy backing here for now until we have bandwidth to separate out into it's own step // definition filterByFirstHood(); new MobileHotelResultsPage(getWebDriver()); } /** * Assert that the result set is sorted by the criteria by looking at the URL */ public void assertSortSuccessful(String criteria) { new MobileHotelResultsPage(getWebDriver()); ArrayList<Double> elementValues = new ArrayList<Double>(); boolean sortAscending = false; if ("price".equalsIgnoreCase(criteria)) { sortAscending = (this.sortByPrice.getAttribute("class").contains("arrowUp")) ? true : false; for (WebElement element : this.resultRates) { elementValues.add(new Double(element.getText().replaceAll("[^0-9.]", ""))); } } else if ("distance".equalsIgnoreCase(criteria)) { // Distance always sorted in ascending order. sortAscending = true; for (WebElement element : this.resultDistances) { String text = element.getText(); elementValues.add(new Double(text.substring(text.indexOf("(") + 1, text.indexOf(" ")))); } } else if ("star rating".equalsIgnoreCase(criteria)) { sortAscending = (this.sortByStarRating.getAttribute("class").contains("arrowUp")) ? true : false; for (WebElement element : this.resultRatings) { String starRating = element.getAttribute("title").trim(); // Handle the case where there is no retail hero module on the results page if (starRating != null && !starRating.isEmpty()) { elementValues.add(new Double(starRating)); } } } else if ("best value".equalsIgnoreCase(criteria)) { // All results on the page are sorted in ascending order by the best value index exposed via the // data attribute 'data-bv-index' sortAscending = true; for (WebElement element : this.bestValueResultContainers) { // Add in the rate for best value result. elementValues.add(Double.valueOf(element.getAttribute("data-bv-index"))); } } else { throw new RuntimeException("Unrecognized sort criteria."); } if (elementValues.size() > 1) { assertThat(areValuesInListSorted(elementValues, sortAscending)) .as("Expected " + ((!sortAscending) ? "descending" : "ascending") + " ordered results to be true but got false instead") .isTrue(); } else if (elementValues.isEmpty()) { throw new ZeroResultsTestException("0 results returned."); } else { // 1 result list is always sorted. LOGGER.info("1 result returned."); } } /** * Verify the retail hero module is present and visible. Verify the path of the deep link url, and the presence of * certain query string parameters */ public void assertRetailHeroIsPresent() { // assert the module that is being targeted is displayed before proceeding assertThat(this.retailHeroModule.isDisplayed()) .as("Expected to see retail hero module present on results page").isTrue(); String uRL_PATH = "/hotel/deeplink-details.jsp"; StringBuilder errorString = new StringBuilder(); boolean wellFormed = false; // get the deep link/retail hero url from the retail hero module URI uri = URI.create(this.retailHeroModule.getAttribute("data-hero-url")); // create a list of all query string parameters List<NameValuePair> urlParameters = URLEncodedUtils.parse(uri, "UTF-8"); // check that the path is correct for the url wellFormed = hasValidRetailHeroPath(uRL_PATH, uri.getPath(), errorString); wellFormed = wellFormed && hasValidQueryStringValues(urlParameters, errorString); assertThat(wellFormed).as(errorString.toString()).isTrue(); } public void clickRetailHeroResult() { retailHeroModule.findElement(By.cssSelector(".hero-price")).click(); } /** * Compare two paths for equality * * @param expectedPath expected url path * @param actualPath actual path * @param errorString StringBuilder to append any errors to * @return true if both paths are non-null, non-empty, and equal, false otherwise */ private boolean hasValidRetailHeroPath(String expectedPath, String actualPath, StringBuilder errorString) { if (StringUtils.isNotEmpty(actualPath) && StringUtils.isNotEmpty(expectedPath)) { return expectedPath.equals(actualPath); } errorString.append("Expected path: " + expectedPath + " instead got: " + actualPath); return false; } /** * Compare expected query strings and values to actual query strings and values * * @param urlParameters list of query string parameters and values * @param errorString StringBuilder to append any errors to * @return true if all required parameters exist and have correct values, false otherwise */ private boolean hasValidQueryStringValues(List<NameValuePair> urlParameters, StringBuilder errorString) { String resultId = "resultId"; String inputId = "inputId"; String actionType = "actionType"; String expectedActionType = "303"; String expectedInputId = "hotel-index"; boolean wellFormed = true; for (NameValuePair param : urlParameters) { boolean parameterIsValid = false; if (resultId.equals(param.getName())) { parameterIsValid = StringUtils.isNotEmpty(param.getValue()); errorString.append(parameterIsValid ? "" : " | Expected " + resultId + "to not be empty instead got: " + param.getValue()); } else if (inputId.equals(param.getName())) { parameterIsValid = expectedInputId.equals(param.getValue()); errorString.append(parameterIsValid ? "" : " | Expected " + inputId + " to be: " + expectedInputId + " instead got: " + param.getValue()); } else if (actionType.equals(param.getName())) { parameterIsValid = expectedActionType.equals(param.getValue()); errorString.append(parameterIsValid ? "" : " | Expected " + actionType + " to be: " + expectedActionType + " instead got: " + param.getValue()); } else { // all other parameters will fall here ex: marketing sid/bid values. They do not need to be verified // and shouldn't affect the outcome of the test parameterIsValid = true; } // keep track of the overall status of the deep link wellFormed = wellFormed && parameterIsValid; } return wellFormed; } private boolean areValuesInListSorted(List<Double> values, boolean sortAscending) { for (int i = 1; i < values.size(); i++) { Double a = values.get(i); Double b = values.get(i - 1); LOGGER.info("Compare " + b + (sortAscending ? " <= " : " >= ") + a); if (sortAscending) { if (!(b.compareTo(a) == 0 || b.compareTo(a) == -1)) { return false; } } else { // Descending order. if (!(b.compareTo(a) == 0 || b.compareTo(a) == 1)) { return false; } } } return true; } private void checkKey(String val, String key) { if (!val.contains(key)) { throw new RuntimeException("Did not find: " + key); } } public void changeCurrency(String currencyCode) { Select selectMenu = new Select(this.currencySelection); selectMenu.selectByValue(currencyCode); if (currencyCode.equals("USD")) { currencyCode = "$"; } else if (currencyCode.equals("GBP")) { currencyCode = ""; } else if (currencyCode.equals("EUR")) { currencyCode = ""; } new MobileHotelResultsPage(getWebDriver()); new WebDriverWait(getWebDriver(), TIME_TO_WAIT) .until(PageObjectUtils.webElementVisibleTestFunction(this.result1, true)); // Assert that the first result's currency symbol or code has changed this.checkKey(this.result1.getText(), currencyCode); } private void assertReferenceNumberExists() { assertThat(this.reference != null).as("Expected to see a reference number but did not find any").isTrue(); assertThat(this.reference.isDisplayed()).as("Expected the reference number to be visible but is not") .isTrue(); } @SuppressWarnings("unused") private void assertFeedbackFormExists(WebDriver webdriver) { this.feedback.click(); webdriver.navigate().back(); } public boolean verifyDiscountBannerOnResultsPage() { return discountBanner.isDisplayed(); } public List<SearchSolution> getSearchSolutionList() { List<SearchSolution> searchSolutionList = new ArrayList<>(); List<WebElement> elements = getWebDriver().findElements(By.cssSelector("div.result")); for (int i = 0; i < elements.size(); i++) { SearchSolution searchSolution = new SearchSolution(); searchSolution.setNumber(i); String hotelName = elements.get(i).findElement(By.cssSelector("strong.ng-binding")).getText(); searchSolution.setHotelName(hotelName); searchSolutionList.add(searchSolution); } return searchSolutionList; } public String getCurrency() { return new Select(getWebDriver().findElement(By.id("currencies"))).getFirstSelectedOption().getText(); } public void clickFliter() { clickFliter.click(); } public void sortByNeighborhood() { clickFliter(); for (WebElement hood : neighborhoods) { if (hood.isSelected()) { hood.click(); break; } } doneFliter.click(); } public void changeHotelSearch() { changeSearch.click(); } public void selectFirstResultByType(String resultsType) { if (resultsType.equals("opaque")) { List<WebElement> results = getWebDriver().findElements(By.cssSelector(".cost.opaque")); results.get(0).click(); } else { List<WebElement> results = getWebDriver().findElements(By.cssSelector(".cost.retail")); results.get(0).click(); } } public boolean isSoldOutMessageDisplayed() { return getWebDriver().findElement(By.cssSelector("div.hero-soldOut")).isDisplayed(); } }