You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@falcon.apache.org by ra...@apache.org on 2015/05/18 20:37:18 UTC

[2/2] falcon git commit: FALCON-1202: Add tests for EntityPage contributed by Raghav Kumar Gautam

FALCON-1202: Add tests for EntityPage contributed by Raghav Kumar Gautam


Project: http://git-wip-us.apache.org/repos/asf/falcon/repo
Commit: http://git-wip-us.apache.org/repos/asf/falcon/commit/fc2179f6
Tree: http://git-wip-us.apache.org/repos/asf/falcon/tree/fc2179f6
Diff: http://git-wip-us.apache.org/repos/asf/falcon/diff/fc2179f6

Branch: refs/heads/master
Commit: fc2179f643a21e03b19005ad8aef2284c2aca5b1
Parents: ab93ac7
Author: Raghav Kumar Gautam <ra...@apache.org>
Authored: Mon May 18 11:35:54 2015 -0700
Committer: Raghav Kumar Gautam <ra...@apache.org>
Committed: Mon May 18 11:35:54 2015 -0700

----------------------------------------------------------------------
 falcon-regression/CHANGES.txt                   |   2 +
 .../regression/Entities/ClusterMerlin.java      |   5 +
 .../falcon/regression/Entities/FeedMerlin.java  |  19 +-
 .../regression/Entities/ProcessMerlin.java      |  42 ++
 .../helpers/entity/AbstractEntityHelper.java    |  19 +-
 .../falcon/regression/core/util/UiUtil.java     | 106 +++
 .../falcon/regression/core/util/Util.java       |  21 +-
 .../regression/testHelper/BaseUITestClass.java  |   3 +
 .../ui/search/AbstractSearchPage.java           |  26 +-
 .../falcon/regression/ui/search/EntityPage.java | 685 +++++++++++++++++++
 .../falcon/regression/ui/search/PageHeader.java |  52 +-
 .../falcon/regression/ui/search/SearchPage.java |  97 +++
 .../regression/searchUI/EntityPageTest.java     | 597 ++++++++++++++++
 13 files changed, 1639 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/CHANGES.txt
----------------------------------------------------------------------
diff --git a/falcon-regression/CHANGES.txt b/falcon-regression/CHANGES.txt
index 07c03e9..296e5b8 100644
--- a/falcon-regression/CHANGES.txt
+++ b/falcon-regression/CHANGES.txt
@@ -5,6 +5,8 @@ Trunk (Unreleased)
   INCOMPATIBLE CHANGES
 
   NEW FEATURES
+   FALCON-1202 Add tests for EntityPage (Raghav Kumar Gautam)
+
    FALCON-1210 Process Setup tests for testing header and general step default scenario (Raghav Kumar Gautam)
 
    FALCON-1201 Feed Setup tests for testing header and default scenario (Namit Maheshwari)

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ClusterMerlin.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ClusterMerlin.java b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ClusterMerlin.java
index 5acf1eb..d8305c8 100644
--- a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ClusterMerlin.java
+++ b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ClusterMerlin.java
@@ -104,4 +104,9 @@ public class ClusterMerlin extends Cluster {
         }
     }
 
