/*******************************************************************************
* Copyright 2014 JHC Systems Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package jhc.redsniff.webdriver;
import static jhc.redsniff.core.Describer.asString;
import static jhc.redsniff.core.Describer.newDescription;
import static jhc.redsniff.core.FindingExpectations.expectationOf;
import static jhc.redsniff.core.FindingExpectations.expectationOfSome;
import static jhc.redsniff.internal.expectations.ExpectationChecker.checkerFor;
import java.io.File;
import java.util.Collection;
import jhc.redsniff.core.MFinder;
import jhc.redsniff.internal.core.CollectionOf;
import jhc.redsniff.internal.expectations.ExpectationCheckResult;
import jhc.redsniff.internal.predicates.NoItems;
import jhc.redsniff.webdriver.activity.Activity;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Duration;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
/**
// The ones using finders and matchers still needed somewhere though
* Extra Canned {@link ExpectedCondition}s which are generally useful within webdriver tests
*/
public final class ExtraExpectedConditions {
private ExtraExpectedConditions(){}
/**
* An expectation for checking that an element is present on the DOM of a
* page and satisfies the supplied {@link Matcher}
* @param locator
* @param matcher
* @return
*/
public static ExpectedCondition<Boolean> elementsLocated(final By locator,final Matcher<Iterable<WebElement>> matcher){
return new ExpectedCondition<Boolean>(){
public Boolean apply(WebDriver input) {
return matcher.matches(ExpectedConditions.presenceOfAllElementsLocatedBy(locator));
}
@Override
public String toString() {
return new StringDescription()
.appendText("Element located by " + locator + " is ")
.appendDescriptionOf(matcher).toString();
}
};
}
/**
* An expectation for checking that an element, known to be present on the DOM
* of a page, is visible. Visibility means that the element is not only
* displayed but also has a height and width that is greater than 0.
*
* @param element the WebElement
* @return the (same) WebElement once it is visible
*/
public static ExpectedCondition<WebElement> visibilityOf(final WebElement element) {
return new ExpectedCondition<WebElement>() {
public String toString() {
return "visibility of " + asString(element);
}
@Override
public WebElement apply(WebDriver input) {
return ExpectedConditions.visibilityOf(element).apply(input);
}
};
}
/**
* Wait until an element is no longer attached to the DOM. The version in {@link ExpectedConditions} is unstable if the element's toString calls any method on the element
*
* @param element The element to wait for.
* @return false is the element is still attached to the DOM, true
* otherwise.
*/
public static ExpectedCondition<Boolean> stalenessOf(final WebElement element) {
final String elementDescription;//need to get the element description first to avoid causing a stale element exception in the description - when it becomes stale too fast
try{
elementDescription= asString(element);
}catch(StaleElementReferenceException e){
return TRUE_CONDITION;
}
return new ExpectedCondition<Boolean>() {
@Override
public String toString() {
return "Element to become 'stale':" + elementDescription;
}
@Override
public Boolean apply(WebDriver input) {
return ExpectedConditions.stalenessOf(element).apply(input);
}
};
}
/**
* Wait until an element is no longer attached to the DOM.
*
* @param element The element to wait for.
* @return false is the element is still attached to the DOM, true
* otherwise.
//TODO - add descriptions of elements expected to be stale
*/
public static ExpectedCondition<Boolean> stalenessOfAnyOf(final Collection<WebElement> elements) {
return new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver input) {
for(WebElement element:elements)
if(ExpectedConditions.stalenessOf(element).apply(input))
return true;
return false;
}
@Override
public String toString() {
return "Staleness of any of a list of elements (description TBA)";
}
};
}
public static ExpectedCondition<Boolean> visibilityOfAny(final MFinder<WebElement,SearchContext> finder){
return new ExpectedCondition<Boolean>(){
@Override
public Boolean apply(WebDriver driver) {
Collection<WebElement> foundElements = checkerFor((SearchContext) driver).thatWhichIsFoundBy(expectationOfSome(finder));
//TODO - wrap in a that() matcher so don't need the exception check here -
//although care needed for the invisibility version - does stale mean can assume invisible? not sure
if(foundElements.isEmpty())
return false;
else {
for(WebElement element:foundElements){
try{
if(element.isDisplayed())
return true;
}catch(StaleElementReferenceException e){ // skip - implies not displayed
}
}
return false;
}
}
@Override
public String toString() {
return newDescription().appendDescriptionOf(finder).appendText(" to be visible").toString();
}
};
}
public static ExpectedCondition<Boolean> invisibilityOf(final MFinder<WebElement, SearchContext> finder){
return new ExpectedCondition<Boolean>(){
@Override
public Boolean apply(WebDriver driver) {
Collection<WebElement> foundElements = checkerFor((SearchContext) driver).thatWhichIsFoundBy(expectationOfSome(finder));
if(foundElements.isEmpty())
return true;
else {
for(WebElement element:foundElements){
try{
if(element.isDisplayed())
return false;
}catch(StaleElementReferenceException e){} // if stale this implies element is not displayed so we can skip over it
}
return true;
}
}
@Override
public String toString() {
return newDescription().appendDescriptionOf(finder).appendText(" to be invisible or not present").toString();
}
};
}
/**
*
* @deprecated - use FindingExpectations#presenceOf
* @param finder
* @return
*/
public static ExpectedCondition<Collection<WebElement>> presenceOf(final MFinder<WebElement, SearchContext> finder){
return new ExpectedCondition<Collection<WebElement>>(){
@Override
public String toString() {
return new StringDescription().appendDescriptionOf(finder).toString();
}
@Override
public Collection<WebElement> apply(WebDriver driver) {
ExpectationCheckResult<WebElement, CollectionOf<WebElement>> result = checkerFor((SearchContext) driver).resultOfChecking(expectationOfSome(finder));
if(result.meetsExpectation())
return result.foundQuantity().get();
else
return null;
}
};
}
@Deprecated
/**
* @deprecated - use FindingExpectations#presenceOf
* @param finder
* @return
*/
public static ExpectedCondition<WebElement> presenceOfA(final MFinder<WebElement,SearchContext> finder){
return new ExpectedCondition<WebElement>(){
@Override
public String toString() {
return new StringDescription().appendDescriptionOf(finder).toString();
}
@Override
public WebElement apply(WebDriver driver) {
ExpectationCheckResult<WebElement, CollectionOf<WebElement>> result = checkerFor((SearchContext) driver).resultOfChecking(expectationOfSome(finder));
if(result.meetsExpectation())
return result.foundQuantity().iterator().next();
else
return null;
}
};
}
public static ExpectedCondition<File> fileToHaveDownloaded(final File file) {
return new ExpectedCondition<File>() {
@Override
public File apply(WebDriver driver) {
if(file.exists())
return file;
else
return null;
}
};
}
public static ExpectedCondition<Boolean> absenceOf(final MFinder<WebElement,SearchContext> finder){
return new ExpectedCondition<Boolean>(){
@Override
public String toString() {
return new StringDescription().appendText("absence of ").appendDescriptionOf(finder).toString();
}
@Override
public Boolean apply(WebDriver driver) {
try {
return checkerFor((SearchContext) driver).isMet(expectationOf(NoItems.<WebElement>noElements(), finder));
}catch(StaleElementReferenceException e){
return false;
}
}
};
}
public static ExpectedCondition<Boolean> done(final Activity activity){
return new DelayingExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver driver) {
return !activity.busyOn(driver);
}
@Override
public String toString() {
return activity.toString() + " to be done";
}
@Override
public Duration initialDelay() {
return activity.initialDelay();
}
};
}
private static final ExpectedCondition<Boolean> TRUE_CONDITION= new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver input) {
return true;
}
};
}