/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.falcon.regression.ui.search; import org.apache.commons.lang.StringUtils; import org.apache.falcon.entity.v0.Entity; import org.apache.falcon.regression.Entities.FeedMerlin; import org.apache.falcon.regression.Entities.ProcessMerlin; import org.apache.falcon.regression.core.util.UIAssert; import org.apache.log4j.Logger; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBys; import org.openqa.selenium.support.PageFactory; import org.testng.Assert; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; /** Page object for the Search Page. */ public class SearchPage extends AbstractSearchPage { private static final String CLASS_OF_SELECTED_ROW = "rowSelected"; private static final Logger LOGGER = Logger.getLogger(SearchPage.class); public SearchPage(WebDriver driver) { super(driver); } @FindBys({ @FindBy(className = "mainUIView"), @FindBy(className = "searchBoxContainer") }) private WebElement searchBlock; @FindBys({ @FindBy(className = "mainUIView"), @FindBy(className = "dashboardBox") }) private WebElement resultBlock; @FindBys({ @FindBy(className = "mainUIView"), @FindBy(className = "dashboardBox"), @FindBy(tagName = "thead") }) private WebElement resultHeader; @FindBys({ @FindBy(className = "mainUIView"), @FindBy(className = "dashboardBox"), @FindBy(className = "buttonRow") }) private WebElement resultButtons; private List<WebElement> getSearchResultElements() { return resultBlock.findElements(By.className("entityRow")); } public List<SearchResult> getSearchResults() { List<SearchResult> searchResults = new ArrayList<>(); for (WebElement oneResultElement : getSearchResultElements()) { final List<WebElement> resultParts = oneResultElement.findElements(By.tagName("td")); final String entityName = resultParts.get(1).getText(); final SearchResult searchResult = SearchResult.create(entityName); final String[] allClasses = oneResultElement.getAttribute("class").split(" "); if (Arrays.asList(allClasses).contains(CLASS_OF_SELECTED_ROW)) { searchResult.withChecked(true); } final String tags = resultParts.get(2).getText(); searchResult.withTags(tags); final String clusterName = resultParts.get(3).getText(); searchResult.withClusterName(clusterName); final String type = resultParts.get(4).getText(); searchResult.withType(type); final String status = resultParts.get(5).getText(); searchResult.withStatus(status); searchResults.add(searchResult); } return searchResults; } public EntityPage openEntityPage(String entityName) { return click(doSearch(entityName).get(0)); } public EntityPage click(SearchResult result) { LOGGER.info("attempting to click: " + result + " on search page."); for (WebElement oneResultElement : getSearchResultElements()) { final List<WebElement> resultParts = oneResultElement.findElements(By.tagName("td")); final WebElement entityNameElement = resultParts.get(1); final String entityName = entityNameElement.getText(); if (entityName.equals(result.getEntityName())) { entityNameElement.findElement(By.tagName("button")).click(); return PageFactory.initElements(driver, EntityPage.class); } } return null; } @Override public void checkPage() { UIAssert.assertDisplayed(searchBlock, "Cluster box"); } private WebElement getSearchBox() { return searchBlock.findElement(By.className("input")); } public List<SearchResult> doSearch(String searchString) { clearSearch(); return appendAndSearch(searchString); } public List<SearchResult> appendAndSearch(String appendedPart) { for(String queryParam : appendedPart.split("\\s+")) { focusOnSearchBox(); getSearchBox().sendKeys(queryParam); getSearchBox().sendKeys(Keys.ENTER); } waitForAngularToFinish(); if (resultBlock.isDisplayed()) { return getSearchResults(); } else { return Collections.emptyList(); } } public SearchQuery getSearchQuery() { return new SearchQuery(searchBlock); } public void clearSearch() { focusOnSearchBox(); getSearchBox().clear(); SearchQuery query = getSearchQuery(); for (int i = 0; i < query.getElementsNumber(); i++) { removeLastParam(); } } public void removeLastParam() { focusOnSearchBox(); getSearchBox().sendKeys(Keys.BACK_SPACE); getSearchBox().sendKeys(Keys.BACK_SPACE); } private void focusOnSearchBox() { driver.findElement(By.className("tags")).click(); } public void checkNoResult() { UIAssert.assertNotDisplayed(resultBlock, "Search result block"); } public void selectRow(int row) { changeRowClickedStatus(row, true); } public void deselectRow(int row) { changeRowClickedStatus(row, false); } private void changeRowClickedStatus(int row, boolean checked) { WebElement checkboxBlock = resultBlock.findElements(By.className("entityRow")).get(row - 1); if (checked != checkboxBlock.getAttribute("class").contains(CLASS_OF_SELECTED_ROW)) { checkboxBlock.findElement(By.xpath("./td/input")).click(); } } public void clickSelectAll() { resultBlock.findElement(By.xpath(".//input[@ng-model='selectedAll']")).click(); } /** Class representing search query displayed in the search box. */ public static final class SearchQuery { private WebElement searchBlock; private String name; private String type; private int elementsNumber; private final List<String> tags = new ArrayList<>(); private static final Logger LOGGER = Logger.getLogger(SearchQuery.class); public SearchQuery(WebElement searchBlock) { this.searchBlock = searchBlock; updateElements(); } private SearchQuery updateElements() { name = null; tags.clear(); final WebElement queryGroup = searchBlock.findElement(By.className("tag-list")); final List<WebElement> queryParts = queryGroup.findElements(By.tagName("li")); elementsNumber = queryParts.size(); for (WebElement queryPart : queryParts) { final WebElement queryLabel = queryPart.findElement(By.tagName("strong")); final String queryText = queryPart.findElement(By.tagName("span")).getText(); switch (queryLabel.getText().trim()) { case "Name:": if (name != null) { LOGGER.warn(String.format("NAME block is already added: '%s' => '%s'", name, queryText)); } name = queryText; break; case "Tag:": tags.add(queryText); break; default: } } return this; } public String getName() { return name; } public List<String> getTags() { return tags; } public int getElementsNumber() { return elementsNumber; } /** * Delete element by index (1, 2, 3,..). * @param index of element in search query. * @return true if deletion was successful */ public boolean deleteByIndex(int index) { if (index > elementsNumber || index < 1) { LOGGER.warn("There is no element with index=" + index); return false; } int oldElementsNumber = elementsNumber; final WebElement queryGroup = searchBlock.findElement(By.className("tag-list")); final List<WebElement> queryParts = queryGroup.findElements(By.tagName("li")); queryParts.get(index - 1).findElement(By.className("remove-button")).click(); this.updateElements(); boolean result = oldElementsNumber == elementsNumber + 1; LOGGER.info(String.format( "Element with index=%d was%s deleted", index, result ? "" : "n't")); return result; } public boolean deleteLast() { return deleteByIndex(elementsNumber); } } public Set<Button> getButtons(boolean active) { List<WebElement> buttons = resultBlock.findElement(By.className("buttonsRow")) .findElements(By.className("btn")); Set<Button> result = EnumSet.noneOf(Button.class); for (WebElement button : buttons) { if ((button.getAttribute("disabled") == null) == active) { result.add(Button.valueOf(button.getText())); } } return result; } public void clickButton(Button button) { resultBlock.findElement(By.className("buttonsRow")) .findElements(By.className("btn")).get(button.ordinal()).click(); waitForAngularToFinish(); } /** * Buttons available for entities in result box. */ public enum Button { Schedule, Resume, Suspend, Edit, Copy, Delete, XML } /** Class representing search result displayed on the entity table page. */ public static final class SearchResult { private boolean isChecked = false; private String entityName; private String tags = ""; private String clusterName; private String type; private EntityStatus status; public static SearchResult create(String entityName) { return new SearchResult(entityName); } public SearchResult withChecked(boolean pIsChecked) { this.isChecked = pIsChecked; return this; } private SearchResult(String entityName) { this.entityName = entityName; } public SearchResult withTags(String pTags) { this.tags = pTags; return this; } public SearchResult withClusterName(String pClusterName) { this.clusterName = pClusterName; return this; } public SearchResult withType(String pType) { this.type = pType; return this; } public SearchResult withStatus(String pStatus) { this.status = EntityStatus.valueOf(pStatus); return this; } public boolean isChecked() { return isChecked; } public String getEntityName() { return entityName; } public String getTags() { return tags; } public String getClusterName() { Assert.assertFalse(clusterName.contains(","), "getClusterName() called" + " in multi-cluster setup: " + clusterName + ", maybe use getClusterNames()"); return clusterName; } public List<String> getClusterNames() { return Arrays.asList(clusterName.split(",")); } public String getType() { return type; } public EntityStatus getStatus() { return status; } @Override public String toString() { return "SearchResult{" + "isChecked=" + isChecked + ", entityName='" + entityName + '\'' + ", tags='" + tags + '\'' + ", clusterName='" + clusterName + '\'' + ", type='" + type + '\'' + ", status='" + status + '\'' + '}'; } public static void assertEqual(List<SearchResult> searchResults, List<Entity> expectedEntities, String errorMessage) { Assert.assertEquals(searchResults.size(), expectedEntities.size(), errorMessage + "(Length of lists don't match, searchResults: " + searchResults + " expectedEntities: " + expectedEntities + ")"); for (Entity entity : expectedEntities) { boolean found = false; for (SearchResult result : searchResults) { //entities are same if they have same name & type if (entity.getName().equals(result.entityName)) { //entity type in SearchResults has a different meaning //so, not comparing entity types //equality of cluster names List<String> entityClusters = null; switch (entity.getEntityType()) { case FEED: final FeedMerlin feed = (FeedMerlin) entity; entityClusters = feed.getClusterNames(); // tags equality check Assert.assertEquals(result.getTags(), StringUtils.trimToEmpty(feed.getTags()), errorMessage + "(tags mismatch: " + result.entityName + " & " + entity.toShortString() + ")"); break; case PROCESS: final ProcessMerlin process = (ProcessMerlin) entity; entityClusters = process.getClusterNames(); // tags equality check Assert.assertEquals(result.getTags(), StringUtils.trimToEmpty(process.getTags()), errorMessage + "(tags mismatch: " + result.entityName + " & " + entity.toShortString() + ")"); break; default: Assert.fail("Cluster entity is unexpected: " + entity); break; } Collections.sort(entityClusters); final List<String> actualClusters = result.getClusterNames(); Collections.sort(actualClusters); Assert.assertEquals(actualClusters, entityClusters, errorMessage + "(cluster names mismatch: " + result + " " + entity + ")"); found = true; } } Assert.assertTrue(found, "Entity: " + entity.toShortString() + " not found in: " + searchResults); } } } }