+    @Override
+    public EntityType getEntityType() {
+        return EntityType.CLUSTER;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/FeedMerlin.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/FeedMerlin.java b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/FeedMerlin.java
index 9dba50a..91ba8a1 100644
--- a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/FeedMerlin.java
+++ b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/FeedMerlin.java
@@ -74,6 +74,14 @@ public class FeedMerlin extends Feed {
         return new FeedMerlin(feedString);
     }
 
+    public List<String> getClusterNames() {
+        List<String> names = new ArrayList<>();
+        for (Cluster cluster : getClusters().getClusters()) {
+            names.add(cluster.getName());
+        }
+        return names;
+    }
+
     /**
      * Sets custom feed property.
      * @param propertyName custom property name
@@ -99,11 +107,13 @@ public class FeedMerlin extends Feed {
     }
 
     /**
+     * Return feed path of the specified type.
      * @return feed data path
+     * @param locationType the type of the location
      */
-    public String getFeedPath() {
+    public String getFeedPath(LocationType locationType) {
         for (Location location : this.getLocations().getLocations()) {
-            if (location.getType() == LocationType.DATA) {
+            if (location.getType() == locationType) {
                 return location.getPath();
             }
         }
@@ -374,4 +384,9 @@ public class FeedMerlin extends Feed {
         this.setTable(catalogTable);
     }
 
+    @Override
+    public EntityType getEntityType() {
+        return EntityType.FEED;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ProcessMerlin.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ProcessMerlin.java b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ProcessMerlin.java
index 4b06abc..9e3d2d7 100644
--- a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ProcessMerlin.java
+++ b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/Entities/ProcessMerlin.java
@@ -42,6 +42,7 @@ import org.testng.Assert;
 
 import javax.xml.bind.JAXBException;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,6 +70,41 @@ public class ProcessMerlin extends Process {
         return this;
     }
 
+    public List<String> getClusterNames() {
+        List<String> names = new ArrayList<>();
+        for (Cluster cluster : getClusters().getClusters()) {
+            names.add(cluster.getName());
+        }
+        return names;
+    }
+
+    public Cluster getClusterByName(String name) {
+        for (Cluster cluster : getClusters().getClusters()) {
+            if (name.equals(cluster.getName())) {
+                return cluster;
+            }
+        }
+        return null;
+    }
+
+    public Input getInputByName(String name) {
+        for (Input input : getInputs().getInputs()) {
+            if (input.getName().equals(name)) {
+                return input;
+            }
+        }
+        return null;
+    }
+
+    public Output getOutputByName(String name) {
+        for (Output output : getOutputs().getOutputs()) {
+            if (output.getName().equals(name)) {
+                return output;
+            }
+        }
+        return null;
+    }
+
     /** Fluent builder wrapper for cluster fragment of process entity . */
     public static class ProcessClusterBuilder {
         private Cluster cluster = new Cluster();
@@ -413,6 +449,12 @@ public class ProcessMerlin extends Process {
     public String getFirstInputName() {
         return getInputs().getInputs().get(0).getName();
     }
+
+    @Override
+    public EntityType getEntityType() {
+        return EntityType.PROCESS;
+    }
+
 }
 
 

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/helpers/entity/AbstractEntityHelper.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/helpers/entity/AbstractEntityHelper.java b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/helpers/entity/AbstractEntityHelper.java
index 08e11a1..4dce3f3 100644
--- a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/helpers/entity/AbstractEntityHelper.java
+++ b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/helpers/entity/AbstractEntityHelper.java
@@ -295,7 +295,7 @@ public abstract class AbstractEntityHelper {
         throws IOException, URISyntaxException, AuthenticationException, InterruptedException {
         LOGGER.info("Validating " + getEntityType() + ": \n" + Util.prettyPrintXml(data));
         return Util.sendRequest(createUrl(this.hostname + URLS.VALIDATE_URL.getValue(),
-                getEntityType() + colo), "post", data, user);
+            getEntityType() + colo), "post", data, user);
     }
 
     public ServiceResponse schedule(String processData)
@@ -414,6 +414,23 @@ public abstract class AbstractEntityHelper {
             .createAndSendRequestProcessInstance(url, params, allColo, user);
     }
 
+    public InstancesResult getProcessInstanceLogs(String entityName, String params)
+        throws IOException, URISyntaxException, AuthenticationException, InterruptedException {
+        return getProcessInstanceLogs(entityName, params, null);
+    }
+
+    public InstancesResult getProcessInstanceLogs(String entityName, String params,
+                                                  String user)
+        throws IOException, URISyntaxException, AuthenticationException, InterruptedException {
+        String url = createUrl(this.hostname + URLS.INSTANCE_LOGS.getValue(), getEntityType(),
+            entityName);
+        if (StringUtils.isNotEmpty(params)) {
+            url += "?";
+        }
+        return (InstancesResult) InstanceUtil
+            .createAndSendRequestProcessInstance(url, params, allColo, user);
+    }
+
     public InstancesResult getProcessInstanceSuspend(
         String readEntityName, String params)
         throws IOException, URISyntaxException, AuthenticationException, InterruptedException {

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/UiUtil.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/UiUtil.java b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/UiUtil.java
new file mode 100644
index 0000000..6142332
--- /dev/null
+++ b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/UiUtil.java
@@ -0,0 +1,106 @@
+/**
+ * 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.core.util;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for UI related tasks.
+ */
+public final class UiUtil {
+    private UiUtil() {
+        throw new AssertionError("Instantiating utility class...");
+    }
+    private static final Logger LOGGER = Logger.getLogger(UiUtil.class);
+
+    /**
+     * Convert the element to string representation. Useful for debugging/development.
+     * @param element element to be converted
+     * @param limitDepth the depth to traverse. Typically <=3 is good value.
+     * @return
+     */
+    protected static String elementToString(WebElement element, Integer limitDepth) {
+        final StringBuilder retVal =
+            new StringBuilder("String representation of the element(first line is format):\n");
+        retVal.append("-> tagname")
+            .append("(id)")
+            .append("(classes)")
+            .append("[extra-info]")
+            .append("\t")
+            .append("text")
+            .append("\n");
+        retVal.append(elementToString("", element, limitDepth));
+        return retVal.toString();
+    }
+
+    private static StringBuilder elementToString(String prefix, WebElement element, Integer
+        limitDepth) {
+        if (limitDepth != null && limitDepth == 0) {
+            return new StringBuilder();
+        }
+        final Integer newDepth = limitDepth == null ? null : limitDepth - 1;
+        final StringBuilder elementStr = new StringBuilder(prefix);
+        List<String> extraInfo = new ArrayList<>();
+        if (StringUtils.isNotBlank(element.getAttribute("ng-repeat"))) {
+            extraInfo.add("array");
+        }
+        elementStr.append("-> ")
+            .append(element.getTagName())
+            .append("(").append(element.getAttribute("id")).append(")")
+            .append("(").append(element.getAttribute("class")).append(")")
+            .append(extraInfo)
+            .append("\t").append(StringEscapeUtils.escapeJava(element.getText()));
+        final String childPrefix = prefix + "\t";
+        final List<WebElement> childElements = element.findElements(By.xpath("./*"));
+        for (WebElement oneChildElement : childElements) {
+            StringBuilder childStr = elementToString(childPrefix, oneChildElement, newDepth);
+            if (childStr.length() > 0) {
+                elementStr.append("\n").append(childStr);
+            }
+        }
+        return elementStr;
+    }
+
+    /**
+     * Highlight the element in the UI. Useful for development/debugging.
+     * Copied from http://www.testingdiaries.com/highlight-element-using-selenium-webdriver/
+     * @param element the element to highlight
+     * @param driver the web driver in use
+     */
+    public static void elementHighlight(WebElement element, WebDriver driver) {
+        for (int i = 0; i < 2; i++) {
+            JavascriptExecutor js = (JavascriptExecutor) driver;
+            js.executeScript(
+                "arguments[0].setAttribute('style', arguments[1]);",
+                element, "color: red; border: 3px solid red;");
+            js.executeScript(
+                "arguments[0].setAttribute('style', arguments[1]);",
+                element, "");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/Util.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/Util.java b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/Util.java
index 0cce072..62e8ae1 100644
--- a/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/Util.java
+++ b/falcon-regression/merlin-core/src/main/java/org/apache/falcon/regression/core/util/Util.java
@@ -25,6 +25,7 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
 import com.jcraft.jsch.JSchException;
 import org.apache.falcon.entity.v0.EntityType;
+import org.apache.falcon.entity.v0.feed.LocationType;
 import org.apache.falcon.regression.Entities.ClusterMerlin;
 import org.apache.falcon.regression.Entities.FeedMerlin;
 import org.apache.falcon.regression.Entities.ProcessMerlin;
@@ -32,20 +33,18 @@ import org.apache.falcon.regression.core.helpers.ColoHelper;
 import org.apache.falcon.regression.core.helpers.entity.AbstractEntityHelper;
 import org.apache.falcon.regression.core.response.ServiceResponse;
 import org.apache.falcon.regression.core.supportClasses.JmsMessageConsumer;
+import org.apache.falcon.request.BaseRequest;
+import org.apache.falcon.request.RequestKeys;
 import org.apache.falcon.resource.APIResult;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
-import org.apache.http.HttpResponse;
-import org.apache.falcon.request.BaseRequest;
-import org.apache.falcon.request.RequestKeys;
 import org.apache.hadoop.security.authentication.client.AuthenticationException;
-
-
+import org.apache.http.HttpResponse;
+import org.apache.log4j.Logger;
 import org.joda.time.DateTime;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 import org.testng.Assert;
-import org.apache.log4j.Logger;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
@@ -54,10 +53,6 @@ import javax.jms.MapMessage;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Unmarshaller;
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
-
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Source;
 import javax.xml.transform.Transformer;
@@ -66,6 +61,9 @@ import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Enumeration;
@@ -190,7 +188,7 @@ public final class Util {
     public static List<String> getHadoopDataFromDir(FileSystem fs, String feed, String dir)
         throws IOException {
         List<String> finalResult = new ArrayList<>();
-        String feedPath = new FeedMerlin(feed).getFeedPath();
+        String feedPath = new FeedMerlin(feed).getFeedPath(LocationType.DATA);
         int depth = feedPath.split(dir)[1].split("/").length - 1;
         List<Path> results = HadoopUtil.getAllDirsRecursivelyHDFS(fs, new Path(dir), depth);
         for (Path result : results) {
@@ -393,6 +391,7 @@ public final class Util {
         INSTANCE_PARAMS("/api/instance/params"),
         INSTANCE_LIST("/api/instance/list"),
         INSTANCE_LISTING("/api/instance/listing"),
+        INSTANCE_LOGS("/api/instance/logs"),
         TOUCH_URL("/api/entities/touch");
 
         private final String url;

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/testHelper/BaseUITestClass.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/testHelper/BaseUITestClass.java b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/testHelper/BaseUITestClass.java
index caa9a38..205aad6 100644
--- a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/testHelper/BaseUITestClass.java
+++ b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/testHelper/BaseUITestClass.java
@@ -22,6 +22,8 @@ import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.firefox.FirefoxDriver;
 import org.openqa.selenium.firefox.FirefoxProfile;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Base class for UI test classes.
  */
@@ -39,6 +41,7 @@ public class BaseUITestClass extends BaseTestClass{
         profile.setPreference("network.negotiate-auth.trusted-uris", "http://, https://");
 
         driver = new FirefoxDriver(profile);
+        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
         driver.manage().window().maximize();
 
     }

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/AbstractSearchPage.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/AbstractSearchPage.java b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/AbstractSearchPage.java
index e72ce67..3550604 100644
--- a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/AbstractSearchPage.java
+++ b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/AbstractSearchPage.java
@@ -19,8 +19,11 @@
 package org.apache.falcon.regression.ui.search;
 
 import org.apache.falcon.regression.core.enumsAndConstants.MerlinConstants;
+import org.apache.falcon.regression.core.util.TimeUtil;
 import org.apache.falcon.regression.ui.pages.Page;
+import org.apache.log4j.Logger;
 import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
 import org.openqa.selenium.support.FindBy;
@@ -30,17 +33,19 @@ import org.openqa.selenium.support.PageFactory;
 public abstract class AbstractSearchPage extends Page {
 
     public static final String UI_URL = MerlinConstants.PRISM_URL;
-    public static final long PAGELOAD_TIMEOUT_THRESHOLD = 10;
+    private static final Logger LOGGER = Logger.getLogger(AbstractSearchPage.class);
+    public static final int PAGELOAD_TIMEOUT_THRESHOLD = 10;
 
     public AbstractSearchPage(WebDriver driver) {
         super(driver);
+        waitForAngularToFinish();
         pageHeader = PageFactory.initElements(driver, PageHeader.class);
     }
 
     private PageHeader pageHeader;
 
     @FindBy(className = "mainUIView")
-    private WebElement mainUI;
+    protected WebElement mainUI;
 
     public PageHeader getPageHeader() {
         return pageHeader;
@@ -56,10 +61,25 @@ public abstract class AbstractSearchPage extends Page {
     public abstract void checkPage();
 
     // Utility method to enter the data slowly on an element
-    public void sendKeysSlowly(WebElement webElement, String data){
+    public static void sendKeysSlowly(WebElement webElement, String data){
         for (String str : data.split("")) {
             webElement.sendKeys(str);
         }
 
     }
+
+    protected void waitForAngularToFinish() {
+        final String javaScript = "return (window.angular != null) && "
+            + "(angular.element(document).injector() != null) && "
+            + "(angular.element(document).injector().get('$http').pendingRequests.length === 0)";
+        boolean isLoaded = false;
+        for (int i = 0; i < PAGELOAD_TIMEOUT_THRESHOLD && !isLoaded; i++) {
+            final Object output = ((JavascriptExecutor) driver).executeScript(javaScript);
+            isLoaded = Boolean.valueOf(output.toString());
+            LOGGER.info(i+1 + ". waiting on angular to finish.");
+            TimeUtil.sleepSeconds(1);
+        }
+        LOGGER.info("angular is done continuing...");
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/EntityPage.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/EntityPage.java b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/EntityPage.java
new file mode 100644
index 0000000..0b7057e
--- /dev/null
+++ b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/EntityPage.java
@@ -0,0 +1,685 @@
+/**
+ * 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 com.google.common.collect.ImmutableMap;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.WordUtils;
+import org.apache.falcon.entity.v0.Frequency;
+import org.apache.falcon.entity.v0.feed.Cluster;
+import org.apache.falcon.entity.v0.feed.LocationType;
+import org.apache.falcon.entity.v0.feed.Property;
+import org.apache.falcon.entity.v0.process.Input;
+import org.apache.falcon.entity.v0.process.Output;
+import org.apache.falcon.entity.v0.process.Retry;
+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.falcon.resource.InstancesResult;
+import org.apache.log4j.Logger;
+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.FindBys;
+import org.openqa.selenium.support.ui.Select;
+import org.testng.asserts.SoftAssert;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+/**
+ * Class representation of Search UI entity page.
+ */
+public class EntityPage extends AbstractSearchPage {
+    private static final Logger LOGGER = Logger.getLogger(EntityPage.class);
+
+    /**
+     * Possible instance actions available on entity page.
+     */
+    public enum InstanceAction {
+        Log,
+        Suspend,
+        Resume,
+        Rerun,
+        Kill
+    }
+
+    public EntityPage(WebDriver driver) {
+        super(driver);
+    }
+
+    private WebElement getEntityTitle() {
+        final WebElement title = driver.findElement(By.id("entity-title"));
+        UIAssert.assertDisplayed(title, "entity title");
+        return title;
+    }
+
+    @FindBys({
+        @FindBy(className = "mainUIView"),
+        @FindBy(className = "dependencies-graph")
+    })
+    private WebElement dependencyBox;
+
+
+    @FindBys({
+        @FindBy(className = "mainUIView"),
+        @FindBy(xpath = "(.//*[contains(@class, 'detailsBox')])[2]")
+    })
+    private WebElement instanceListBox;
+
+    @FindBys({
+        @FindBy(className = "mainUIView"),
+        @FindBy(className = "summaryBox")
+    })
+    private WebElement propertiesBlock;
+
+    public String getEntityName() {
+        UIAssert.assertDisplayed(getEntityTitle(), "Entity title");
+        return getEntityTitle().getText().split(" ")[0];
+    }
+
+    @Override
+    public void checkPage() {
+        UIAssert.assertDisplayed(dependencyBox, "Dependency box");
+        UIAssert.assertDisplayed(instanceListBox, "Instance list box");
+        UIAssert.assertDisplayed(propertiesBlock, "Summary box");
+    }
+
+    public EntityPage refreshPage() {
+        final String entityName = getEntityName();
+        SearchPage searchPage = getPageHeader().gotoHome();
+        return searchPage.openEntityPage(entityName);
+    }
+
+    public void checkFeedProperties(FeedMerlin feed) {
+        openProperties();
+
+        final WebElement propertiesBox =
+            propertiesBlock.findElement(By.xpath("//div[@ui-view='feedSummary']"));
+        UIAssert.assertDisplayed(propertiesBox, "Properties box");
+
+        //all the parts of the entity properties
+        final List<WebElement> propertyParts = propertiesBox.findElements(By.xpath("./div"));
+        //First set of properties
+        final WebElement generalBox = propertyParts.get(0);
+        final List<WebElement> generalParts = generalBox.findElements(By.xpath("./div"));
+        SoftAssert softAssert = new SoftAssert();
+        //General
+        softAssert.assertEquals(generalParts.get(0).getText(), "General", "Unexpected heading");
+        final List<WebElement> nameAndDesc = generalParts.get(1).findElements(By.xpath("./div"));
+        softAssert.assertEquals(nameAndDesc.get(0).getText(), "Name: " + feed.getName(),
+            "Unexpected feed name in properties.");
+        softAssert.assertEquals(nameAndDesc.get(1).getText(), "Description: " + feed.getDescription(),
+            "Unexpected description in properties.");
+        //Tags
+        softAssert.assertEquals(generalParts.get(2).getText(), "Tags", "Unexpected heading");
+        softAssert.assertEquals(generalParts.get(3).getText(),
+            StringUtils.trimToEmpty(feed.getTags()),
+            "Unexpected tags");
+        //Groups
+        softAssert.assertEquals(generalParts.get(4).getText(), "Groups", "Unexpected heading");
+        softAssert.assertEquals(generalParts.get(5).getText(),
+            StringUtils.trimToEmpty(feed.getGroups()),
+            "Unexpected groups");
+        //Access Control list
+        softAssert.assertEquals(generalParts.get(6).getText(), "Access Control List",
+            "Unexpected heading");
+        final List<WebElement> ownerGrpPerm = generalParts.get(7).findElements(By.xpath("./div"));
+        softAssert.assertEquals(ownerGrpPerm.get(0).getText(),
+            "Owner: " + feed.getACL().getOwner(), "Unexpected owner");
+        softAssert.assertEquals(ownerGrpPerm.get(1).getText(),
+            "Group: " + feed.getACL().getGroup(), "Unexpected group");
+        softAssert.assertEquals(ownerGrpPerm.get(2).getText(),
+            "Permissions: " + feed.getACL().getPermission(), "Unexpected permission");
+        //Schema
+        softAssert.assertEquals(generalParts.get(8).getText(), "Schema",
+            "Unexpected heading for general properties");
+        final List<WebElement> locAndProvider = generalParts.get(9).findElements(By.xpath("./div"));
+        softAssert.assertEquals(locAndProvider.get(0).getText(),
+            "Location: " + feed.getSchema().getLocation(), "Unexpected schema locations");
+        softAssert.assertEquals(locAndProvider.get(1).getText(),
+            "Provider: " + feed.getSchema().getProvider(), "Unexpected schema provider");
+        //Properties
+        softAssert.assertEquals(generalParts.get(10).getText(), "Properties",
+            "Unexpected heading for general properties");
+        final List<WebElement> freqLateAvail = generalParts.get(11).findElements(By.xpath("./div"));
+        final Frequency feedFrequency = feed.getFrequency();
+        softAssert.assertEquals(freqLateAvail.get(0).getText(),
+            String.format("Frequency: Every %s %s",
+                feedFrequency.getFrequency(), feedFrequency.getTimeUnit()),
+            "Unexpected frequency");
+        final Frequency feedLateCutoff = feed.getLateArrival().getCutOff();
+        softAssert.assertEquals(freqLateAvail.get(1).getText(),
+            String.format("Late Arrival: Up to %s %s",
+                feedLateCutoff.getFrequency(), feedLateCutoff.getTimeUnit()),
+            "Unexpected late arrival");
+        softAssert.assertEquals(freqLateAvail.get(2).getText(),
+            String.format("Availability Flag:%s",
+                StringUtils.trimToEmpty(feed.getAvailabilityFlag())),
+            "Unexpected availability flag");
+        final List<WebElement> propertyElements =
+            generalParts.get(12).findElements(By.xpath("./div"));
+        List<String> displayedPropStr = new ArrayList<>();
+        for (WebElement webElement : propertyElements) {
+            displayedPropStr.add(webElement.getText());
+        }
+        Collections.sort(displayedPropStr);
+        final List<String> expectedPropStr = getFeedPropString(feed);
+        softAssert.assertEquals(displayedPropStr, expectedPropStr,
+            "Feed properties & displayed properties don't match. Expected: " + expectedPropStr
+                + " Actual: " + displayedPropStr);
+        //Storage type
+        softAssert.assertEquals(generalParts.get(13).getText(), "Default Storage Type:",
+            "Unexpected label for storage type.");
+        if (feed.getLocations() != null
+            && feed.getLocations().getLocations() != null
+            && feed.getLocations().getLocations().size() > 0) {
+            softAssert.assertEquals(generalParts.get(13).getText(), "File System",
+                "Unexpected storage type for feed.");
+        } else {
+            softAssert.fail("Need to add handler for other feed types.");
+        }
+        //Feed locations - Data followed by Stats followed by Meta
+        softAssert.assertEquals(generalParts.get(14).getText(), "Default Location:",
+            "Unexpected label for default location.");
+        softAssert.assertEquals(generalParts.get(15).getText(),
+            "Data\n" + feed.getFeedPath(LocationType.DATA),
+            "Unexpected label for feed data label");
+        softAssert.assertEquals(generalParts.get(16).getText(),
+            "Stats\n" + feed.getFeedPath(LocationType.STATS),
+            "Unexpected label for feed stats label");
+        softAssert.assertEquals(generalParts.get(17).getText(),
+            "Meta\n" + feed.getFeedPath(LocationType.META),
+            "Unexpected label for feed mata label");
+
+        //Second set of properties details with Source Cluster Properties
+        final WebElement clustersBox = propertyParts.get(1);
+        final List<WebElement> displayedClusters = clustersBox.findElements(By.xpath("./div"));
+        final List<Cluster> feedClusters = feed.getClusters().getClusters();
+        //test needs to be fixed when we have support for more than one feed cluster
+        softAssert.assertEquals(feedClusters.size(), 1,
+            "Current UI has support for only one feed cluster.");
+        checkFeedCluster(displayedClusters.get(0), feedClusters.get(0), softAssert);
+        softAssert.assertAll();
+    }
+
+    private void openProperties() {
+        final WebElement heading = propertiesBlock.findElement(By.tagName("h4"));
+        assertEquals(heading.getText(), "Properties",
+            "Unexpected heading of properties box.");
+        final WebElement upButton = propertiesBlock.findElement(By.className("pointer"));
+        upButton.click();
+    }
+
+    private void checkFeedCluster(WebElement cluster, Cluster feedCluster, SoftAssert softAssert) {
+        final List<WebElement> clusterElements = cluster.findElements(By.xpath("./div"));
+        final String vClusterName = clusterElements.get(1).getText();
+        softAssert.assertNotNull(feedCluster,
+            "Unexpected feed cluster is displayed: " + vClusterName);
+        final String clusterType = clusterElements.get(0).getText();
+        softAssert.assertEquals(clusterType,
+            WordUtils.capitalize(feedCluster.getType().toString().toLowerCase() + " Cluster"),
+            "Unexpected cluster type for cluster: " + vClusterName);
+        softAssert.assertEquals(clusterElements.get(2).getText(),
+            "Start: " + feedCluster.getValidity().getStart()
+                + "\nEnd: " + feedCluster.getValidity().getEnd(),
+            "Unexpected validity of the cluster: " + vClusterName);
+        softAssert.assertEquals(clusterElements.get(3).getText(), "Timezone: UTC",
+            "Unexpected timezone for validity of the cluster: " + vClusterName);
+        softAssert.assertEquals(clusterElements.get(4).getText(),
+            "Retention: Archive in " + feedCluster.getRetention().getLimit().getFrequency()
+                + " " + feedCluster.getRetention().getLimit().getTimeUnit(),
+            "Unexpected retention associated with cluster: " + vClusterName);
+    }
+
+    private List<String> getFeedPropString(FeedMerlin feed) {
+        List<String> retVals = new ArrayList<>();
+        for (Property property : feed.getProperties().getProperties()) {
+            retVals.add(property.getName() + ": " + property.getValue());
+        }
+        Collections.sort(retVals);
+        return retVals;
+    }
+
+    public void checkProcessProperties(ProcessMerlin process) {
+        openProperties();
+
+        final WebElement propertiesBox =
+            propertiesBlock.findElement(By.xpath("//div[@ui-view='processSummary']"));
+        UIAssert.assertDisplayed(propertiesBox, "Properties box");
+        final List<WebElement> propertiesParts = propertiesBox.findElements(By.xpath("./div"));
+        final WebElement generalPropBlock = propertiesParts.get(0);
+        final WebElement clusterPropBlock = propertiesParts.get(1);
+        final WebElement inputPropBlock = propertiesParts.get(2);
+        final WebElement outputPropBlock = propertiesParts.get(3);
+
+        //checking general properties
+        final List<WebElement> generalPropParts =
+            generalPropBlock.findElement(By.xpath("./*")).findElements(By.xpath("./*"));
+        SoftAssert softAssert = new SoftAssert();
+        softAssert.assertEquals(generalPropParts.get(0).getText(), "Process",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(1).getText(), "Name",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(2).getText(), process.getName(),
+            "Unexpected process name in general properties.");
+        softAssert.assertEquals(generalPropParts.get(3).getText(), "Tags",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(4).getText(),
+            StringUtils.defaultIfBlank(process.getTags(), "No tags selected"),
+            "Unexpected tags in general properties.");
+        softAssert.assertEquals(generalPropParts.get(5).getText(), "Workflow",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(6).getText(), "Name\nEngine\nVersion",
+            "Unexpected workflow properties in general properties.");
+        softAssert.assertEquals(generalPropParts.get(7).getText(),
+            String.format("%s%n%s%n%s",
+                StringUtils.defaultIfBlank(process.getWorkflow().getName(), ""),
+                process.getWorkflow().getEngine(), process.getWorkflow().getVersion()),
+            "Unexpected workflow properties in general properties.");
+        softAssert.assertEquals(generalPropParts.get(7).getText(), "Path",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(8).getText(), process.getWorkflow().getPath(),
+            "Unexpected workflow path in general properties.");
+        softAssert.assertEquals(generalPropParts.get(9).getText(), "Timing",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(10).getText(), "Timezone",
+            "Unexpected label in general properties.");
+        softAssert.assertEquals(generalPropParts.get(12).getText(),
+            String.format("Frequency%nEvery %s %s%n", process.getFrequency().getFrequency(),
+                process.getFrequency().getTimeUnit())
+                + "Max. parallel instances\n" + process.getParallel()
+                + "\nOrder\n" + process.getOrder().toString(),
+            "Unexpected frequency/parallel/order info in general properties.");
+        softAssert.assertEquals(generalPropParts.get(13).getText(), "Retry",
+            "Unexpected label in general properties.");
+        final Retry processRetry = process.getRetry();
+        softAssert.assertEquals(generalPropParts.get(14).getText(),
+            "Policy\n" + processRetry.getPolicy().toString().toLowerCase()
+                + "\nAttempts\n" + processRetry.getAttempts()
+                + "\nDelay\nUp to " + processRetry.getDelay().getFrequency()
+                + " " + processRetry.getDelay().getTimeUnit(),
+            "Unexpected policy/attempt/delay in general properties.");
+
+        //checking cluster properties
+        final List<WebElement> allClusterProps =
+            clusterPropBlock.findElements(By.xpath("./div/div/div"));
+        final WebElement clustersHeading = clusterPropBlock.findElement(By.xpath(".//h5"));
+        softAssert.assertEquals(clustersHeading.getText(), "Clusters",
+            "Unexpected label in clusters heading");
+        for (WebElement oneClusterProp : allClusterProps) {
+            final List<WebElement> clusterPropParts = oneClusterProp.findElements(By.xpath("./*"));
+            softAssert.assertEquals(clusterPropParts.get(0).getText(), "Name",
+                "Unexpected label in clusters properties");
+            final String clusterName = clusterPropParts.get(1).getText();
+            final org.apache.falcon.entity.v0.process.Cluster processCluster =
+                process.getClusterByName(clusterName);
+            softAssert.assertNotNull(processCluster,
+                "cluster with name " + clusterName + " was not present in process.");
+            softAssert.assertEquals(clusterName, processCluster.getName(),
+                "Unexpected cluster name in clusters properties");
+            softAssert.assertEquals(clusterPropParts.get(2).getText(), "Validity",
+                "Unexpected label in clusters properties");
+            softAssert.assertEquals(clusterPropParts.get(3).getText(),
+                "Start\n" + processCluster.getValidity().getStart()
+                + "\nEnd\n" + processCluster.getValidity().getEnd(),
+                "Unexpected start/end time in clusters properties");
+        }
+        //checking  inputs properties
+        final WebElement inputHeading = inputPropBlock.findElement(By.xpath(".//h5"));
+        softAssert.assertEquals(inputHeading.getText(), "Inputs",
+            "Unexpected heading for input properties.");
+        final List<WebElement> allInputsProps =
+            inputPropBlock.findElements(By.xpath("./div/div/*"));
+        for (WebElement oneInputProps : allInputsProps) {
+            final List<WebElement> inputPropParts = oneInputProps.findElements(By.xpath("./*"));
+            softAssert.assertEquals(inputPropParts.get(0).getText(), "Name",
+                "Unexpected label in input properties");
+            final String inputName = inputPropParts.get(1).getText();
+            final Input processInput = process.getInputByName(inputName);
+            softAssert.assertEquals(inputName, processInput.getName(),
+                "Unexpected input name in input properties");
+            softAssert.assertEquals(inputPropParts.get(2).getText(), "Feed",
+                "Unexpected label in input properties");
+            softAssert.assertEquals(inputPropParts.get(3).getText(), processInput.getFeed(),
+                "Unexpected label in input properties");
+            softAssert.assertEquals(inputPropParts.get(4).getText(), "Instance",
+                "Unexpected label in input properties");
+            softAssert.assertEquals(inputPropParts.get(5).getText(),
+                "Start\n" + processInput.getStart() + "\nEnd\n" + processInput.getEnd(),
+                "Unexpected start/end in input properties");
+        }
+        final WebElement outputHeading = outputPropBlock.findElement(By.tagName("h5"));
+        softAssert.assertEquals(outputHeading.getText(), "Outputs",
+            "Unexpected label for output properties.");
+        final List<WebElement> allOutputsProps =
+            outputPropBlock.findElements(By.xpath("./div/div/*"));
+        for (WebElement oneOutputProps : allOutputsProps) {
+            final List<WebElement> outputPropParts = oneOutputProps.findElements(By.xpath("./*"));
+            softAssert.assertEquals(outputPropParts.get(0).getText(), "Name",
+                "Unexpected label in output properties");
+            final String outputName = outputPropParts.get(1).getText();
+            final Output processOutput = process.getOutputByName(outputName);
+            softAssert.assertEquals(outputName, processOutput.getName(),
+                "Unexpected output name in output properties");
+            softAssert.assertEquals(outputPropParts.get(2).getText(), "Feed",
+                "Unexpected label in output properties");
+            softAssert.assertEquals(outputPropParts.get(3).getText(), processOutput.getFeed(),
+                "Unexpected feed name in output properties");
+            softAssert.assertEquals(outputPropParts.get(4).getText(), "Instance",
+                "Unexpected label in output properties");
+            softAssert.assertEquals(outputPropParts.get(5).getText(), processOutput.getInstance(),
+                "Unexpected instance in output properties");
+            softAssert.assertAll();
+        }
+    }
+
+    public InstanceSummary getInstanceSummary() {
+        return new InstanceSummary(this);
+    }
+
+    /**
+     * Class representing all the displayed instance.
+     */
+    public static class InstanceSummary {
+        private final WebElement instanceListBox;
+        private final WebElement summaryTableHeading;
+
+        public InstanceSummary(EntityPage entityPage) {
+            instanceListBox = entityPage.instanceListBox;
+            UIAssert.assertDisplayed(instanceListBox, "instance list box");
+            assertEquals(instanceListBox.findElement(By.tagName("h4")).getText(),
+                "Instances",
+                "Unexpected heading in instances box.");
+
+            summaryTableHeading = instanceListBox.findElement(By.xpath(".//thead/tr"));
+        }
+
+        private List<WebElement> getTableRows() {
+            return instanceListBox.findElements(By.xpath(".//tbody/tr"));
+        }
+
+        public void performActionOnSelectedInstances(InstanceAction instanceAction) {
+            final List<WebElement> summaryTableBodyParts = getTableRows();
+            final WebElement actionRibbon = summaryTableBodyParts.get(0);
+            for (WebElement element : actionRibbon.findElements(By.xpath("./td/div"))) {
+                if (InstanceAction.valueOf(element.getText()) == instanceAction) {
+                    element.click();
+                    return;
+                }
+            }
+        }
+
+        /**
+         * Get instance summary starting for all the pages.
+         * @return instance summary
+         */
+        public List<OneInstanceSummary> getSummary() {
+            List<OneInstanceSummary> summary = new ArrayList<>();
+            final List<WebElement> tableBody = getTableRows();
+            //last line has page number
+            final WebElement pageNumberRow = tableBody.remove(tableBody.size() - 1);
+            final List<WebElement> pages = pageNumberRow.findElement(By.className("pagination"))
+                .findElements(By.className("ng-scope"));
+            final int numberOfPages = pages.size();
+            for (int pageNumber = 1; pageNumber <= numberOfPages; ++pageNumber) {
+                //We want to use new web elements to avoid stale element issues
+                final List<WebElement> newTableBody = getTableRows();
+                //last line has page number
+                final WebElement newPageNumberRow = newTableBody.remove(newTableBody.size() - 1);
+                final List<WebElement> newPages =
+                    newPageNumberRow.findElement(By.className("pagination"))
+                        .findElements(By.className("ng-scope"));
+                newPages.get(pageNumber-1).findElement(By.tagName("a")).click();
+                summary.addAll(getSummaryInner());
+            }
+            return summary;
+        }
+
+        /**
+         * Get instance summary starting for the current page.
+         * @return instance summary
+         */
+        private List<OneInstanceSummary> getSummaryInner() {
+            List<OneInstanceSummary> summary = new ArrayList<>();
+            final List<WebElement> tableBody = getTableRows();
+            //first line in body has buttons
+            tableBody.remove(0);
+            //last line has page number
+            tableBody.remove(tableBody.size() - 1);
+            //second last line is horizontal line
+            tableBody.remove(tableBody.size() - 1);
+            if (tableBody.size() == 1
+                && tableBody.get(0).getText().equals("There are no results")) {
+                return summary;
+            }
+            for (WebElement oneSummaryRow : tableBody) {
+                summary.add(new OneInstanceSummary(oneSummaryRow));
+            }
+            return summary;
+        }
+
+        public void check() {
+            final List<WebElement> summaryHeadParts = getSummaryHeadParts();
+            getSelectAllCheckBox(summaryHeadParts);
+            final WebElement instanceHeadLabel = summaryHeadParts.get(1);
+            assertEquals(instanceHeadLabel.getText(), "Instance",
+                "Unexpected label in instance summary heading");
+            getSummaryStartedButton();
+            getSummaryEndedButton();
+            getStatusDropDown();
+        }
+
+        public void setInstanceSummaryStartTime(String timeStr) {
+            final WebElement startTimeButton = getSummaryStartedButton();
+            startTimeButton.clear();
+            sendKeysSlowly(startTimeButton, timeStr);
+        }
+
+        public void setInstanceSummaryEndTime(String timeStr) {
+            final WebElement endTimeButton = getSummaryEndedButton();
+            endTimeButton.clear();
+            sendKeysSlowly(endTimeButton, timeStr);
+        }
+
+        public void selectInstanceSummaryStatus(String labelText) {
+            getStatusDropDown().selectByVisibleText(labelText);
+        }
+
+        public static OneInstanceSummary getOneSummary(final List<OneInstanceSummary> summaries,
+                                                       final String nominalTime) {
+            for (OneInstanceSummary oneSummary : summaries) {
+                if (oneSummary.getNominalTime().equals(nominalTime)) {
+                    return oneSummary;
+                }
+            }
+            return null;
+        }
+
+        public void checkSummary(InstancesResult.Instance[] apiSummary) {
+            final List<OneInstanceSummary> summary = getSummary();
+            assertEquals(apiSummary.length, summary.size(),
+                String.format("Length of the displayed instance summary is not same: %s %s",
+                    Arrays.toString(apiSummary), summary));
+            for (InstancesResult.Instance oneApiSummary : apiSummary) {
+                final OneInstanceSummary oneSummary =
+                    getOneSummary(summary, oneApiSummary.instance);
+                assertEquals(oneApiSummary.instance, oneSummary.getNominalTime(),
+                    "Nominal time of instance summary doesn't match.");
+                final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm");
+                final Date apiStartTime = oneApiSummary.getStartTime();
+                if (apiStartTime == null) {
+                    assertTrue(StringUtils.isEmpty(oneSummary.getStartTime()),
+                        "Displayed start time : " + oneSummary + " is not "
+                            + "consistent with start time of api which is null");
+                } else {
+                    assertEquals(oneSummary.getStartTime(), dateFormat.format(apiStartTime),
+                        "Displayed start time : " + oneSummary + " is not "
+                            + "consistent with start time of api: " + apiStartTime);
+                }
+                final Date apiEndTime = oneApiSummary.getEndTime();
+                if (apiEndTime == null) {
+                    assertTrue(StringUtils.isEmpty(oneSummary.getEndTime()),
+                        "Displayed end time : " + oneSummary + " is not "
+                            + "consistent end start time of api which is null");
+                } else {
+                    assertEquals(oneSummary.getEndTime(), dateFormat.format(apiEndTime),
+                        "Displayed end time : " + oneSummary + " is not "
+                            + "consistent with end time of api: " + apiEndTime);
+                }
+                assertEquals(oneApiSummary.status.toString(), oneSummary.getStatus(),
+                    "Status of instance summary doesn't match.");
+            }
+        }
+
+        public WebElement getSummaryStartedButton() {
+            final WebElement startedBox = getSummaryHeadParts().get(2);
+            assertEquals(startedBox.getText(), "Started ",
+                "Unexpected label in instance summary heading");
+            return startedBox.findElement(By.tagName("input"));
+        }
+
+        public WebElement getSummaryEndedButton() {
+            final WebElement endedBox = getSummaryHeadParts().get(3);
+            assertEquals(endedBox.getText(), "Ended ",
+                "Unexpected label in instance summary heading");
+            return endedBox.findElement(By.tagName("input"));
+        }
+
+        public Select getStatusDropDown() {
+            final WebElement statusBox = getSummaryHeadParts().get(4);
+            assertEquals(statusBox.getText(),
+                "Status \nALL\nRUNNING\nSUCCEEDED\nSUSPENDED\nKILLED",
+                "Unexpected label in instance summary heading");
+            return new Select(statusBox.findElement(By.tagName("select")));
+        }
+
+        public List<WebElement> getSummaryHeadParts() {
+            return summaryTableHeading.findElements(By.xpath("./th/div"));
+        }
+
+        public WebElement getSelectAllCheckBox(List<WebElement> summaryHeadParts) {
+            return summaryHeadParts.get(0).findElement(By.tagName("input"));
+        }
+    }
+
+    /**
+     * Class representing summary of one instance.
+     */
+    public static final class OneInstanceSummary {
+        private final WebElement oneInstanceSummary;
+        private final String startTime;
+        private final String endTime;
+        private final String status;
+        private final String nominalTime;
+
+        private final Map<Object, Object> statusColorMap = ImmutableMap.builder()
+            .put("WAITING", "rgba(51, 51, 51, 1)")
+            .put("RUNNING", "")
+            .put("KILLED", "")
+            .put("SUCCEEDED", "")
+            .put("SUSPENDED", "")
+            .put("FAILED", "").build();
+        private boolean isCheckBoxTicked;
+
+        private OneInstanceSummary(WebElement oneInstanceSummary) {
+            this.oneInstanceSummary = oneInstanceSummary;
+            nominalTime = getNominalTimeButton().getText();
+            startTime = getSummaryCols().get(2).getText();
+            endTime = getSummaryCols().get(3).getText();
+
+            final WebElement statusElement = getSummaryCols().get(4);
+            assertTrue(statusElement.isDisplayed(), "Status should be displayed");
+            final String statusText = statusElement.getText();
+            final Object expectedColor = statusColorMap.get(statusText.trim());
+            assertNotNull(expectedColor,
+                "Unexpected status: " + statusText + " not found in: " + statusColorMap);
+            //status color not checked
+            //final String actualColor = statusElement.getCssValue("color");
+            //assertEquals(actualColor, expectedColor,
+            //    "Unexpected color for status in process instances block: " + statusText);
+            status = statusText;
+            isCheckBoxTicked = getCheckBox().isSelected();
+        }
+
+        private List<WebElement> getSummaryCols() {
+            return oneInstanceSummary.findElements(By.tagName("td"));
+        }
+
+        private WebElement getCheckBox() {
+            return getSummaryCols().get(0).findElement(By.tagName("input"));
+        }
+
+        private WebElement getNominalTimeButton() {
+            return getSummaryCols().get(1);
+        }
+
+        public String getStartTime() {
+            return startTime;
+        }
+
+        public String getEndTime() {
+            return endTime;
+        }
+
+        public String getStatus() {
+            return status;
+        }
+        public String getNominalTime() {
+            return nominalTime;
+        }
+
+        public boolean isCheckBoxSelected() {
+            return isCheckBoxTicked;
+        }
+
+        /**
+         * Click the checkbox corresponding to this result. It is the responsibility of the
+         * client to make sure that the web element for the instance is displayed and valid.
+         */
+        public void clickCheckBox() {
+            getCheckBox().click();
+            // Toggling of checkbox should change its internal state
+            // Note that we can't expect the web element to be displayed & valid at the point this
+            // object is used
+            isCheckBoxTicked = !isCheckBoxTicked;
+        }
+
+        @Override
+        public String toString() {
+            return "OneInstanceSummary{"
+                + "checkBox=" + isCheckBoxSelected()
+                + ", nominalTime=" + getNominalTime()
+                + ", startTime=" + getStartTime()
+                + ", endTime=" + getEndTime()
+                + ", status=" + getStatus()
+                + "}";
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/PageHeader.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/PageHeader.java b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/PageHeader.java
index c166eaf..8af6101 100644
--- a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/PageHeader.java
+++ b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/PageHeader.java
@@ -28,6 +28,8 @@ 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.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
 import org.testng.Assert;
 
 import java.util.ArrayList;
@@ -106,6 +108,17 @@ public class PageHeader {
             "Unexpected text on logout button");
     }
 
+    public SearchPage gotoHome() {
+        homeButton.click();
+        final SearchPage searchPage = PageFactory.initElements(driver, SearchPage.class);
+        searchPage.checkPage();
+        final PageHeader searchHeader = searchPage.getPageHeader();
+        searchHeader.checkLoggedIn();
+        Assert.assertEquals(searchHeader.getLoggedInUser(), LoginPage.UI_DEFAULT_USER,
+            "Unexpected user is displayed");
+        return searchPage;
+    }
+
     public void checkLoggedOut() {
         UIAssert.assertNotDisplayed(getLogoutButton(), "logout button");
     }
@@ -119,13 +132,10 @@ public class PageHeader {
         UIAssert.assertDisplayed(homeButton, "falcon logo");
         Assert.assertEquals(homeButton.getText(), "Falcon", "Unexpected home button text");
         UIAssert.assertDisplayed(falconLogo, "falcon logo");
-        final String oldUrl = driver.getCurrentUrl();
-
-        homeButton.click();
-        Assert.assertTrue(getHomeUrls().contains(driver.getCurrentUrl()),
-            "home button navigate to: " + driver.getCurrentUrl() + " instead of: " + getHomeUrls());
-        driver.get(oldUrl);
+        final WebElement helpLink = loginHeaderBox.findElement(By.tagName("a"));
+        UIAssert.assertDisplayed(helpLink, "help link");
 
+        final String oldUrl = driver.getCurrentUrl();
         //displayed if user is logged in: create entity buttons, upload entity button, username
         if (getLogoutButton().isDisplayed()) {
             //checking create entity box
@@ -133,15 +143,6 @@ public class PageHeader {
             final WebElement createEntityLabel = createEntityBox.findElement(By.tagName("h4"));
             Assert.assertEquals(createEntityLabel.getText(), "Create an entity",
                 "Unexpected create entity text");
-            doCreateCluster();
-            driver.get(oldUrl);
-            doCreateFeed();
-            driver.get(oldUrl);
-            doCreateProcess();
-            driver.get(oldUrl);
-            doCreateMirror();
-            driver.get(oldUrl);
-
             //checking upload entity part
             UIAssert.assertDisplayed(uploadEntityBox, "Create entity box");
             final WebElement uploadEntityLabel = uploadEntityBox.findElement(By.tagName("h4"));
@@ -152,13 +153,28 @@ public class PageHeader {
                 "Unexpected text on upload entity button");
             //checking if logged-in username is displayed
             AssertUtil.assertNotEmpty(getLoggedInUser(), "Expecting logged-in username.");
+
+            //create button navigation
+            doCreateCluster();
+            driver.get(oldUrl);
+            doCreateFeed();
+            driver.get(oldUrl);
+            doCreateProcess();
+            driver.get(oldUrl);
+            doCreateMirror();
+            driver.get(oldUrl);
         }
+        //home button navigation
+        homeButton.click();
+        Assert.assertTrue(getHomeUrls().contains(driver.getCurrentUrl()),
+            "home button navigate to: " + driver.getCurrentUrl() + " instead of: " + getHomeUrls());
+        driver.get(oldUrl);
 
-        //help link is always displayed
-        final WebElement helpLink = loginHeaderBox.findElement(By.tagName("a"));
-        UIAssert.assertDisplayed(helpLink, "help link");
+        //help link navigation
         Assert.assertEquals(helpLink.getText(), "Help", "Help link expected to have text 'Help'");
         helpLink.click();
+        new WebDriverWait(driver, AbstractSearchPage.PAGELOAD_TIMEOUT_THRESHOLD).until(
+            ExpectedConditions.stalenessOf(helpLink));
         Assert.assertEquals(driver.getCurrentUrl(), MerlinConstants.HELP_URL,
             "Unexpected help url");
         driver.get(oldUrl);

http://git-wip-us.apache.org/repos/asf/falcon/blob/fc2179f6/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/SearchPage.java
----------------------------------------------------------------------
diff --git a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/SearchPage.java b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/SearchPage.java
index aabd9ca..b1a7963 100644
--- a/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/SearchPage.java
+++ b/falcon-regression/merlin/src/main/java/org/apache/falcon/regression/ui/search/SearchPage.java
@@ -18,6 +18,10 @@
 
 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.TimeUtil;
 import org.apache.falcon.regression.core.util.UIAssert;
 import org.apache.log4j.Logger;
@@ -27,6 +31,7 @@ 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;
@@ -42,6 +47,8 @@ public class SearchPage extends AbstractSearchPage {
     private static final String CLASS_OF_SELECTED_ROW = "rowSelected";
     private static final int ANIMATION_DELAY = 2;
 
+    private static final Logger LOGGER = Logger.getLogger(SearchPage.class);
+
     public SearchPage(WebDriver driver) {
         super(driver);
     }
@@ -105,6 +112,25 @@ public class SearchPage extends AbstractSearchPage {
         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");
@@ -366,9 +392,16 @@ public class SearchPage extends AbstractSearchPage {
         }
 
         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;
         }
@@ -376,6 +409,70 @@ public class SearchPage extends AbstractSearchPage {
         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);
+            }
+        }
+
     }
 
 }