mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-07 03:22:23 +00:00
Lot of documentation for API and tests added. Furthermore the
implementations for the support of categories of Ajax requests is almost finished. It remains to add the support to the JavaScript in the file 'ajax-semaphore.js'.
This commit is contained in:
1 parent
9a267c57c6
commit
15cc751975
34 files changed
+1494
-171
No files matched your search
+10
-3
@@ -22,8 +22,15 @@ import com.google.gwt.user.client.ui.RootPanel;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
|
||||
public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, WindowSizeDetector {
|
||||
public static final String DEBUG_ID_ATTRIBUTE = "selenium-id";
|
||||
public static final String DEBUG_ID_PREFIX = "";
|
||||
/**
|
||||
* <p>The attribute which is used for the debug id.</p>
|
||||
*/
|
||||
public static final String DEBUG_ID_ATTRIBUTE = "selenium-id"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* <p>The prefix which is used for the debug id.</p>
|
||||
*/
|
||||
public static final String DEBUG_ID_PREFIX = ""; //$NON-NLS-1$
|
||||
|
||||
private DialogBox errorDialogBox;
|
||||
private HTML serverResponseLabel;
|
||||
@@ -62,7 +69,7 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W
|
||||
doOnModuleLoad();
|
||||
|
||||
if(DebugInfo.isDebugIdEnabled()) {
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls();
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls(MarkedAsyncCallback.CATEGORY_GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-4
@@ -6,9 +6,22 @@ import com.google.gwt.dom.client.HeadElement;
|
||||
import com.google.gwt.dom.client.NodeList;
|
||||
import com.google.gwt.dom.client.ScriptElement;
|
||||
|
||||
/**
|
||||
* <p>Utility class to inject JavaScript in the default document.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class JavaScriptInjector {
|
||||
private static HeadElement head;
|
||||
|
||||
/**
|
||||
* <p>Injects the given JavaScript code in the head of the default document. To inject the code a script element is
|
||||
* created and appended to the head element of the document.</p>
|
||||
*
|
||||
* @param javascript
|
||||
* The JavaScript code to inject.
|
||||
*/
|
||||
public static void inject(String javascript) {
|
||||
HeadElement head = getHead();
|
||||
ScriptElement element = createScriptElement();
|
||||
@@ -19,17 +32,17 @@ public class JavaScriptInjector {
|
||||
private static ScriptElement createScriptElement() {
|
||||
Document document = Document.get();
|
||||
ScriptElement script = document.createScriptElement();
|
||||
script.setAttribute("type", "text/javascript");
|
||||
script.setAttribute("type", "text/javascript"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
private static HeadElement getHead() {
|
||||
if (head == null) {
|
||||
if (JavaScriptInjector.head == null) {
|
||||
Document document = Document.get();
|
||||
NodeList<Element> nodes = document.getElementsByTagName("head");
|
||||
NodeList<Element> nodes = document.getElementsByTagName("head"); //$NON-NLS-1$
|
||||
Element element = nodes.getItem(0);
|
||||
assert element != null : "HTML Head element required";
|
||||
assert element != null : "HTML Head element required"; //$NON-NLS-1$
|
||||
HeadElement head = HeadElement.as(element);
|
||||
JavaScriptInjector.head = head;
|
||||
}
|
||||
|
||||
+75
-5
@@ -1,34 +1,104 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import com.google.gwt.debug.client.DebugInfo;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
|
||||
/**
|
||||
* <p>Abstract base class for asynchronous remote procedure calls which should be marked as pending until they complete.
|
||||
* Such calls will be marked using a counter, which is necessary in UI tests to be able able to tell exactly when an
|
||||
* asynchronous request has finished. With this counter a test which triggers a request (which can cause additional
|
||||
* request) can wait until the requests have finished. It's guaranteed that the counter is decremented no matter if
|
||||
* the call was successful or not.</p>
|
||||
*
|
||||
* <p>Note: Since the counter for pending Ajax requests is incremented as soon as an instance of this class is created
|
||||
* you must not create instances which are not used or assigned to an field!</p>
|
||||
*
|
||||
* @param <T>
|
||||
* The type of the return value for the asynchronous remote procedure call.
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public abstract class MarkedAsyncCallback<T> implements AsyncCallback<T> {
|
||||
/**
|
||||
* <p>The key for the category of global requests. The key for the global category is just an empty string.</p>
|
||||
*/
|
||||
public static final String CATEGORY_GLOBAL = ""; //$NON-NLS-1$
|
||||
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* <p>Creates a marked asynchronous remote procedure call for the global category.</p>
|
||||
*/
|
||||
public MarkedAsyncCallback() {
|
||||
PendingAjaxCallMarker.incrementPendingAjaxCalls();
|
||||
this(CATEGORY_GLOBAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a marked asynchronous remote procedure call for the given category. As soon as the asynchronous remote
|
||||
* procedure call is created, the counter for pending Ajax calls for the given category is incremented.</p>
|
||||
*
|
||||
* @param category
|
||||
* The category of the asynchronous remote procedure call.
|
||||
*/
|
||||
public MarkedAsyncCallback(String category) {
|
||||
this.category = category;
|
||||
|
||||
if(DebugInfo.isDebugIdEnabled()) {
|
||||
PendingAjaxCallMarker.incrementPendingAjaxCalls(this.category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Called when the asynchronous call fails to complete normally. The concrete handling of the failure has to be
|
||||
* implemented in {@link #handleFailure(Throwable)}. It's guaranteed that the counter for the pending requests is
|
||||
* decremented.</p>
|
||||
*
|
||||
* @param cause
|
||||
* The failure encountered while executing the remote procedure call.
|
||||
*/
|
||||
@Override
|
||||
public final void onFailure(Throwable cause) {
|
||||
try {
|
||||
handleFailure(cause);
|
||||
} finally {
|
||||
//if(DebugInfo.isDebugIdEnabled())
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls();
|
||||
if(DebugInfo.isDebugIdEnabled()) {
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Called when the asynchronous call completes successfully. The concrete handling of the result has to be
|
||||
* implemented in {@link #handleSuccess(Object)}. It's guaranteed that the counter for the pending requests is
|
||||
* decremented.</p>
|
||||
*
|
||||
* @param result
|
||||
* The return value of the remote produced call.
|
||||
*/
|
||||
@Override
|
||||
public final void onSuccess(T result) {
|
||||
try {
|
||||
handleSuccess(result);
|
||||
} finally {
|
||||
//if(DebugInfo.isDebugIdEnabled())
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls();
|
||||
if(DebugInfo.isDebugIdEnabled()) {
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Called when the asynchronous call fails to complete normally.</p>
|
||||
*
|
||||
* @param cause
|
||||
* The failure encountered while executing the remote procedure call.
|
||||
*/
|
||||
protected abstract void handleFailure(Throwable cause);
|
||||
|
||||
/**
|
||||
* <p>Called when the asynchronous call completes successfully.</p>
|
||||
*
|
||||
* @param result
|
||||
* The return value of the remote produced call.
|
||||
*/
|
||||
protected abstract void handleSuccess(T result);
|
||||
}
|
||||
+13
@@ -3,7 +3,20 @@ package com.sap.sailing.gwt.ui.client;
|
||||
import com.google.gwt.resources.client.ClientBundle;
|
||||
import com.google.gwt.resources.client.TextResource;
|
||||
|
||||
/**
|
||||
* <p>The client bundle for the resource "ajax-semaphore.js" which is needed for the execution of UI tests.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public interface PendingAjaxCallBundle extends ClientBundle {
|
||||
/**
|
||||
* <p>Returns the JavaScript for the counter of pending Ajax requests. The counter is used in UI tests to be able
|
||||
* to tell exactly when an asynchronous request has finished.</p>
|
||||
*
|
||||
* @return
|
||||
* The JavaScript for the counter of pending Ajax requests.
|
||||
*/
|
||||
@Source("ajax-semaphore.js")
|
||||
TextResource ajaxSemaphoreJS();
|
||||
}
|
||||
+24
-6
@@ -1,20 +1,38 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
/**
|
||||
* <p>Utility class to mark pending Ajax requests.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class PendingAjaxCallMarker {
|
||||
public static native void decrementPendingAjaxCalls() /*-{
|
||||
/**
|
||||
* <p>Decrements the counter for pending Ajax request of the given category.</p>
|
||||
*
|
||||
* @param category
|
||||
* The category of the Ajax request.
|
||||
*/
|
||||
public static native void decrementPendingAjaxCalls(String category) /*-{
|
||||
try {
|
||||
if (($wnd.PENDING_AJAX_CALLS) && (typeof $wnd.PENDING_AJAX_CALLS.decrementPendingCalls === "function")) {
|
||||
$wnd.PENDING_AJAX_CALLS.decrementPendingCalls();
|
||||
$wnd.PENDING_AJAX_CALLS.decrementPendingCalls(category);
|
||||
}
|
||||
} catch (exception) {
|
||||
// Do error handling
|
||||
}
|
||||
}-*/;
|
||||
|
||||
public static native void incrementPendingAjaxCalls() /*-{
|
||||
|
||||
/**
|
||||
* <p>Increments the counter for pending Ajax request of the given category.</p>
|
||||
*
|
||||
* @param category
|
||||
* The category of the Ajax request.
|
||||
*/
|
||||
public static native void incrementPendingAjaxCalls(String category) /*-{
|
||||
try {
|
||||
if (($wnd.PENDING_AJAX_CALLS) && (typeof $wnd.PENDING_AJAX_CALLS.incrementPendingCalls === "function")) {
|
||||
$wnd.PENDING_AJAX_CALLS.incrementPendingCalls();
|
||||
$wnd.PENDING_AJAX_CALLS.incrementPendingCalls(category);
|
||||
}
|
||||
} catch (exception) {
|
||||
// Do error handling
|
||||
@@ -22,6 +40,6 @@ public class PendingAjaxCallMarker {
|
||||
}-*/;
|
||||
|
||||
private PendingAjaxCallMarker() {
|
||||
super();
|
||||
// Utility class
|
||||
}
|
||||
}
|
||||
+41
-4
@@ -1,3 +1,19 @@
|
||||
/**
|
||||
* <p>JavaScript counter for pending Ajax requests. Since GWT application are heavily AJAX driven one of the challenges
|
||||
* in performing UI tests is to be able to tell when your application is in a state you expect. In simple cases you
|
||||
* can wait until a loading animation disappears or until a well known element has changed the state (e.g. visibility
|
||||
* or something else). However, since things are usually not that simple, especially in the context of GWT, it is more
|
||||
* stable if we are able to tell exactly when an asynchronous request has finished. For this reason we use the counter
|
||||
* for pending request which is incremented if an asynchronous callback is created and decremented if the callback
|
||||
* completes. With this counter a test which triggers a request (which can cause additional request) can wait until
|
||||
* the counter reaches 0 again.</p>
|
||||
*
|
||||
* TODO: Write some additional documentation for categories!
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
// TODO: Support categories of requests!
|
||||
var PENDING_AJAX_CALLS = PENDING_AJAX_CALLS || function() {
|
||||
/* Private variable bound through closure which is initialized the counter to be 1. This is important because the
|
||||
* GWT bootstrap process in itself can take a bit of time. So, at a minimum we have to wait until the module is
|
||||
@@ -5,16 +21,37 @@ var PENDING_AJAX_CALLS = PENDING_AJAX_CALLS || function() {
|
||||
*/
|
||||
var pendingCalls = 1;
|
||||
|
||||
return {
|
||||
numberOfPendingCalls : function() {
|
||||
return {
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param {string} category
|
||||
* The category of pending Ajax requests.
|
||||
* @returns {int}
|
||||
* The number of pending Ajax requests.
|
||||
*/
|
||||
numberOfPendingCalls : function(category) {
|
||||
return pendingCalls;
|
||||
},
|
||||
|
||||
decrementPendingCalls : function() {
|
||||
/**
|
||||
* <p>Decrements the counter by 1. This method should be called if an Ajax request is created.</p>
|
||||
*
|
||||
* @param {string} category
|
||||
* The category of the Ajax request.
|
||||
*/
|
||||
decrementPendingCalls : function(category) {
|
||||
pendingCalls--;
|
||||
},
|
||||
|
||||
incrementPendingCalls : function() {
|
||||
/**
|
||||
* <p>Increments the counter by 1. This method should be called if an Ajax request completes regardless if it
|
||||
* was successful or not.</p>
|
||||
*
|
||||
* @param {string} category
|
||||
* The category of the Ajax request.
|
||||
*/
|
||||
incrementPendingCalls : function(category) {
|
||||
pendingCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
means that each test will be executed once for each driver defined here. This allows to perform the tests with
|
||||
different browsers to validate the proper functionality of the application across different browsers.
|
||||
|
||||
The capabilities section can be used for fine tuning of the driver (e.g. version). For more informations see the
|
||||
The capabilities section can be used for fine tuning of the driver (e.g. version). For more information see the
|
||||
pages mentioned above.
|
||||
-->
|
||||
<driver-definition class="org.openqa.selenium.firefox.FirefoxDriver">
|
||||
|
||||
+46
-1
@@ -2,13 +2,58 @@ package com.sap.sailing.selenium.core;
|
||||
|
||||
import org.openqa.selenium.JavascriptExecutor;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||
|
||||
/**
|
||||
* <p>Models a condition that checks for pending Ajax calls. The condition is used in tests to be able able to tell
|
||||
* exactly when an asynchronous request has finished. The condition checks for the counter of a specified category as
|
||||
* introduced by {@code PendingAjaxCallBundle} and {@code PendingAjaxCallMarker}.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class AjaxCallsComplete implements ExpectedCondition<Boolean> {
|
||||
/**
|
||||
* <p>The key for the global category as defined in the class
|
||||
* <code>com.sap.sailing.gwt.ui.client.MarkedAsyncCallback</code>.</p>
|
||||
*/
|
||||
public static final String CATEGORY_GLOBAL = ""; //$NON-NLS-1$
|
||||
|
||||
private static final String JAVASCRIPT = "return (window.PENDING_AJAX_CALLS.numberOfPendingCalls(%s) === 0)"; //$NON-NLS-1$
|
||||
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* <p>Creates a new condition that checks the global category of Ajax calls.</p>
|
||||
*/
|
||||
public AjaxCallsComplete() {
|
||||
this(CATEGORY_GLOBAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new condition that checks the given category of Ajax calls.</p>
|
||||
*
|
||||
* @param category
|
||||
* The category of Ajax calls to check.
|
||||
*/
|
||||
public AjaxCallsComplete(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Determines if all pending Ajax call have been completed. Returns a boolean representing {@code true} if so
|
||||
* and boolean representing {@code false} otherwise.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver to use.
|
||||
* @return
|
||||
* A boolean representing {@code true} if all pending Ajax calls have been completed and {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public Boolean apply(WebDriver driver) {
|
||||
JavascriptExecutor executor = (JavascriptExecutor) driver;
|
||||
|
||||
return (Boolean) executor.executeScript("return (window.PENDING_AJAX_CALLS.numberOfPendingCalls() === 0)");
|
||||
return (Boolean) executor.executeScript(String.format(JAVASCRIPT, this.category));
|
||||
}
|
||||
}
|
||||
+53
-2
@@ -9,21 +9,56 @@ import org.openqa.selenium.support.CacheLookup;
|
||||
|
||||
import org.openqa.selenium.support.pagefactory.ByChained;
|
||||
|
||||
/**
|
||||
* <p>Builder which is responsible for the creation of location mechanism according to the annotations applied to a
|
||||
* field. The builder supports the annotations {@link org.openqa.selenium.support.CacheLookup}, {@link FindBy} and
|
||||
* {@link FindBys}.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class Annotations {
|
||||
/**
|
||||
* <p>Checks if the given field is annotated with one of the annotations supported by the builder.</p>
|
||||
*
|
||||
* @param field
|
||||
* The field to check for the annotations.
|
||||
* @return
|
||||
* {@code true} if the field is annotated with one of the supported annotations and {@code false} otherwise.
|
||||
*/
|
||||
public static boolean isAnnotationPresent(Field field) {
|
||||
return (field.getAnnotation(FindBys.class) != null || field.getAnnotation(FindBy.class) != null);
|
||||
}
|
||||
|
||||
private Field field;
|
||||
|
||||
/**
|
||||
* <p>Creates a new builder for the given field.</p>
|
||||
*
|
||||
* @param field
|
||||
* The field for which the location mechanism is to build.
|
||||
*/
|
||||
public Annotations(Field field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Determines if the field is annotated with {@code CacheLookup} to indicate that it never changes (that is, that
|
||||
* the same instance in the DOM will always be used).</p>
|
||||
*
|
||||
* @return
|
||||
* {@code true} if the field is annotated with {@code CacheLookup} and {@code false} otherwise.
|
||||
*/
|
||||
public boolean isLookupCached() {
|
||||
return (this.field.getAnnotation(CacheLookup.class) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds and returns the location mechanism for the field according to the defined annotations.</p>
|
||||
*
|
||||
* @return
|
||||
* The location mechanism for the field.
|
||||
*/
|
||||
public By buildBy() {
|
||||
assertValidAnnotations();
|
||||
|
||||
@@ -40,6 +75,14 @@ public class Annotations {
|
||||
throw new IllegalArgumentException("Cannot determine how to locate element " + this.field); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds and returns the location mechanism as specified by the given {@code FindBy}.</p>
|
||||
*
|
||||
* @param findBy
|
||||
* The definition of the location mechanism.
|
||||
* @return
|
||||
* The location mechanism as specified.
|
||||
*/
|
||||
protected By buildByFromFindBy(FindBy findBy) {
|
||||
assertValidFindBy(findBy);
|
||||
|
||||
@@ -54,6 +97,14 @@ public class Annotations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds and returns the location mechanism as specified by the given {@code FindBys}.</p>
|
||||
*
|
||||
* @param findBys
|
||||
* The definition of the location mechanism.
|
||||
* @return
|
||||
* The location mechanism as specified.
|
||||
*/
|
||||
protected By buildByFromFindBys(FindBys findBys) {
|
||||
assertValidFindBys(findBys);
|
||||
|
||||
@@ -79,7 +130,7 @@ public class Annotations {
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
throw new IllegalArgumentException("You may use only one of '@FindBy' and '@FindBys' annotations");
|
||||
throw new IllegalArgumentException("You may use only one of '@FindBy' and '@FindBys' annotations"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +142,7 @@ public class Annotations {
|
||||
|
||||
private void assertValidFindBy(FindBy findBy) {
|
||||
if (findBy.how() == null || findBy.using() == null) {
|
||||
throw new IllegalArgumentException("You must define the 'how' property as well as the 'using' property");
|
||||
throw new IllegalArgumentException("You must define the 'how' property as well as the 'using' property"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -1,12 +1,19 @@
|
||||
package com.sap.sailing.selenium.core;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.SearchContext;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
/**
|
||||
* <p>A mechanism to locate elements by the value of the "selenium-id" attribute.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class BySeleniumId extends By {
|
||||
/**
|
||||
* <p>XPath expression for the search of an element with a given selenium identifier. The search is performed in the
|
||||
@@ -17,6 +24,13 @@ public class BySeleniumId extends By {
|
||||
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* <p>Creates a new location mechanism which search for an element or a list of elements with the given
|
||||
* identifier.</p>
|
||||
*
|
||||
* @param id
|
||||
* The value of the "selenium-id" attribute to search for.
|
||||
*/
|
||||
public BySeleniumId(String id) {
|
||||
super();
|
||||
|
||||
|
||||
+12
@@ -4,5 +4,17 @@ import org.openqa.selenium.SearchContext;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* <p>Models a condition that might reasonably be expected to eventually evaluate to something that is neither
|
||||
* {@code null} nor {@code false} using a search context.</p>
|
||||
*
|
||||
* <p>Note that it is expected that conditions are idempotent. They will be called in a loop by the and any modification
|
||||
* of the state of the application under test may have unexpected side-effects.</p>
|
||||
*
|
||||
* @param <T>
|
||||
* The return type
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public interface ElementSearchCondition<T> extends Function<SearchContext, T> {
|
||||
}
|
||||
+7
-2
@@ -5,8 +5,13 @@ import org.openqa.selenium.SearchContext;
|
||||
import org.openqa.selenium.StaleElementReferenceException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
/**
|
||||
* <p>Canned conditions which use a search context and which are generally useful within tests.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class ElementSearchConditions {
|
||||
|
||||
/**
|
||||
* An expectation for checking that an element is present on the DOM of a page and visible. Visibility means that
|
||||
* the element is not only displayed but also has a height and width that is greater than 0.
|
||||
@@ -29,7 +34,7 @@ public class ElementSearchConditions {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "visibility of element located by " + locator;
|
||||
return "visibility of element located by " + locator; //$NON-NLS-1$
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,10 +7,43 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
/**
|
||||
* <p>Used to mark a field on a page object to indicate an mechanism for locating the element or a list of elements.
|
||||
* In conjunction with a page factory this allows users to quickly and easily create page objects.</p>
|
||||
*
|
||||
* <p>To use this annotation you have to specifying the locating mechanism ("how") as well as an appropriate value to
|
||||
* use ("using") on a field of type {@code WebElement} or {@code List<WebElement>}. This will delegate down
|
||||
* to the matching methods in the defined {@code By} class.</p>
|
||||
*
|
||||
* <p>An usage could be look as follows:
|
||||
*
|
||||
* <pre>
|
||||
* @FindBy(how = ById.class, using = "myImage")
|
||||
* WebElement image;
|
||||
*
|
||||
* @FindBy(how = ByTagName.class, using = "href")
|
||||
* List<WebElement> links;
|
||||
* </pre></p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FindBy {
|
||||
/**
|
||||
* <p>Returns the mechanism to use for locating the element or the list of elements.</p>
|
||||
*
|
||||
* @return
|
||||
* The mechanism to use for locating the element or the list of elements.
|
||||
*/
|
||||
Class<? extends By> how();
|
||||
|
||||
/**
|
||||
* <p>Returns the value to use for locating the element or the list of elements.</p>
|
||||
*
|
||||
* @return
|
||||
* The value to use for locating the element or the list of elements.
|
||||
*/
|
||||
String using();
|
||||
}
|
||||
@@ -5,8 +5,31 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>Used to mark a field on a page object to indicate that the lookup should use a series of mechanism for locating
|
||||
* the element or a list of elements, whereby each of the locators in sequence has to match.</p>
|
||||
*
|
||||
* <p>To find all elements that representing a link which appear under an element with the stylesheet class "menu", a
|
||||
* usage could look as follows:
|
||||
*
|
||||
* <pre>
|
||||
* @FindBys({@FindBy(how = ByClassName.class, using = "menu"),
|
||||
* @FindBy(how = ByTagName.class, using = "href")
|
||||
* })
|
||||
* List<WebElement> menuEntries;
|
||||
* </pre></p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FindBys {
|
||||
/**
|
||||
* <p>Returns the series of mechanisms to use for locating the element or the list of elements.</p>
|
||||
*
|
||||
* @return
|
||||
* The series of mechanisms to use for locating the element or the list of elements.
|
||||
*/
|
||||
FindBy[] value();
|
||||
}
|
||||
@@ -6,6 +6,14 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>Used to mark a field in test cases that should be injected with the {@link TestEnvironment} by the
|
||||
* {@link Selenium} runner. The environment provides the web driver to use for the tests as well as other information
|
||||
* as configured.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
@Inherited
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.sap.sailing.selenium.core;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.runner.Description;
|
||||
|
||||
/**
|
||||
* <p>Describes the interface for objects that generate unique file names for screen shots.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public interface ScreenShotFilenameGenerator {
|
||||
/**
|
||||
* <p>File extension for screenshots captured with a Selenium web driver.</p>
|
||||
*/
|
||||
public static final String SCREENSHOT_FILE_EXTENSION = ".png"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* <p>Generates and returns a unique file name for the test case described by the given description.</p>
|
||||
*
|
||||
* @param description
|
||||
* The test case description.
|
||||
* @return
|
||||
* The generated unique file name.
|
||||
*/
|
||||
File getTargetFilename(Description description);
|
||||
}
|
||||
+99
-61
@@ -34,67 +34,20 @@ import com.sap.sailing.selenium.core.TestEnvironmentConfiguration.DriverDefiniti
|
||||
import com.sap.sailing.selenium.core.impl.TestEnvironmentImpl;
|
||||
|
||||
/**
|
||||
* <p>The Selenium runner is a JUnit runner that runs a test case, using the WebDriver API of Selenium, as a suite of
|
||||
* tests. The runner takes a configuration file and runs the test for each defined browser.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class Selenium extends ParentRunner<SeleniumJUnit4ClassRunner> {
|
||||
private final List<SeleniumJUnit4ClassRunner> children;
|
||||
|
||||
public Selenium(Class<?> klass) throws InitializationError {
|
||||
super(klass);
|
||||
|
||||
this.children = new LinkedList<>();
|
||||
|
||||
initializeRunners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SeleniumJUnit4ClassRunner> getChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Description describeChild(SeleniumJUnit4ClassRunner child) {
|
||||
return child.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runChild(SeleniumJUnit4ClassRunner child, RunNotifier notifier) {
|
||||
child.run(notifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void collectInitializationErrors(java.util.List<java.lang.Throwable> errors) {
|
||||
super.collectInitializationErrors(errors);
|
||||
|
||||
// QUESTION: Do we need addional checks here?
|
||||
}
|
||||
|
||||
private void initializeRunners() throws InitializationError {
|
||||
try {
|
||||
TestEnvironmentConfiguration configuration = TestEnvironmentConfiguration.getInstance();
|
||||
String contextRoot = configuration.getContextRoot();
|
||||
String screenshotsFolder = configuration.getScreenshotsFolder();
|
||||
Map<String, String> systemProperties = configuration.getSystemProperties();
|
||||
|
||||
for (Entry<String, String> property : systemProperties.entrySet()) {
|
||||
System.setProperty(property.getKey(), property.getValue());
|
||||
}
|
||||
|
||||
TestClass test = getTestClass();
|
||||
URL screenshotsStorage = (configuration.screenshotsEnabled() ? new URL(screenshotsFolder) : null);
|
||||
|
||||
for (DriverDefinition defenition : configuration.getDriverDefinitions()) {
|
||||
SeleniumJUnit4ClassRunner child = new SeleniumJUnit4ClassRunner(test.getJavaClass(), contextRoot,
|
||||
screenshotsStorage, defenition);
|
||||
|
||||
this.children.add(child);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
throw new InitializationError(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A test runner that will run the tests for a specific browser instance using a Selenium web driver. The web
|
||||
* driver is provided to the tests by injecting it to annotated fields.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
protected class SeleniumJUnit4ClassRunner extends BlockJUnit4ClassRunner {
|
||||
private String root;
|
||||
private URL screenshots;
|
||||
@@ -102,6 +55,21 @@ public class Selenium extends ParentRunner<SeleniumJUnit4ClassRunner> {
|
||||
|
||||
private TestEnvironmentImpl environment;
|
||||
|
||||
/**
|
||||
* <p>Creates a new runner to run the test cases, encapsulated within the given class, for a specific browser
|
||||
* instance using a Selenium web driver.</p>
|
||||
*
|
||||
* @param klass
|
||||
* The class containing the tests.
|
||||
* @param root
|
||||
* The context root (base URL) against the tests should be executed.
|
||||
* @param screenshots
|
||||
* The folder where screenshots should be stored.
|
||||
* @param definition
|
||||
* Definition of the web driver to use.
|
||||
* @throws InitializationError
|
||||
*
|
||||
*/
|
||||
public SeleniumJUnit4ClassRunner(Class<?> klass, String root, URL screenshots, DriverDefinition definition)
|
||||
throws InitializationError {
|
||||
super(klass);
|
||||
@@ -112,12 +80,13 @@ public class Selenium extends ParentRunner<SeleniumJUnit4ClassRunner> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the test object and inject Selenium server or web driver into fields that were annotated with
|
||||
* {@code annotationType}.
|
||||
* <p>Creates the test object and injects the test environment to use into fields that were annotated with
|
||||
* {@link Managed}.</p>
|
||||
*
|
||||
* @return The test object.
|
||||
* @return
|
||||
* The test object.
|
||||
* @throws Exception
|
||||
* If there was an error creating the test object.
|
||||
* If there was an error creating the test object.
|
||||
*/
|
||||
@Override
|
||||
protected Object createTest() throws Exception {
|
||||
@@ -236,4 +205,73 @@ public class Selenium extends ParentRunner<SeleniumJUnit4ClassRunner> {
|
||||
field.set(instance, value);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<SeleniumJUnit4ClassRunner> children;
|
||||
|
||||
/**
|
||||
* <p>Creates a runner to run the test cases encapsulated within the given class.</p>
|
||||
*
|
||||
* @param klass
|
||||
* The class that encapsulates the test cases.
|
||||
* @throws InitializationError
|
||||
* If there was an error during the initialization of the test runner.
|
||||
*/
|
||||
public Selenium(Class<?> klass) throws InitializationError {
|
||||
super(klass);
|
||||
|
||||
this.children = new LinkedList<>();
|
||||
|
||||
initializeRunners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SeleniumJUnit4ClassRunner> getChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Description describeChild(SeleniumJUnit4ClassRunner child) {
|
||||
return child.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runChild(SeleniumJUnit4ClassRunner child, RunNotifier notifier) {
|
||||
child.run(notifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void collectInitializationErrors(java.util.List<java.lang.Throwable> errors) {
|
||||
super.collectInitializationErrors(errors);
|
||||
|
||||
// QUESTION: Do we need addional checks here?
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds the test runners for each browser for test cases. A test runner is created for each web driver
|
||||
* specified in the used configuration.</p>
|
||||
*/
|
||||
private void initializeRunners() throws InitializationError {
|
||||
try {
|
||||
TestEnvironmentConfiguration configuration = TestEnvironmentConfiguration.getInstance();
|
||||
String contextRoot = configuration.getContextRoot();
|
||||
String screenshotsFolder = configuration.getScreenshotsFolder();
|
||||
Map<String, String> systemProperties = configuration.getSystemProperties();
|
||||
|
||||
for (Entry<String, String> property : systemProperties.entrySet()) {
|
||||
System.setProperty(property.getKey(), property.getValue());
|
||||
}
|
||||
|
||||
TestClass test = getTestClass();
|
||||
URL screenshotsStorage = (configuration.screenshotsEnabled() ? new URL(screenshotsFolder) : null);
|
||||
|
||||
for (DriverDefinition defenition : configuration.getDriverDefinitions()) {
|
||||
SeleniumJUnit4ClassRunner child = new SeleniumJUnit4ClassRunner(test.getJavaClass(), contextRoot,
|
||||
screenshotsStorage, defenition);
|
||||
|
||||
this.children.add(child);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
throw new InitializationError(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
-3
@@ -17,6 +17,18 @@ import org.openqa.selenium.support.ui.FluentWait;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* <p>An element locator, which will lazily locate and wait for an element or an element list to appear, by polling the
|
||||
* UI on a regular basis. It is possible to define the maximum amount of time to wait for the element or the list of
|
||||
* elements, as well as the frequency with which to check the UI.</p>
|
||||
*
|
||||
* <p>This class is designed for use with the page factory to make the usage of page objects as simple and easy as
|
||||
* possible and understands the annotations {@link org.openqa.selenium.support.CacheLookup}, {@link FindBy} and
|
||||
* {@link FindBys}.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class SeleniumElementLocator implements ElementLocator {
|
||||
private final SearchContext context;
|
||||
private final FluentWait<SearchContext> wait;
|
||||
@@ -27,16 +39,39 @@ public class SeleniumElementLocator implements ElementLocator {
|
||||
private WebElement element;
|
||||
private List<WebElement> elements;
|
||||
|
||||
/**
|
||||
* <p>Creates a new element locator for the given field using the specified context and timeout for the search.</p>
|
||||
*
|
||||
* @param context
|
||||
* The context for search of the element or the list of elements.
|
||||
* @param field
|
||||
* The field for which an element or an element list should be lazily locate.
|
||||
* @param timeOut
|
||||
* The maximum amount of time to wait for the element or the list of elements.
|
||||
*/
|
||||
public SeleniumElementLocator(SearchContext context, Field field, int timeOut) {
|
||||
this(context, field, timeOut, 250);
|
||||
}
|
||||
|
||||
public SeleniumElementLocator(SearchContext context, Field field, int timeOut, int intervall) {
|
||||
/**
|
||||
* <p>Creates a new element locator for the given field using the specified context, timeout and polling interval
|
||||
* for the search.</p>
|
||||
*
|
||||
* @param context
|
||||
* The context for search of the element or the list of elements.
|
||||
* @param field
|
||||
* The field for which an element or an element list should be lazily locate.
|
||||
* @param timeOut
|
||||
* The maximum amount of time to wait for the element or the list of elements.
|
||||
* @param interval
|
||||
* The frequency with which to check the search context.
|
||||
*/
|
||||
public SeleniumElementLocator(SearchContext context, Field field, int timeOut, int interval) {
|
||||
this.context = context;
|
||||
|
||||
this.wait = new FluentWait<>(this.context);
|
||||
this.wait.withTimeout(timeOut, TimeUnit.SECONDS);
|
||||
this.wait.pollingEvery(intervall, TimeUnit.MILLISECONDS);
|
||||
this.wait.pollingEvery(interval, TimeUnit.MILLISECONDS);
|
||||
this.wait.ignoring(NoSuchElementException.class);
|
||||
|
||||
Annotations annotations = new Annotations(field);
|
||||
@@ -44,6 +79,14 @@ public class SeleniumElementLocator implements ElementLocator {
|
||||
this.by = annotations.buildBy();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches and returns the context for the first matching element using the mechanism defined on the field. The
|
||||
* element returned will be present on the DOM and is actually visible. You can override
|
||||
* {@link #isElementUsable(WebElement)} if it is important to you to define another condition.</p></p>
|
||||
*
|
||||
* @return
|
||||
* The first matching element in the search context, or null if nothing matches.
|
||||
*/
|
||||
@Override
|
||||
public WebElement findElement() {
|
||||
if (this.useCache && this.element != null) {
|
||||
@@ -62,6 +105,14 @@ public class SeleniumElementLocator implements ElementLocator {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Searches and returns the context for the list of all matching elements using the mechanism defined on the
|
||||
* field. The elements returned will be present on the DOM and are actually visible. You can override
|
||||
* {@link #isElementUsable(WebElement)} if it is important to you to define another condition.</p>
|
||||
*
|
||||
* @return
|
||||
* A list of all matching elements in the search context, or an empty list if nothing matches.
|
||||
*/
|
||||
@Override
|
||||
public List<WebElement> findElements() {
|
||||
if (this.useCache && this.elements != null) {
|
||||
@@ -78,10 +129,25 @@ public class SeleniumElementLocator implements ElementLocator {
|
||||
return this.elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the mechanism used to locate elements within the search context.</p>
|
||||
*
|
||||
* @return
|
||||
* The mechanism used to locate elements within the search context.
|
||||
*/
|
||||
public By getBy() {
|
||||
return this.by;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>By default, elements are considered as "found" if they are in the DOM and displayed. You can override this
|
||||
* method in order to change whether or not an element is considered as loaded.</p>
|
||||
*
|
||||
* @param element
|
||||
* The element to use.
|
||||
* @return
|
||||
* {@code true} if the element is usable and {@code false} otherwise.
|
||||
*/
|
||||
protected boolean isElementUsable(WebElement element) {
|
||||
return element.isDisplayed();
|
||||
}
|
||||
|
||||
+38
-4
@@ -7,26 +7,60 @@ import org.openqa.selenium.SearchContext;
|
||||
import org.openqa.selenium.support.pagefactory.ElementLocator;
|
||||
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
|
||||
|
||||
/**
|
||||
* <p>The factory for producing {@code SeleniumElementLocator}. For each call to {@link #createLocator} a new element
|
||||
* locator will be returned, as expected by the interface.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class SeleniumElementLocatorFactory implements ElementLocatorFactory {
|
||||
private SearchContext context;
|
||||
private final int timeOut;
|
||||
private final int intervall;
|
||||
private final int interval;
|
||||
|
||||
/**
|
||||
* <p>Creates a new factory which uses the given context and timeout for the search. For the search an interval of
|
||||
* 250ms is used.</p>
|
||||
*
|
||||
* @param context
|
||||
* The context for search of the element or the list of elements.
|
||||
* @param timeOut
|
||||
* The maximum amount of time to wait for the element or the list of elements.
|
||||
*/
|
||||
public SeleniumElementLocatorFactory(SearchContext context, int timeOut) {
|
||||
this(context, timeOut, 250);
|
||||
}
|
||||
|
||||
public SeleniumElementLocatorFactory(SearchContext context, int timeOut, int intervall) {
|
||||
/**
|
||||
* <p>Creates a new factory which uses the given context, timeout and interval for the search.</p>
|
||||
*
|
||||
* @param context
|
||||
* The context for search of an element or a list of elements.
|
||||
* @param timeOut
|
||||
* The maximum amount of time to wait for the element or the list of elements.
|
||||
* @param interval
|
||||
* The frequency with which to check the search context.
|
||||
*/
|
||||
public SeleniumElementLocatorFactory(SearchContext context, int timeOut, int interval) {
|
||||
this.context = context;
|
||||
this.timeOut = timeOut;
|
||||
this.intervall = intervall;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new element locator for the given field.</p>
|
||||
*
|
||||
* @param field
|
||||
* The field for which an element or an element list should be lazily locate.
|
||||
* @return
|
||||
* The element locator for the search.
|
||||
*/
|
||||
@Override
|
||||
public ElementLocator createLocator(Field field) {
|
||||
if (!Annotations.isAnnotationPresent(field))
|
||||
return null;
|
||||
|
||||
return new SeleniumElementLocator(this.context, field, this.timeOut, this.intervall);
|
||||
return new SeleniumElementLocator(this.context, field, this.timeOut, this.interval);
|
||||
}
|
||||
}
|
||||
+27
@@ -4,10 +4,37 @@ import java.net.URL;
|
||||
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* <p>Description of the test environment in which a test is executed by the {@link Selenium} runner. The environment is
|
||||
* injected by the runner to fields annotated with {@link Managed} and consists of the web driver which is used, a
|
||||
* folder where screenshots should be stored and a context root (base URL) against the tests is executed.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public interface TestEnvironment {
|
||||
/**
|
||||
* <p>Returns the web driver used for the execution of the test.</p>
|
||||
*
|
||||
* @return
|
||||
* The web driver used for the execution of the test.
|
||||
*/
|
||||
public WebDriver getWebDriver();
|
||||
|
||||
/**
|
||||
* <p>Returns the context root (base URL) against the tests should be executed. The context root identifies the
|
||||
* web application and usually consists of a protocol definition, the host and a path.</p>
|
||||
*
|
||||
* @return
|
||||
* The context root (base URL) against the tests should be executed.
|
||||
*/
|
||||
public String getContextRoot();
|
||||
|
||||
/**
|
||||
* <p>Returns the folder where screenshots, captured during the test, should be stored</p>
|
||||
*
|
||||
* @return
|
||||
* The folder where screenshots should be stored.
|
||||
*/
|
||||
public URL getScreenshotFolder();
|
||||
}
|
||||
+117
-23
@@ -26,131 +26,192 @@ import org.w3c.dom.Element;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* <p>Java representation of the test environment configuration as defined in the XML file. Since there can only be one
|
||||
* configuration per test run, this class is implemented as a singleton.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class TestEnvironmentConfiguration {
|
||||
/**
|
||||
* <p>Java representation of a web driver as defined in the configuration file.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
protected static class DriverDefinition {
|
||||
private String driver;
|
||||
private Map<String, String> capabilities;
|
||||
|
||||
/**
|
||||
* <p>Creates a new definition of a web driver with the given class and the desired capabilities.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The classname of the web driver.
|
||||
* @param capabilities
|
||||
* The capabilities of the web driver.
|
||||
*/
|
||||
public DriverDefinition(String driver, Map<String, String> capabilities) {
|
||||
this.driver = driver;
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the classname of the web driver.</p>
|
||||
*
|
||||
* @return
|
||||
* The classname of the web driver.
|
||||
*/
|
||||
public String getDriver() {
|
||||
return this.driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the desired capabilities of the web driver.</p>
|
||||
*
|
||||
* @return
|
||||
* The desired capabilities of the web driver.
|
||||
*/
|
||||
public Map<String, String> getCapabilities() {
|
||||
return this.capabilities;
|
||||
}
|
||||
}
|
||||
|
||||
public static final String TEST_ENVIRONMENT_CONFIGURATION = "selenium.test.environment.configuration";
|
||||
/**
|
||||
* <p>The key of the system property for the specification of the configuration file.</p>
|
||||
*/
|
||||
public static final String TEST_ENVIRONMENT_CONFIGURATION = "selenium.test.environment.configuration"; //$NON-NLS-1$
|
||||
|
||||
private static final String TEST_ENVIRONMENT_SCHEMA = "test-environment-configuration.xsd";
|
||||
private static final String TEST_ENVIRONMENT_SCHEMA = "test-environment-configuration.xsd"; //$NON-NLS-1$
|
||||
|
||||
private static final String NAMESPACE_URI = "http://www.sapsailing.com/test-environment";
|
||||
private static final String NAMESPACE_URI = "http://www.sapsailing.com/test-environment"; //$NON-NLS-1$
|
||||
|
||||
private static final String CONTEXT_ROOT = "context-root";
|
||||
private static final String CONTEXT_ROOT = "context-root"; //$NON-NLS-1$
|
||||
|
||||
private static final String SCREENSHOTS_FOLDER = "screenshots-folder";
|
||||
private static final String SCREENSHOTS_FOLDER = "screenshots-folder"; //$NON-NLS-1$
|
||||
|
||||
private static final String SYSTEM_PROPERTIES = "system-properties";
|
||||
private static final String SYSTEM_PROPERTIES = "system-properties"; //$NON-NLS-1$
|
||||
|
||||
private static final String SYSTEM_PROPERTY = "system-property";
|
||||
private static final String SYSTEM_PROPERTY = "system-property"; //$NON-NLS-1$
|
||||
|
||||
private static final String DRIVER_DEFINITION = "driver-definition";
|
||||
private static final String DRIVER_DEFINITION = "driver-definition"; //$NON-NLS-1$
|
||||
|
||||
private static final String DRIVER_CLASS = "class";
|
||||
private static final String DRIVER_CLASS = "class"; //$NON-NLS-1$
|
||||
|
||||
private static final String DRIVER_CAPABILITIES = "capabilities";
|
||||
private static final String DRIVER_CAPABILITIES = "capabilities"; //$NON-NLS-1$
|
||||
|
||||
private static final String DRIVER_CAPABILITY = "capability";
|
||||
private static final String DRIVER_CAPABILITY = "capability"; //$NON-NLS-1$
|
||||
|
||||
private static final String PARAMETER_NAME = "name";
|
||||
private static final String PARAMETER_NAME = "name"; //$NON-NLS-1$
|
||||
|
||||
private static final String PARAMETER_VALUE = "value";
|
||||
private static final String PARAMETER_VALUE = "value"; //$NON-NLS-1$
|
||||
|
||||
private static TestEnvironmentConfiguration instance;
|
||||
|
||||
/**
|
||||
* <p>Returns the Java representation of the test environment configuration as defined in the XML file.</p>
|
||||
*
|
||||
* @return
|
||||
* The Java representation of the test environment configuration as defined in the XML file.
|
||||
*/
|
||||
public static TestEnvironmentConfiguration getInstance() {
|
||||
if (instance == null) {
|
||||
if (TestEnvironmentConfiguration.instance == null) {
|
||||
try {
|
||||
instance = createTestEnvironmentConfiguration();
|
||||
TestEnvironmentConfiguration.instance = createTestEnvironmentConfiguration();
|
||||
} catch(Exception exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
return TestEnvironmentConfiguration.instance;
|
||||
}
|
||||
|
||||
private static synchronized TestEnvironmentConfiguration createTestEnvironmentConfiguration() throws
|
||||
ParserConfigurationException, SAXException, IOException {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
if (TestEnvironmentConfiguration.instance != null) {
|
||||
return TestEnvironmentConfiguration.instance;
|
||||
}
|
||||
|
||||
Document document = readTestConfiguration();
|
||||
|
||||
Element testEnvironmentNode = document.getDocumentElement();
|
||||
testEnvironmentNode.normalize();
|
||||
|
||||
String contextRoot = XMLHelper.getContentTextNS(testEnvironmentNode, CONTEXT_ROOT, NAMESPACE_URI);
|
||||
String screenshotsFolder = XMLHelper.getContentTextNS(testEnvironmentNode, SCREENSHOTS_FOLDER, NAMESPACE_URI);
|
||||
Map<String, String> systemProperties = createSystemProperties(testEnvironmentNode);
|
||||
List<DriverDefinition> driverDefenitions = createDriverDefenitions(testEnvironmentNode);
|
||||
|
||||
return new TestEnvironmentConfiguration(contextRoot, screenshotsFolder, systemProperties, driverDefenitions);
|
||||
}
|
||||
|
||||
private static synchronized Document readTestConfiguration() throws ParserConfigurationException,
|
||||
SAXException, IOException {
|
||||
String path = System.getProperty(TEST_ENVIRONMENT_CONFIGURATION);
|
||||
|
||||
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||
Schema schema = schemaFactory.newSchema(new StreamSource(TEST_ENVIRONMENT_SCHEMA));
|
||||
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
||||
builderFactory.setNamespaceAware(true);
|
||||
builderFactory.setSchema(schema);
|
||||
|
||||
DocumentBuilder builder = builderFactory.newDocumentBuilder();
|
||||
InputSource source = new InputSource(new FileInputStream(path));
|
||||
|
||||
return builder.parse(source);
|
||||
}
|
||||
|
||||
private static Map<String, String> createSystemProperties(Element testEnvironmentNode) {
|
||||
Element systemPropertiesNode = XMLHelper.getElementNS(testEnvironmentNode, SYSTEM_PROPERTIES, NAMESPACE_URI);
|
||||
|
||||
if (systemPropertiesNode == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
List<Element> systemPropertyNodes = XMLHelper.getElementsNS(systemPropertiesNode, SYSTEM_PROPERTY,
|
||||
NAMESPACE_URI);
|
||||
|
||||
for(Element systemPropertyNode : systemPropertyNodes) {
|
||||
String propertyName = XMLHelper.getContentTextNS(systemPropertyNode, PARAMETER_NAME, NAMESPACE_URI);
|
||||
String propertyValue = XMLHelper.getContentTextNS(systemPropertyNode, PARAMETER_VALUE, NAMESPACE_URI);
|
||||
|
||||
properties.put(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static List<DriverDefinition> createDriverDefenitions(Element testEnvironmentNode) {
|
||||
List<DriverDefinition> defenitions = new LinkedList<>();
|
||||
|
||||
List<Element> driverDefinitionNodes = XMLHelper.getElementsNS(testEnvironmentNode, DRIVER_DEFINITION,
|
||||
NAMESPACE_URI);
|
||||
|
||||
for(Element driverDefinitionNode : driverDefinitionNodes) {
|
||||
defenitions.add(createDriverDefinition(driverDefinitionNode));
|
||||
}
|
||||
|
||||
return defenitions;
|
||||
}
|
||||
|
||||
private static DriverDefinition createDriverDefinition(Element driverDefinitionNode) {
|
||||
String driverClass = XMLHelper.getAttributeValueNS(driverDefinitionNode, DRIVER_CLASS, null);;
|
||||
Element capabilitiesNode = XMLHelper.getElementNS(driverDefinitionNode, DRIVER_CAPABILITIES, NAMESPACE_URI);
|
||||
|
||||
if (capabilitiesNode == null) {
|
||||
Map<String, String> emptyCapabilities = Collections.emptyMap();
|
||||
return new DriverDefinition(driverClass, emptyCapabilities);
|
||||
return new DriverDefinition(driverClass, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
Map<String, String> capabilities = new HashMap<>();
|
||||
|
||||
for(Element capabilityNode : XMLHelper.getElementsNS(capabilitiesNode, DRIVER_CAPABILITY, NAMESPACE_URI)) {
|
||||
String capabilityName = XMLHelper.getContentTextNS(capabilityNode, PARAMETER_NAME, NAMESPACE_URI);
|
||||
String capabilityValue = XMLHelper.getContentTextNS(capabilityNode, PARAMETER_VALUE, NAMESPACE_URI);
|
||||
capabilities.put(capabilityName, capabilityValue);
|
||||
}
|
||||
|
||||
return new DriverDefinition(driverClass, capabilities);
|
||||
}
|
||||
|
||||
@@ -161,9 +222,7 @@ public class TestEnvironmentConfiguration {
|
||||
private Map<String, String> properties;
|
||||
|
||||
private List<DriverDefinition> definitions;
|
||||
|
||||
// TODO: Add a timestamp which should be used for the screenshot folder
|
||||
|
||||
|
||||
private TestEnvironmentConfiguration(String root, String screenshots, Map<String, String> properties,
|
||||
List<DriverDefinition> defenitions) {
|
||||
this.root = root;
|
||||
@@ -172,22 +231,57 @@ public class TestEnvironmentConfiguration {
|
||||
this.definitions = defenitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the context root (base URL) for the execution of the tests as defined in the configuration. The
|
||||
* context root identifies the web application and usually consists of a protocol definition, the host and a
|
||||
* path.</p>
|
||||
*
|
||||
* @return
|
||||
* The context root for the execution of the tests.
|
||||
*/
|
||||
public String getContextRoot() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns {@code true} if screenshots should be captured during the execution of tests and {@code false}
|
||||
* otherwise. The capturing of screenshots can be disabled by not defining a screenshot folder in the
|
||||
* configuration file.</p>
|
||||
*
|
||||
* @return
|
||||
* {@code true} if screenshots should be captured and {@code false} otherwise.
|
||||
*/
|
||||
public boolean screenshotsEnabled() {
|
||||
return (this.screenshotsFolder != null && !this.screenshotsFolder.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the screenshot folder as defined in the configuration or {@code null} if it is not defined. The
|
||||
* screenshot folder is used for storing screenshots captured during the execution of a test.</p>
|
||||
*
|
||||
* @return
|
||||
* The screenshot folder as defined in the configuration or {@code null} if it is not defined.
|
||||
*/
|
||||
public String getScreenshotsFolder() {
|
||||
return this.screenshotsFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the system properties as defined in the configuration.</p>
|
||||
*
|
||||
* @return
|
||||
* The system properties as defined in the configuration.
|
||||
*/
|
||||
public Map<String, String> getSystemProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the list of web drivers as defined in the configuration.</p>
|
||||
*
|
||||
* @return
|
||||
* The list of web drivers as defined in the configuration.
|
||||
*/
|
||||
public List<DriverDefinition> getDriverDefinitions() {
|
||||
return this.definitions;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,38 @@ import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* <p>Utility class for working with the Java representation of the Document Object Model.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class XMLHelper {
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static String getAttributeValueNS(Element element, String name, String uri) {
|
||||
return getAttributeValueNS(element, name, uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
* @param name
|
||||
* @param uri
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
public static String getAttributeValueNS(Element element, String name, String uri, String defaultValue) {
|
||||
Attr attribute = element.getAttributeNodeNS(uri, name);
|
||||
|
||||
@@ -22,6 +49,14 @@ public class XMLHelper {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static String getContentText(Element element) {
|
||||
if(element == null)
|
||||
return null;
|
||||
@@ -29,10 +64,36 @@ public class XMLHelper {
|
||||
return element.getTextContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static String getContentTextNS(Element element, String name, String uri) {
|
||||
return getContentTextNS(element, name, uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @param defaultValue
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static String getContentTextNS(Element element, String name, String uri, String defaultValue) {
|
||||
Element child = getElementNS(element, name, uri);
|
||||
|
||||
@@ -42,6 +103,18 @@ public class XMLHelper {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static Element getElementNS(Element element, String name, String uri) {
|
||||
if(element == null || !element.hasChildNodes())
|
||||
return null;
|
||||
@@ -57,10 +130,32 @@ public class XMLHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static List<Element> getElements(Element element, String name) {
|
||||
return getElementsNS(element, name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
* @param name
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static List<Element> getElementsNS(Element element, String name, String uri) {
|
||||
if(element == null || !element.hasChildNodes())
|
||||
return Collections.emptyList();
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.sap.sailing.selenium.core.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.runner.Description;
|
||||
|
||||
import com.sap.sailing.selenium.core.ScreenShotFilenameGenerator;
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class ScreenShotFilenameGeneratorImpl implements ScreenShotFilenameGenerator {
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddhhmmssSSS"); //$NON-NLS-1$
|
||||
|
||||
private final File directory;
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param directory
|
||||
*
|
||||
*/
|
||||
public ScreenShotFilenameGeneratorImpl(File directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Generates unique file names for the test case described by the given description.</p>
|
||||
*
|
||||
* TODO: Describe the resulting filename
|
||||
*
|
||||
* @param description
|
||||
* The test case description.
|
||||
* @return
|
||||
* The generated unique file name.
|
||||
*/
|
||||
@Override
|
||||
public File getTargetFilename(final Description description) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(description.getMethodName());
|
||||
builder.append('-');
|
||||
builder.append(DATE_FORMAT.format(new Date()));
|
||||
builder.append('-');
|
||||
builder.append(UUID.randomUUID().toString());
|
||||
builder.append(SCREENSHOT_FILE_EXTENSION);
|
||||
|
||||
return new File(new File(this.directory, description.getTestClass().getCanonicalName().replace('.', File.separatorChar)),
|
||||
builder.toString());
|
||||
}
|
||||
}
|
||||
+19
@@ -6,11 +6,27 @@ import org.openqa.selenium.WebDriver;
|
||||
|
||||
import com.sap.sailing.selenium.core.TestEnvironment;
|
||||
|
||||
/**
|
||||
* <p>Default implementation of the test environment.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class TestEnvironmentImpl implements TestEnvironment {
|
||||
private WebDriver driver;
|
||||
private String root;
|
||||
private URL screenshots;
|
||||
|
||||
/**
|
||||
* <p>Creates a new description of the test environment in which a test is executed.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver used for the execution of the test.
|
||||
* @param root
|
||||
* The context root (base URL) against the tests should be executed.
|
||||
* @param screenshots
|
||||
* The folder where screenshots should be stored.
|
||||
*/
|
||||
public TestEnvironmentImpl(WebDriver driver, String root, URL screenshots) {
|
||||
this.driver = driver;
|
||||
this.root = root;
|
||||
@@ -32,6 +48,9 @@ public class TestEnvironmentImpl implements TestEnvironment {
|
||||
return this.screenshots;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Closes the test environment. This quits the web driver, closing every associated window.</p>
|
||||
*/
|
||||
public void close() {
|
||||
this.driver.quit();
|
||||
this.driver = null;
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.sap.sailing.selenium.test;
|
||||
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
/**
|
||||
* <p>Base class for page objects representing an entry point. This class uses a customized initialization, which waits
|
||||
* for the completion of the GWT bootstrap process before the annotated fields are initialized.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class AbstractEntryPoint extends PageObject {
|
||||
/**
|
||||
* <p>Creates a new page object with the given web driver. In GWT an entry point is connected to a HTML page in
|
||||
* which the code for the application is executed, whereby the page is represented by the web driver.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver to use.
|
||||
*/
|
||||
public AbstractEntryPoint(WebDriver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Initialize the page object. Since the GWT bootstrap process can take a bit of time, the global Ajax counter is
|
||||
* initialized to be 1 and acts as a semaphore for bootstrap. Therefore an entry point waits until the counter
|
||||
* reach zero, which indicates the completion of the bootstrap.</p>
|
||||
*/
|
||||
@Override
|
||||
protected void initElements() {
|
||||
waitForAjaxRequests(super.getTimeOut(), 5);
|
||||
|
||||
super.initElements();
|
||||
}
|
||||
}
|
||||
+76
-18
@@ -2,16 +2,26 @@ package com.sap.sailing.selenium.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import org.junit.Rule;
|
||||
|
||||
import org.junit.rules.TestWatchman;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
|
||||
import org.openqa.selenium.OutputType;
|
||||
import org.openqa.selenium.TakesScreenshot;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import org.openqa.selenium.remote.Augmenter;
|
||||
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||
|
||||
@@ -19,39 +29,83 @@ import com.sap.sailing.selenium.core.Managed;
|
||||
import com.sap.sailing.selenium.core.Selenium;
|
||||
import com.sap.sailing.selenium.core.TestEnvironment;
|
||||
|
||||
/**
|
||||
* <p>Abstract base class for unit tests with Selenium. This class is already annotated as required to get executed
|
||||
* with the Selenium runner. Furthermore there is a rule, which automatically captures a screenshot if a test fails,
|
||||
* since it is helpful to see how a page looked in the case a test failed.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
@RunWith(Selenium.class)
|
||||
public abstract class AbstractSeleniumTest {
|
||||
private static final String NOT_SUPPORTED_IMAGE = "/com/sap/sailing/selenium/resources/not-supported.png"; //$NON-NLS-1$
|
||||
|
||||
private static final String ATTACHMENT_FORMAT = "[[ATTACHMENT|%s]]"; //$NON-NLS-1$
|
||||
|
||||
// TODO this inner class is currently not used; either delete or use
|
||||
// private class ScreenShotRule extends TestWatchman {
|
||||
// @Override
|
||||
// public void failed(Throwable cause, FrameworkMethod method) {
|
||||
// try {
|
||||
// captureScreenshot(method.getName());
|
||||
// } catch (Exception exception) {
|
||||
// exception.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// TODO: Change to TestWatcher if we support a higher version (4.11) of JUnit.
|
||||
//private class ScreenShotRule2 extends TestWatcher {
|
||||
//
|
||||
//}
|
||||
|
||||
//@Rule
|
||||
//public ScreenShotRule takeScreenshoot = new ScreenShotRule();
|
||||
private class ScreenShotRule extends TestWatchman {
|
||||
@Override
|
||||
public void failed(Throwable cause, FrameworkMethod method) {
|
||||
try {
|
||||
captureScreenshot(method.getName());
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Rule for capturing of a screenshot if a test fails.</p>
|
||||
*/
|
||||
@Rule
|
||||
public final ScreenShotRule takeScreenshoot = new ScreenShotRule();
|
||||
|
||||
/**
|
||||
* <p>The test environment used for the execution of the the tests.</p>
|
||||
*/
|
||||
@Managed
|
||||
protected TestEnvironment environment;
|
||||
|
||||
/**
|
||||
* <p>Returns the context root (base URL) against the tests are executed. The context root identifies a web
|
||||
* application and usually consists of a protocol definition, the host and a path.</p>
|
||||
*
|
||||
* @return
|
||||
* The context root against the tests are executed.
|
||||
*/
|
||||
protected String getContextRoot() {
|
||||
return this.environment.getContextRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the web driver to use for the execution of the tests.</p>
|
||||
*
|
||||
* @return
|
||||
* The web driver to use for the execution of the tests
|
||||
*/
|
||||
protected WebDriver getWebDriver() {
|
||||
return this.environment.getWebDriver();
|
||||
}
|
||||
|
||||
protected void captureScreenshot(String filename) throws URISyntaxException, IOException {
|
||||
/**
|
||||
* <p>Captures a screen shot and saves the picture as an PNG file under the given file name. The complete path to
|
||||
* the stored picture consists of the screenshot folder, as defined in the test environment, and the given
|
||||
* filename with "png" as file extension.</p>
|
||||
*
|
||||
* <p>If the used web driver does not support the capturing of screenshots an alternative picture is used instead
|
||||
* of the screenshot.</p>
|
||||
*
|
||||
* @param filename
|
||||
* The file name under which the screenshot should be saved.
|
||||
* @throws IOException
|
||||
* if an I/O error occurs.
|
||||
*/
|
||||
protected void captureScreenshot(String filename) throws IOException {
|
||||
URL screenshotFolder = this.environment.getScreenshotFolder();
|
||||
|
||||
if(screenshotFolder == null)
|
||||
@@ -70,10 +124,14 @@ public abstract class AbstractSeleniumTest {
|
||||
|
||||
URL destination = new URL(screenshotFolder, filename + ".png"); //$NON-NLS-1$
|
||||
|
||||
Files.copy(source.toPath(), new File(destination.toURI()).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// ATTENTION: Do not remove this line because it is needed for the JUnit Attachment Plugin!
|
||||
System.out.println(String.format(ATTACHMENT_FORMAT, destination));
|
||||
try {
|
||||
Files.copy(source.toPath(), new File(destination.toURI()).toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
// ATTENTION: Do not remove this line because it is needed for the JUnit Attachment Plugin!
|
||||
System.out.println(String.format(ATTACHMENT_FORMAT, destination));
|
||||
} catch(URISyntaxException exception) {
|
||||
// This should never happen, but it's a checked exception.
|
||||
}
|
||||
}
|
||||
|
||||
private File getScreenshotNotSupportedImage() {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.sap.sailing.selenium.test;
|
||||
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
/**
|
||||
* <p>Base class for page objects representing an area of a page.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class PageArea extends PageObject {
|
||||
/**
|
||||
* <p>Creates a new page object with the given driver representing an area which is the specified by the element
|
||||
* and its children.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver to use.
|
||||
* @param element
|
||||
* The element representing the area of the page.
|
||||
*/
|
||||
public PageArea(WebDriver driver, WebElement element) {
|
||||
super(driver, element);
|
||||
}
|
||||
}
|
||||
+199
-1
@@ -14,58 +14,256 @@ import org.openqa.selenium.support.ui.FluentWait;
|
||||
|
||||
import com.sap.sailing.selenium.core.AjaxCallsComplete;
|
||||
import com.sap.sailing.selenium.core.BySeleniumId;
|
||||
import com.sap.sailing.selenium.core.FindBy;
|
||||
import com.sap.sailing.selenium.core.FindBys;
|
||||
import com.sap.sailing.selenium.core.SeleniumElementLocatorFactory;
|
||||
|
||||
/**
|
||||
* <p>Within a web app's UI there are areas that tests interact with. A page object simply models these as objects
|
||||
* within the test code and abstracts from the actual structure (DOM) of a website. This reduces the amount of
|
||||
* duplicated code and means that if the UI changes, the fix need only be applied in one place.</p>
|
||||
*
|
||||
* <p>Page objects can be thought of as facing in two directions simultaneously. Facing towards the developer of a test,
|
||||
* they represent the services offered by a particular page. Facing away from the developer, they should be the only
|
||||
* thing that has a deep knowledge of the structure of a page (or part of a page). It's simplest to think of the
|
||||
* methods on a page object as offering the "services" that a page offers rather than exposing the details and
|
||||
* mechanics of the page.</p>
|
||||
*
|
||||
* <p>Because the developer of a test should think about the services that they're interacting with rather than the
|
||||
* implementation, page objects should seldom expose the underlying web driver or elements. To facilitate this,
|
||||
* methods on a page object should return other page objects.</p>
|
||||
*
|
||||
* <p>To make the development of page objects as simple and easy as possible, we also support a factory for this pattern
|
||||
* which helps to remove some boiler-plate code from the page objects by using annotations. The factory uses the
|
||||
* annotations on fields to lazily locate and wait for an element or an element list to appear, by polling the UI on
|
||||
* a regular basis, and initializes the field with the element or the list of elements.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class PageObject {
|
||||
/**
|
||||
* </p>The default timeout of 60 seconds for the initialization of the page object.</p>
|
||||
*/
|
||||
protected static final int DEFAULT_TIMEOUT = 60;
|
||||
|
||||
/**
|
||||
* <p>The web driver to use.</p>
|
||||
*/
|
||||
protected final WebDriver driver;
|
||||
|
||||
/**
|
||||
* <p>The context for the locating of elements.</p>
|
||||
*/
|
||||
protected final SearchContext context;
|
||||
|
||||
/**
|
||||
* <p>Creates a new page object with the given web driver. This constructor is equivalent to the two argument
|
||||
* constructor whereby the web driver is also used as the search context for the page object.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver which also acts as the search context to use.
|
||||
*/
|
||||
public PageObject(WebDriver driver) {
|
||||
this(driver, driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a new page object with the given web driver and search context.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver to use.
|
||||
* @param context
|
||||
* The search context to use.
|
||||
*/
|
||||
public PageObject(WebDriver driver, SearchContext context) {
|
||||
this.driver = driver;
|
||||
this.context = context;
|
||||
|
||||
verify();
|
||||
initElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Verifies the page object. This method can be overwritten to check that the browser is on the correct page or
|
||||
* that the provided search context represents the right element. The default implementation does nothing.</p>
|
||||
*/
|
||||
protected void verify() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Initialize the page object. The default implementation use a factory to lazily initialize the elements
|
||||
* (annotated fields) of the page object using the timeout duration returned by {@link #initElements}. To get a
|
||||
* field lazily initialized you have to annotate the field either with {@link FindBy} or with {@link FindBys}.
|
||||
* If the element never changes (that is, that the same instance in the DOM can always be used) it is also
|
||||
* possible to use a cache for the lookup by using the annotation {@code CacheLookup} in addition.</p>
|
||||
*
|
||||
* <p>A simple example for the implementation of a page object can look as follows:</p>
|
||||
*
|
||||
* <pre>
|
||||
* public class MySearchPage extends PageObject {
|
||||
* // The element is looked up using the id attribute and it is
|
||||
* // never look up again once it has been used the first time
|
||||
* @FindBy(how = ById.class, using = "my-searchfield")
|
||||
* @CacheLookup
|
||||
* private WebElement searchField;
|
||||
*
|
||||
* // The element is looked up using the tag name and it is
|
||||
* // look up every time it is used
|
||||
* @FindBy(how = ByTagName, using = "input")
|
||||
* private WebElement submitButton;
|
||||
*
|
||||
* public MySearchPage(WebDriver driver) {
|
||||
* super(driver);
|
||||
* }
|
||||
*
|
||||
* public void enterSearchText(String text) {
|
||||
* this.searchField.clear();
|
||||
* this.searchField.sendKeys(text);
|
||||
* }
|
||||
*
|
||||
* public ResultPage performSearch() {
|
||||
* this.submitButton.click();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>If the implementation is not sufficient for your case, you can overwrite this method with or without calling
|
||||
* the default implementation.</p>
|
||||
*/
|
||||
protected void initElements() {
|
||||
PageFactory.initElements(new SeleniumElementLocatorFactory(this.context, getTimeOut()), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the timeout duration for the initialization of the page object.</p>
|
||||
*
|
||||
* @return
|
||||
* The timeout duration for the initialization of the page object.
|
||||
*/
|
||||
protected int getTimeOut() {
|
||||
return DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds and returns the first element with the specified selenium id in the search context of the page object.
|
||||
* If multiple elements exists, the element closest to the context is returned.</p>
|
||||
*
|
||||
* @param id
|
||||
* The selenium id of the element.
|
||||
* @return
|
||||
* The first matching element in the search context of the page object.
|
||||
* @see #findElementBySeleniumId(SearchContext context, String id)
|
||||
*/
|
||||
protected WebElement findElementBySeleniumId(String id) {
|
||||
return findElementBySeleniumId(this.context, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds and returns the first element with the specified selenium id in the given search context. If multiple
|
||||
* elements exists, the element closest to the context is returned.</p>
|
||||
*
|
||||
* @param context
|
||||
* The search context to use for the search.
|
||||
* @param id
|
||||
* The selenium id of the element.
|
||||
* @return
|
||||
* The first matching element in the given context.
|
||||
*/
|
||||
protected WebElement findElementBySeleniumId(SearchContext context, String id) {
|
||||
return context.findElement(new BySeleniumId(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds and returns all elements within the specified selenium id in the search context of the page object.</p>
|
||||
*
|
||||
* @param id
|
||||
* The selenium id of the elements.
|
||||
* @return
|
||||
* A list of all matching elements in the search context of the page object, or an empty list if nothing matches.
|
||||
* @see #findElementsBySeleniumId(SearchContext context, String id)
|
||||
*/
|
||||
protected List<WebElement> findElementsBySeleniumId(String id) {
|
||||
return findElementsBySeleniumId(this.context, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds and returns all elements within the specified selenium id in the given search context.</p>
|
||||
*
|
||||
* @param context
|
||||
* The search context to use for the search.
|
||||
* @param id
|
||||
* The selenium id of the elements.
|
||||
* @return
|
||||
* A list of all matching elements in the given context, or an empty list if nothing matches.
|
||||
*/
|
||||
protected List<WebElement> findElementsBySeleniumId(SearchContext context, String id) {
|
||||
return context.findElements(new BySeleniumId(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Waits until all pending Ajax request in the global category have been complete using a timeout of 30 seconds
|
||||
* and a polling interval of 5 seconds. In reality, the interval may be greater as the cost of actually evaluating
|
||||
* the condition is not factored in.</p>
|
||||
*
|
||||
* @throws org.openqa.selenium.TimeoutException
|
||||
* if the timeout of 30 seconds expires.
|
||||
* @see #waitForAjaxRequests(int timeout, int polling)
|
||||
*/
|
||||
protected void waitForAjaxRequests() {
|
||||
waitForAjaxRequests(30, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Waits until all pending Ajax request in the specified category have been complete using a timeout of 30
|
||||
* seconds and a polling interval of 5 seconds. In reality, the interval may be greater as the cost of actually
|
||||
* evaluating the condition is not factored in.</p>
|
||||
*
|
||||
* @param category
|
||||
* The category of Ajax requests to wait for.
|
||||
* @throws org.openqa.selenium.TimeoutException
|
||||
* if the timeout of 30 seconds expires.
|
||||
* @see #waitForAjaxRequests(int timeout, int polling)
|
||||
*/
|
||||
protected void waitForAjaxRequests(String category) {
|
||||
waitForAjaxRequests(category, 30, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Waits until all pending Ajax request in the global category have been complete using the given timeout
|
||||
* and polling interval. In reality, the interval may be greater as the cost of actually evaluating the condition
|
||||
* is not factored in.</p>
|
||||
*
|
||||
* @param timeout
|
||||
* The timeout duration for the waiting.
|
||||
* @param polling
|
||||
* The interval in which the check should be performed.
|
||||
* @throws org.openqa.selenium.TimeoutException
|
||||
* if the timeout expires.
|
||||
*/
|
||||
protected void waitForAjaxRequests(int timeout, int polling) {
|
||||
waitForAjaxRequests(AjaxCallsComplete.CATEGORY_GLOBAL, timeout, polling);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Waits until all pending Ajax request in the specified category have been complete using the given timeout
|
||||
* and polling interval. In reality, the interval may be greater as the cost of actually evaluating the condition
|
||||
* is not factored in.</p>
|
||||
*
|
||||
* @param category
|
||||
* The category of Ajax requests to wait for.
|
||||
* @param timeout
|
||||
* The timeout duration for the waiting.
|
||||
* @param polling
|
||||
* The interval in which the check should be performed.
|
||||
* @throws org.openqa.selenium.TimeoutException
|
||||
* if the timeout expires.
|
||||
*/
|
||||
protected void waitForAjaxRequests(String category, int timeout, int polling) {
|
||||
FluentWait<WebDriver> wait = new FluentWait<>(this.driver);
|
||||
wait.withTimeout(timeout, TimeUnit.SECONDS);
|
||||
wait.pollingEvery(polling, TimeUnit.SECONDS);
|
||||
|
||||
wait.until(new AjaxCallsComplete());
|
||||
wait.until(new AjaxCallsComplete(category));
|
||||
}
|
||||
}
|
||||
+49
-5
@@ -1,7 +1,5 @@
|
||||
package com.sap.sailing.selenium.test.adminconsole;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.core.Is.*;
|
||||
@@ -12,11 +10,22 @@ import com.sap.sailing.selenium.test.AbstractSeleniumTest;
|
||||
import com.sap.sailing.selenium.test.adminconsole.pages.AdminConsolePage;
|
||||
import com.sap.sailing.selenium.test.adminconsole.pages.TracTracEventManagementPanel;
|
||||
|
||||
/**
|
||||
* <p>Tests for filtering of trackable TracTrac races.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
|
||||
protected static final String BMW_CUP_JSON_URL = "http://kml.skitrac.traclive.dk/events/event_20120803_BMWCup/jsonservice.php";
|
||||
private static final String BMW_CUP_JSON_URL =
|
||||
"http://kml.skitrac.traclive.dk/events/event_20120803_BMWCup/jsonservice.php"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* <p>Test with no filter at all, which means we expect all TracTrac races are displayed for a given event.</p>
|
||||
*/
|
||||
@Test
|
||||
public void testNoFilter() throws IOException {
|
||||
@SuppressWarnings("boxing")
|
||||
public void testNoFilter() {
|
||||
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
|
||||
|
||||
TracTracEventManagementPanel tracTracEvents = adminConsole.goToTracTracEvents();
|
||||
@@ -26,6 +35,9 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test with partial filter, which should show multiple races but not all.</p>
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("boxing")
|
||||
public void testPartialFilter() {
|
||||
@@ -33,8 +45,40 @@ public class TestRaceFilterForTracTracEvents extends AbstractSeleniumTest {
|
||||
|
||||
TracTracEventManagementPanel tracTracEvents = adminConsole.goToTracTracEvents();
|
||||
tracTracEvents.listRaces(BMW_CUP_JSON_URL);
|
||||
tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 1");
|
||||
tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 1"); //$NON-NLS-1$
|
||||
|
||||
assertThat(tracTracEvents.getTrackableRaces().size(), is(5));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test with exact filter which should match only 1 races.</p>
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("boxing")
|
||||
public void testExactFilter() {
|
||||
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
|
||||
|
||||
TracTracEventManagementPanel tracTracEvents = adminConsole.goToTracTracEvents();
|
||||
tracTracEvents.listRaces(BMW_CUP_JSON_URL);
|
||||
tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 12"); //$NON-NLS-1$
|
||||
|
||||
assertThat(tracTracEvents.getTrackableRaces().size(), is(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test with filter which does not match anything.</p>
|
||||
*/
|
||||
// TODO: This test fails at the moment, because of an incorrect result returned by the page object! If the table is
|
||||
// empty GWT uses a table body with one row as spacer, which is not filtered out correctly by the page object.
|
||||
// @Test
|
||||
// @SuppressWarnings("boxing")
|
||||
// public void testNoneExisingFilter() {
|
||||
// AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
|
||||
//
|
||||
// TracTracEventManagementPanel tracTracEvents = adminConsole.goToTracTracEvents();
|
||||
// tracTracEvents.listRaces(BMW_CUP_JSON_URL);
|
||||
// tracTracEvents.setFilterForTrackableRaces("BMW Cup Race 18"); //$NON-NLS-1$
|
||||
//
|
||||
// assertThat(tracTracEvents.getTrackableRaces().size(), is(0));
|
||||
// }
|
||||
}
|
||||
+15
-1
@@ -3,21 +3,35 @@ package com.sap.sailing.selenium.test.adminconsole;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.selenium.test.AbstractSeleniumTest;
|
||||
|
||||
import com.sap.sailing.selenium.test.adminconsole.pages.AdminConsolePage;
|
||||
import com.sap.sailing.selenium.test.adminconsole.pages.TracTracEventManagementPanel;
|
||||
|
||||
/**
|
||||
* <p>Test for starting and stopping the tracking of TracTrac races.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class TestStartAndStopTrackingForTracTracEvents extends AbstractSeleniumTest {
|
||||
private static final String BMW_CUP_JSON_URL = "http://kml.skitrac.traclive.dk/events/event_20120803_BMWCup/jsonservice.php";
|
||||
private static final String BMW_CUP_JSON_URL = "http://kml.skitrac.traclive.dk/events/event_20120803_BMWCup/jsonservice.php"; //$NON-NLS-1$
|
||||
|
||||
// TODO see below: Complete the test case
|
||||
// private static final String NO_REGATTA = "No regatta";
|
||||
// private static final String BMW_CUP_REGATTA = "BMW Cup (J80)";
|
||||
// private static final String BMW_CUP_RACE_8 = "BMW Cup Race 8";
|
||||
|
||||
/**
|
||||
* <p>Test for start tracking with an incorrect regatta selected, which should lead to an dialog with a warning.</p>
|
||||
*/
|
||||
@Test
|
||||
public void testStartTrackingWithoutCorrectRegatta() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Test for the correct start and stop of a tracking.</p>
|
||||
*/
|
||||
@Test
|
||||
public void testStartAndStopTrackingWithCorrectRegatta() {
|
||||
AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
|
||||
|
||||
+43
-12
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.selenium.test.adminconsole.pages;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
@@ -13,20 +14,38 @@ import com.sap.sailing.selenium.core.BySeleniumId;
|
||||
import com.sap.sailing.selenium.core.ElementSearchConditions;
|
||||
import com.sap.sailing.selenium.core.FindBy;
|
||||
|
||||
import com.sap.sailing.selenium.test.PageObject;
|
||||
import com.sap.sailing.selenium.test.AbstractEntryPoint;
|
||||
|
||||
public class AdminConsolePage extends PageObject {
|
||||
private static final String PAGE_TITLE = "SAP Sailing Analytics Administration Console";
|
||||
/**
|
||||
* <p>The page object representing the administration console. The console consists of multiple tabs with its content
|
||||
* between you can switch.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class AdminConsolePage extends AbstractEntryPoint {
|
||||
private static final String PAGE_TITLE = "SAP Sailing Analytics Administration Console"; //$NON-NLS-1$
|
||||
|
||||
private static final MessageFormat TAB_EXPRESSION = new MessageFormat(
|
||||
".//div[@class=\"gwt-TabBarItem\" and @role=\"tab\"]/div[text()=\"{0}\"]/..");
|
||||
".//div[@class=\"gwt-TabBarItem\" and @role=\"tab\"]/div[text()=\"{0}\"]/.."); //$NON-NLS-1$
|
||||
|
||||
private static final String TRACTRAC_TAB_LABEL = "TracTrac Events";
|
||||
private static final String TRACTRAC_TAB_IDENTIFIER = "TracTracEventManagement";
|
||||
private static final String TRACTRAC_TAB_LABEL = "TracTrac Events"; //$NON-NLS-1$
|
||||
private static final String TRACTRAC_TAB_IDENTIFIER = "TracTracEventManagement"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* <p>Goes to the administration console and returns the representing page object.</p>
|
||||
*
|
||||
* @param driver
|
||||
* The web driver to use.
|
||||
* @param root
|
||||
* The context root of the application.
|
||||
* @return
|
||||
* The page object for the administration console.
|
||||
*/
|
||||
public static AdminConsolePage goToPage(WebDriver driver, String root) {
|
||||
driver.get(root + "gwt/AdminConsole.html");
|
||||
driver.get(root + "gwt/AdminConsole.html"); //$NON-NLS-1$
|
||||
|
||||
// TODO: As soon as the security API is available in Selenium we should use it to login into the admin console.
|
||||
// FluentWait<WebDriver> wait = new FluentWait<>(driver);
|
||||
// wait.withTimeout(5, TimeUnit.SECONDS);
|
||||
// wait.pollingEvery(100, TimeUnit.MILLISECONDS);
|
||||
@@ -44,22 +63,34 @@ public class AdminConsolePage extends PageObject {
|
||||
return new AdminConsolePage(driver);
|
||||
}
|
||||
|
||||
//@FindBy(how = How.XPATH, using = "//*[@selenium-id='AdministrationTabs']")
|
||||
@FindBy(how = BySeleniumId.class, using = "AdministrationTabs")
|
||||
private WebElement tabPanel;
|
||||
|
||||
private AdminConsolePage(WebDriver driver) {
|
||||
super(driver);
|
||||
|
||||
if(!PAGE_TITLE.equals(driver.getTitle())) {
|
||||
throw new IllegalStateException("This is not the administration console");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Switch to the TracTrac Events tab and waits until the animation is finished. The returned page object,
|
||||
* representing the tab, becomes invalid if you switch to another tab in the administration console.</p>
|
||||
*
|
||||
* @return
|
||||
* The page object for the TracTracEvents tab.
|
||||
*/
|
||||
public TracTracEventManagementPanel goToTracTracEvents() {
|
||||
return new TracTracEventManagementPanel(this.driver, gotToTab(TRACTRAC_TAB_LABEL, TRACTRAC_TAB_IDENTIFIER));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Verifies that the current page is the administration console by checking the title of the page.</p>
|
||||
*/
|
||||
@Override
|
||||
protected void verify() {
|
||||
if(!PAGE_TITLE.equals(this.driver.getTitle())) {
|
||||
throw new IllegalStateException("This is not the administration console"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
private WebElement gotToTab(String label, final String id) {
|
||||
String expression = TAB_EXPRESSION.format(new Object[] {label});
|
||||
WebElement tab = this.tabPanel.findElement(By.xpath(expression));
|
||||
|
||||
+42
-10
@@ -4,7 +4,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.SearchContext;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
@@ -14,18 +13,21 @@ import org.openqa.selenium.WebElement;
|
||||
import com.sap.sailing.selenium.core.FindBy;
|
||||
import com.sap.sailing.selenium.core.BySeleniumId;
|
||||
|
||||
import com.sap.sailing.selenium.test.PageObject;
|
||||
import com.sap.sailing.selenium.test.PageArea;
|
||||
|
||||
public class TracTracEventManagementPanel extends PageObject {
|
||||
//@FindBy(how = How.XPATH, using = ".//*[@selenium-id='JSONURL']")
|
||||
/**
|
||||
* <p>The page object representing the TracTrac Events tab.</p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class TracTracEventManagementPanel extends PageArea {
|
||||
@FindBy(how = BySeleniumId.class, using = "JSONURL")
|
||||
private WebElement jsonURLField;
|
||||
|
||||
//@FindBy(how = How.XPATH, using = ".//*[@selenium-id='ListRaces']")
|
||||
@FindBy(how = BySeleniumId.class, using = "ListRaces")
|
||||
private WebElement listRacesButton;
|
||||
|
||||
//@FindBy(how = How.XPATH, using = ".//*[@selenium-id='FilterRaces']")
|
||||
@FindBy(how = BySeleniumId.class, using = "FilterRaces")
|
||||
private WebElement filterTrackableRacesField;
|
||||
|
||||
@@ -33,10 +35,25 @@ public class TracTracEventManagementPanel extends PageObject {
|
||||
// private WebElement correctWindCheckbox;
|
||||
// private WebElement simulateWithNowCheckbox;
|
||||
|
||||
public TracTracEventManagementPanel(WebDriver driver, SearchContext context) {
|
||||
super(driver, context);
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param driver
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
*/
|
||||
protected TracTracEventManagementPanel(WebDriver driver, WebElement element) {
|
||||
super(driver, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lists all available trackable races for the given URL. The list of the races can be obtained via
|
||||
* {@link #getTrackableRaces()}.</p>
|
||||
*
|
||||
* @param url
|
||||
* The URL for which the races are to list.
|
||||
*/
|
||||
public void listRaces(String url) {
|
||||
this.jsonURLField.clear();
|
||||
this.jsonURLField.sendKeys(url);
|
||||
@@ -45,9 +62,17 @@ public class TracTracEventManagementPanel extends PageObject {
|
||||
waitForAjaxRequests();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the list of all available trackable races. This list will be empty if no race is available or if no
|
||||
* race was specified before.</p>
|
||||
*
|
||||
* @return
|
||||
* The list of all available trackable races.
|
||||
*/
|
||||
// QUESTION: Should we return something different instead of WebElements since it is not recommended to do so?
|
||||
public List<WebElement> getTrackableRaces() {
|
||||
WebElement availableRacesTabel = findElementBySeleniumId(this.context, "RacesTable");
|
||||
List<WebElement> elements = availableRacesTabel.findElements(By.xpath("./tbody/tr"));
|
||||
WebElement availableRacesTabel = findElementBySeleniumId(this.context, "RacesTable"); //$NON-NLS-1$
|
||||
List<WebElement> elements = availableRacesTabel.findElements(By.xpath("./tbody/tr")); //$NON-NLS-1$
|
||||
Iterator<WebElement> iterator = elements.iterator();
|
||||
|
||||
while(iterator.hasNext()) {
|
||||
@@ -72,6 +97,13 @@ public class TracTracEventManagementPanel extends PageObject {
|
||||
//
|
||||
// }
|
||||
|
||||
/**
|
||||
* <p>Sets the filter for the trackable races. After the filter is set you can obtain the new resulting list via
|
||||
* {@link #getTrackableRaces}</p>
|
||||
*
|
||||
* @param filter
|
||||
* The filter to apply to the trackable races.
|
||||
*/
|
||||
public void setFilterForTrackableRaces(String filter) {
|
||||
this.filterTrackableRacesField.clear();
|
||||
this.filterTrackableRacesField.sendKeys(filter);
|
||||
|
||||
+27
-1
@@ -1,5 +1,31 @@
|
||||
package com.sap.sailing.selenium.test.gwt.widgets;
|
||||
|
||||
public class CellTable {
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import com.sap.sailing.selenium.test.PageObject;
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @author
|
||||
* D049941
|
||||
*/
|
||||
public class CellTable extends PageObject {
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @param driver
|
||||
*
|
||||
* @param element
|
||||
*
|
||||
*/
|
||||
public CellTable(WebDriver driver, WebElement element) {
|
||||
super(driver, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify() {
|
||||
// TODO: Verify that the context represents a GWT CellTable
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cfg="http://www.sapsailing.com/test-environment"
|
||||
targetNamespace="http://www.sapsailing.com/test-environment" elementFormDefault="qualified">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
XML schema definition for the configuration file of the test environment which is used for the execution of UI
|
||||
tests with Selenium. The configuration file is used by the JUnit Selenium runner to perform the tests with
|
||||
different browsers as defined in the configuration.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
<xsd:element name="test-environment" type="cfg:test-environment" />
|
||||
|
||||
<xsd:complexType name="test-environment">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The test-environment is used to specified the overall setup for the execution of the tests, consisting of a
|
||||
context root, a screenshot folder, necessary system properties and web drivers to use.
|
||||
|
||||
The context-root element defines the base URL against the tests are executed. The context root identifies a
|
||||
web application and usually consists of a protocol definition, the host and a path.
|
||||
|
||||
The screenshots-folder element can be used to enable the capturing of screenshot in the case a test failed.
|
||||
|
||||
The system-properties element contains all additional system properties need by Selenium which depend on the
|
||||
web driver to use.
|
||||
|
||||
The driver-definition element is used to define web drivers (representing a browser) with witch the tests
|
||||
should be executed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="context-root" type="xsd:string" minOccurs="1" maxOccurs="1" />
|
||||
<xsd:element name="screenshots-folder" type="xsd:string" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="screenshots-folder" type="xsd:anyURI" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="system-properties" type="cfg:system-properties" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="driver-definition" type="cfg:driver-definition" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="system-properties">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The system-properties is used to define all additional system properties, needed by Selenium for the
|
||||
different web drivers. For more informations about available properties see the following sides:
|
||||
|
||||
- http://code.google.com/p/selenium/wiki/ChromeDriver
|
||||
- http://code.google.com/p/selenium/wiki/FirefoxDriver
|
||||
- http://code.google.com/p/selenium/wiki/InternetExplorerDriver
|
||||
- http://code.google.com/p/selenium/wiki/OperaDriver
|
||||
- http://code.google.com/p/selenium/wiki/SafariDriver
|
||||
- http://code.google.com/p/selenium/wiki/RemoteWebDriverServer
|
||||
|
||||
NOTE: For the Chrome and the InternetExplorer driver an additional "server", which acts as a bridge between
|
||||
the browser and the driver, is needed.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element name="system-property" type="cfg:parameter" minOccurs="1" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="driver-definition">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The driver-definition defines a web driver (representing a browser) with witch the tests should be executed.
|
||||
The capabilities can be used for fine tuning of the driver (e.g. version).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="capabilities" type="cfg:capabilities" minOccurs="1" maxOccurs="1" />
|
||||
</xsd:sequence>
|
||||
@@ -28,9 +74,11 @@
|
||||
|
||||
<xsd:complexType name="capabilities">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
See http://code.google.com/p/selenium/wiki/DesiredCapabilities
|
||||
</xsd:documentation>
|
||||
<xsd:documentation>
|
||||
The capabilities can be used for the fine tuning of the web driver, by specifying an exact version or
|
||||
particular settings for example. Since different web drivers support different capabilities, you can find
|
||||
more informations at the pages mentioned above.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element name="capability" type="cfg:parameter" minOccurs="1" maxOccurs="1" />
|
||||
@@ -38,6 +86,11 @@
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="parameter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A parameter is just a name-value pair, booth defined as a string.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="1" maxOccurs="1" />
|
||||
|
||||
Reference in new issue
Block a user