/*******************************************************************************
* 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.FindingExpectations.presenceOf;
import static jhc.redsniff.internal.finders.AsActionableFinder.each;
import static jhc.redsniff.internal.finders.OnlyFinder.only;
import static jhc.redsniff.internal.matchers.ContainsIgnoringWhitespace.containsIgnoringWhitespace;
import static jhc.redsniff.matchers.numerical.NumericalMatchers.atLeast;
import static jhc.redsniff.webdriver.Finders.$;
import static jhc.redsniff.webdriver.Finders.attribute;
import static jhc.redsniff.webdriver.Finders.div;
import static jhc.redsniff.webdriver.Finders.dropDown;
import static jhc.redsniff.webdriver.Finders.dropDownOption;
import static jhc.redsniff.webdriver.Finders.elementFound;
import static jhc.redsniff.webdriver.Finders.fifth;
import static jhc.redsniff.webdriver.Finders.first;
import static jhc.redsniff.webdriver.Finders.form;
import static jhc.redsniff.webdriver.Finders.fourth;
import static jhc.redsniff.webdriver.Finders.last;
import static jhc.redsniff.webdriver.Finders.second;
import static jhc.redsniff.webdriver.Finders.span;
import static jhc.redsniff.webdriver.Finders.tableElement;
import static jhc.redsniff.webdriver.Finders.textElement;
import static jhc.redsniff.webdriver.Finders.textbox;
import static jhc.redsniff.webdriver.Finders.third;
import static jhc.redsniff.webdriver.WebDriverMatchers.hasCssClass;
import static jhc.redsniff.webdriver.WebDriverMatchers.hasId;
import static jhc.redsniff.webdriver.WebDriverMatchers.hasName;
import static jhc.redsniff.webdriver.WebDriverMatchers.hasSubElement;
import static jhc.redsniff.webdriver.WebDriverMatchers.hasText;
import static jhc.redsniff.webdriver.WebDriverMatchers.isDisabled;
import static jhc.redsniff.webdriver.WebDriverMatchers.isEnabled;
import static jhc.redsniff.webdriver.WebDriverMatchers.isString;
import static jhc.redsniff.webdriver.matchers.ColorMatcher.hasColor;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.openqa.selenium.lift.match.NumericalMatchers.exactly;
import java.util.Collection;
import java.util.Iterator;
import jhc.redsniff.RedsniffTestBase;
import jhc.redsniff.core.Finder;
import jhc.redsniff.core.FindingExpectations;
import jhc.redsniff.core.MFinder;
import jhc.redsniff.internal.core.CollectionOf;
import jhc.redsniff.internal.core.Item;
import jhc.redsniff.internal.core.Quantity;
import jhc.redsniff.internal.expectations.ExpectationCheckResult;
import jhc.redsniff.internal.expectations.ExpectationChecker;
import jhc.redsniff.internal.expectations.FindingExpectation;
import jhc.redsniff.internal.finders.BaseMFinder;
import jhc.redsniff.internal.matchers.CheckAndDiagnoseTogetherMatcher;
import jhc.redsniff.internal.matchers.ContainsIgnoringWhitespace;
import jhc.redsniff.matchers.numerical.NumericalMatchers;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsCollectionWithSize;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class BasicFindersTests extends RedsniffTestBase {
@Test public void
confirmsPresenceOfElementWhenPresent(){
t.assertPresenceOf(dropDown().that(hasName("a_drop-down")));
}
@Test public void
describesReasonNotFoundWhenNoBaseItemAtAll(){
expectError("Expected: a div that has name a string starting with \"X\"\n" +
"but:\n" +
"Not found - No div found at all");
t.assertPresenceOf(div().that(hasName(startsWith("X"))));
t.assertPresenceOf($("div.myClass"));
}
@Test public void
describesReasonNotFoundWhenNoBaseItemAtAllOptimizing(){
expectError("Expected: a div that has id \"XXX\"\n" +
"but:\n" +
"Not found - No div found at all");
t.assertPresenceOf(div().that(hasId("XXX")));
}
@Ignore("TODO")
@Test public void
failFastOnWaiting(){
expectError("Expected: a div that has id \"XXX\"\n" +
"but:\n" +
"Not found - No div found at all");
t.waitFor(div().that(hasId("XXX")));
}
@Test public void
describesReasonWhenSoleMatcherNotMatchedOneCandidate(){
expectError(
"Expected: a input that has name \"search\"\n"+
"but:\n" +
"Not found - \n" +
"<input> (name:Search) - not matched because name was:\n"+
" '[S]earch'\n"+
"instead of:\n" +
" '[s]earch'\n");
t.assertPresenceOf(elementFound(By.tagName("input"))
.that(hasName("search")));
}
@Test public void
describesReasonWhenLastMatcherNotMatched(){
expectError(
"Expected: a form that: {has name \"theForm\" and has css class \"NotClass\"}\n" +
"but:\n" +
"Not found - \n" +
"<form> (class:form) (name:theForm) (id:frmId) OPTION A OPTION 1 OPTION 2 OPTION 3 - not matched because class was:\n" +
" '[f]o[rm]'\n" +
"instead of:\n" +
" '[N]o[tClass]'");
t.assertPresenceOf(form()
.that(hasName("theForm"))
.that(hasCssClass("NotClass")));
}
@Test public void
describesAllNearestMatchesAndReasonsWhereSeveralCandidates(){
expectError(
"Expected: a drop-down that has name \"a_nonexistent-drop-down\"\n" +
"but:\n" +
"Not found - nearest matches:\n");
expectError(
"<select> (name:a_drop-down) OPTION A - not matched because name was:\n" +
" 'a_[]drop-down'\n" +
"instead of:\n" +
" 'a_[nonexistent-]drop-down'");
expectError(
"<select> (name:a_drop-down_with_3_options) OPTION 1 OPTION 2 OPTION 3 - not matched because name was:\n" +
" 'a_[]drop-down[_with_3_options]'\n"+
"instead of:\n" +
" 'a_[nonexistent-]drop-down[]'");
expectError(
"<select> (name:a_drop-down) OPTION AA - not matched because name was:\n" +
" 'a_[]drop-down'\n"+
"instead of:\n" +
" 'a_[nonexistent-]drop-down'");
expectError(
"<select> (name:another_drop-down_with_3_options) OPTION 10 OPTION 20 OPTION 30 - not matched because name was:\n" +
" 'a[nother_drop-down_with_3_options]'\n"+
"instead of:\n" +
" 'a[_nonexistent-drop-down]'");
t.assertPresenceOf(dropDown().that(hasName("a_nonexistent-drop-down")));
}
@Test public void
describesNearestMatchWhereNotFoundSeveralMatchers(){
expectError(
"Expected: a drop-down that: {has name \"a_nonexistent-drop-down\" and has css class \"nope\"}\n" +
"but:\n" +
"Not found - but searching for drop-down that has name \"a_nonexistent-drop-down\"\n"+
"nearest matches:\n");
expectError(
"<select> (name:a_drop-down) OPTION A - not matched because name was:\n" +
" 'a_[]drop-down'\n" +
"instead of:\n" +
" 'a_[nonexistent-]drop-down'");
expectError(
"<select> (name:a_drop-down_with_3_options) OPTION 1 OPTION 2 OPTION 3 - not matched because name was:\n" +
" 'a_[]drop-down[_with_3_options]'\n"+
"instead of:\n" +
" 'a_[nonexistent-]drop-down[]'");
expectError(
"<select> (name:a_drop-down) OPTION AA - not matched because name was:\n" +
" 'a_[]drop-down'\n"+
"instead of:\n" +
" 'a_[nonexistent-]drop-down'");
expectError(
"<select> (name:another_drop-down_with_3_options) OPTION 10 OPTION 20 OPTION 30 - not matched because name was:\n" +
" 'a[nother_drop-down_with_3_options]'\n"+
"instead of:\n" +
" 'a[_nonexistent-drop-down]'");
t.assertPresenceOf(dropDown().that(hasName("a_nonexistent-drop-down")).that(hasCssClass("nope")));
}
//Absence
@Test public void
confirmsAbsenceOfElementWhenNotPresent(){
t.assertAbsenceOf(dropDown().that(hasName("a_nonexistent-drop-down")));
}
@Test public void
complainsWhenPresentIfAssertedAbsent() {
expectError(
"Expected: not to find any form that has name \"theForm\"\n"
+ "but:\n"
+ "did find:\n"
+ "[ <form> (class:form) (name:theForm) (id:frmId) OPTION A OPTION 1 OPTION 2 OPTION 3 ]");
t.assertAbsenceOf(form().that(hasName("theForm")));
}
//Quantity
@Test public void
checksQuantityFound(){
t.assertPresenceOf(exactly(2), form().that(hasCssClass("form")));
}
@Test public void
checksMinQuantityFound(){
t.assertPresenceOf(atLeast(1), form().that(hasCssClass("form")));
}
@Test public void
describesActualQuantityFound(){
expectError( "Expected: there to be <3> of form that has css class \"form\"\n" +
"but:\n" +
"found <2> such elements");
t.assertPresenceOf(exactly(3), form().that(hasCssClass("form")));
}
//Ordinals
@Test public void
confirmsPresenceOfSecondItem(){
t.assertPresenceOf(second(dropDown().that(hasName("a_drop-down"))));
}
@Test public void
complainsNotEnoughForThirdItem(){
expectError("Expected: a 3rd drop-down that has name \"a_drop-down\"\n" +
"but:\n" +
"only found <2> such elements");
t.assertPresenceOf(third(dropDown().that(hasName("a_drop-down"))));
}
@Test public void
findsNthItemWhenEnoughPresent(){
//html has 4 spans that has numbered text
t.assertThatThe(first(span().that(hasCssClass("span_class"))),
hasText("span1Text"));
t.assertThatThe(third(span().that(hasCssClass("span_class"))),
hasText("span3Text"));
t.assertThatThe(last(span().that(hasCssClass("span_class"))),
hasText("span4Text"));
t.clickOn(fourth(span().that(hasCssClass("span_class"))));
}
@Test public void
complainsNotEnoughIfAskedForNthItemGreaterThanNumberFound(){
//html has 4 spans that has numbered text - asking for 5th
expectError(
"Expected: a 5th span that has css class \"span_class\" to click on\n"+
"but:\n"+
"only found <4> such elements\n"+
"[ <span> (class:span_class) span1Text ], [ <span> (class:span_class) span2Text ], [ <span> (class:span_class) span3Text ], [ <span> (class:span_class) span4Text ]");
t.clickOn(fifth(span().that(hasCssClass("span_class"))));
}
//using find() to obtain the WebElement(s)
@Test public void
findReturnsElementsAsCollectionOfWebElementsforMFinder(){
Collection<WebElement> dropDowns = t.find(dropDown().that(hasName("a_drop-down")));
assertThat(dropDowns, hasSize(2));
Iterator<WebElement> iterator = dropDowns.iterator();
WebElement dropDown1 = iterator.next();
WebElement dropDown2 = iterator.next();
assertEquals("select", dropDown1.getTagName());
assertEquals("select", dropDown2.getTagName());
assertEquals("OPTION A", dropDown1.getText());
assertEquals("OPTION AA", dropDown2.getText());
}
@Test public void
findReturnsSingleElementForOrdinalSFinder(){
WebElement dropDown1 = t.find(first(dropDown().that(hasName("a_drop-down"))));
assertEquals("select", dropDown1.getTagName());
assertEquals("OPTION A", dropDown1.getText());
}
@Test public void
findReturnsSingleElementForOnlySFinder(){
WebElement dropDown1 = t.find(only(dropDown().that(hasName("another_drop-down_with_3_options"))));
assertEquals("select", dropDown1.getTagName());
assertEquals("OPTION 10 OPTION 20 OPTION 30", dropDown1.getText());
}
@Test public void
findDescribesErrorWhenNotFound(){
expectError(
"Expected: a unique drop-down that has name \"a_drop-down\"\n" +
"but:\n" +
"Was not unique: drop-down that has name \"a_drop-down\"\n" +
"found <2> such elements\n" +
"[ <select> (name:a_drop-down) OPTION A ], [ <select> (name:a_drop-down) OPTION AA ]");
WebElement dropDown = t.find(only(dropDown().that(hasName("a_drop-down"))));
}
@Test public void
assertsThatTheChecksSomeMatcherMatchesFindings(){
t.assertThatThe(first(dropDown().that(hasName("a_drop-down"))), isEnabled());
}
@Test public void
assertThatTheDescribesMismatchUsingDescriptionOfFinder(){
expectError("Expected: 1st drop-down that has name \"a_drop-down\" to match is <disabled>\n"+
"but:\n" +
"element was <enabled>");
t.assertThatThe(first(dropDown().that(hasName("a_drop-down"))), isDisabled());
}
//Subelement
@Test public void
assertsPresenceOfSubElement(){
t.assertPresenceOf(dropDown().that(hasSubElement(dropDownOption("OPTION A"))));
}
@Test public void
assertPresenceOfSubElementsContainedWithinParent(){
t.assertPresenceOf(exactly(1),
dropDown()
.that(hasSubElement(dropDownOption("OPTION 2"))));
}
@Test public void
findersAreImmutableByThat(){
MFinder<WebElement, SearchContext> div = div();
assertHasDescription(div, "div");
MFinder<WebElement, SearchContext> divNamedBob = div.that(hasName("bob"));
assertHasDescription(div, "div");
assertHasDescription(divNamedBob, "div that has name \"bob\"");
assertNotSame(div, divNamedBob);
}
@Test public void
usesUnoptimizedToDescribeMismatch() {
expectError(
"Expected: a textbox that has name \"search\"\n"+
"but:\n" +
"Not found - \n" +
"<input> (name:Search) - not matched because name was:\n"+//TODO take out space
" '[S]earch'\n"+
"instead of:\n" +
" '[s]earch'\n");
t.find(textbox().that(hasName("search")));
}
//Within
@Test public void
confirmsElementWithinAnotherElement(){
t.assertPresenceOf(dropDown().that(hasName("a_drop-down")).withinA(form().that(hasName("theForm"))));
}
@Test public void
confirmsAbsenceOfElementWithinAnotherElement(){
t.assertAbsenceOf(dropDown().that(hasName("a_drop-down_with_3_options")).withinA(form().that(hasName("theOtherForm"))));
}
@Test public void
confirmsNumberOfElementsWithinAnotherElementTakingScopeIntoAccount(){
//there are 2 altogether but only one that's within "theForm"
t.assertPresenceOf(exactly(2), dropDown().that(hasName("a_drop-down")));
t.assertPresenceOf(exactly(1), dropDown().that(hasName("a_drop-down"))
.withinA(form().that(hasName("theForm"))));
}
@Test public void
checksRenderedColourOfElement(){
t.assertPresenceOf(span().that(hasColor("#000")));
}
@Test public void
complainsOfElementFoundWhenAbsenceOfElementWithinAnotherElementAsserted(){
expectError("Expected: not to find any drop-down that has name \"a_drop-down_with_3_options\", within a form that has name \"theForm\"\n" +
"but:");
expectError("" +
"did find:\n" +
"[ <select> (name:a_drop-down_with_3_options) OPTION 1 OPTION 2 OPTION 3 ]");
t.assertAbsenceOf(dropDown().that(hasName("a_drop-down_with_3_options"))
.withinA(form().that(hasName("theForm"))));
}
@Test public void
complainsWhenOuterElementNotFound(){
expectError("Expected: a drop-down that has name \"a_drop-down\", " +
"within a form that has css class \"nonExistentForm\"\n" +
"but:\n" +
"Not found - form that has css class \"nonExistentForm\"\n" +
"within which to search for {drop-down that has name \"a_drop-down\"}");
t.find(dropDown().that(hasName("a_drop-down"))
.withinA(form().that(hasCssClass("nonExistentForm"))));
}
@Test public void
complainsWhenInnerElementNotFound(){
expectError("Expected: a drop-down that has name \"a_nonexistent_drop-down\", within a form that has css class \"form\"\n" +
"but:\n");
expectError(
" within: <form> (class:form) (name:theOtherForm) (id:frmId) OPTION AA OPTION 10 OPTION 20 OPTION 30\n" +
" Not found - nearest matches:\n" +
"<select> (name:a_drop-down) OPTION AA - not matched because name was:\n" +
" 'a_[]drop-down'\n" +
"instead of:\n" +
" 'a_[nonexistent_]drop-down'");
t.find(
dropDown().that(hasName("a_nonexistent_drop-down"))
.withinA(form().that(hasCssClass("form"))));
}
@Test public void
assertsSeveralLevelsDeepByWrappingOrChainingWithSameErrorMessage() {
String errorMessageWithChaining="";
String errorMessageWithWrapping="";
try {
t.assertPresenceOf(dropDownOption("OPTION X")
.withinA(
dropDown()
.withinA(
form()
.withinA(
div()
)
)
));
}catch (AssertionError e){
errorMessageWithWrapping = e.getMessage();
}
try {
t.assertPresenceOf(dropDownOption("OPTION X")
.withinA(
dropDown()
)
.withinA(
form()
)
.withinA(
div()
));
}catch (AssertionError e){
errorMessageWithChaining = e.getMessage();
}
assertThat(errorMessageWithWrapping, isString(errorMessageWithChaining));
assertThat(errorMessageWithWrapping, isString("" +
"Expected: a drop-down option that has text \"OPTION X\" within:\n" +
" a drop-down within:\n" +
" a form, within a div\n" +
"but:\n" +
"Not found - div\n" +
"within which to search for {form}\n" +
"because\n" +
"No div found at all"));
}
@Test public void
whenExceptionThrownByMatcherUsesExceptionMessageInMismatch(){
expectError(
"Expected: a span that <matcherDescription>\n" +
"but:\n" +
"Not found - nearest matches:\n" +
"could not check whether {<matcherDescription>} because exception was thrown: <ExampleExceptionMessage>");
t.assertPresenceOf(span().that(matcherThrowingException()));
}
private Matcher<? super WebElement> matcherThrowingException() {
return new CheckAndDiagnoseTogetherMatcher<WebElement>() {
@Override
public void describeTo(Description description) {
description.appendText("<matcherDescription>");
}
@Override
protected boolean matchesSafely(WebElement item,
Description mismatchDescription) {
throw new RuntimeException("<ExampleExceptionMessage>");
}
};
}
@Test public void
testTextElement(){
t.assertPresenceOf(textElement());
}
@Test public void
testPartialCssClass(){
t.assertThe(presenceOf(tableElement().that(hasCssClass("classA"))));
t.assertThe(presenceOf(tableElement().that(hasCssClass("classB"))));
}
@Test public void
testsCssClassSubstrings(){
WebElement element = t.findThe(tableElement().that(hasCssClass("classAAA")));
assertThat(element, hasCssClass("classAAA"));
assertThat(element, not(hasCssClass("classA")));
}
@Test public void
testsSwappedMatcherStillUsed(){
t.assertThe(presenceOf(exactly(1),tableElement().that(hasCssClass("classA"))));
}
@Test public void
fetchesSingleAttributeUsingTransformingFinder(){
String id = t.find(attribute("id").within(only(span().that(hasCssClass("classC")))));
assertThat(id, equalTo("123"));
}
@Test public void
fetchesMultipleAttributeUsingTransformingFinder(){
Collection<String> id = t.find(attribute("id").withinEach(span().that(hasCssClass("classD"))));
assertThat(id, contains("s1","s2","s3","s4"));
}
@Test public void
complainsAttributeMissingUsingTransformingFinder(){
thrown.expectMessage(allOf(containsIgnoringWhitespace("Expected: a attribute 'idx', within a unique span that has css class \"classC\""), ContainsIgnoringWhitespace.containsIgnoringWhitespace("no attribute 'idx'")));
t.find(attribute("idx").within(only(span().that(hasCssClass("classC")))));
}
@Test public void
complainsAttributeMissingUsingAttributesFinder(){
expectError("Expected: a attribute 'idx' within each span that has css class \"classD\"");
expectError("Not found - \n" +
"within: {<span> (class:classD) (id:s1) span1Text}\n" +
"\tno attribute 'idx'");
t.find(attribute("idx").withinEach(span().that(hasCssClass("classD"))));
}
@Test public void
clicksOnEachItem(){
expectError("Expected: a span that has text \"wibble\" to click on each one found");
t.clickOn(each(span().that(hasText("wibble"))));
}
@Test
public void shouldUseOptimizationOnlyForFailFastDirect() throws Exception {
assertUsesOptimization(optimizeOnlyFinder());
}
@Test
public void shouldUseOptimizationOnlyForFailFastOrdinal() throws Exception {
assertUsesOptimization(second(optimizeOnlyFinder()));
}
@Test
public void shouldUseOptimizationOnlyForFailFastOnly() throws Exception {
assertUsesOptimization(only(optimizeOnlyFinder()));
}
@Test
public void shouldUseOptimizationOnlyForFailFastWithinOther() throws Exception {
assertUsesOptimization(optimizeOnlyFinder().withinA(span().that(hasCssClass("classC"))));
}
@Test
public void shouldUseOptimizationOnlyForFailFastOtherWithinThis() throws Exception {
assertUsesOptimization((span().that(hasCssClass("classC")).withinA(optimizeOnlyFinder())));
}
@Test
public void shouldUseOptimizationOnlyForFailFastWhenModifiedWithThat() throws Exception {
assertUsesOptimization(optimizeOnlyFinder().that(hasCssClass("classC")));
}
@Test
@Ignore("unsure how to test automatedly")
public void waitForOptimizeOnly() throws Exception {
t.waitFor(optimizeOnlyFinder());
}
private <Q extends Quantity<WebElement>> void assertUsesOptimization(Finder<WebElement,Q,SearchContext> optimizeOnlyFinder){
ExpectationChecker<SearchContext> checker = checkerForPage();
checker.resultOfCheckingFailFast(presenceOf(optimizeOnlyFinder));
}
private ExpectationChecker<SearchContext> checkerForPage() {
return ExpectationChecker.checkerFor((SearchContext)t.getDriver());
}
private static OptimizeOnlyAllowedFinder optimizeOnlyFinder() {
//initialized without optimization - it should get optimized by callers
return new OptimizeOnlyAllowedFinder(false, div().that(hasId("XXX")));
}
private static class OptimizeOnlyAllowedFinder extends BaseMFinder<WebElement, SearchContext>{
private final boolean optimized;
private final MFinder<WebElement, SearchContext> innerFinder;
public OptimizeOnlyAllowedFinder(boolean optimized,
MFinder<WebElement, SearchContext> innerFinder) {
this.optimized = optimized;
this.innerFinder = innerFinder;
}
@Override
public MFinder<WebElement, SearchContext> asOptimizedFinder() {
return new OptimizeOnlyAllowedFinder(true, innerFinder);
}
@Override
public void describeTo(Description description) {
description.appendText("Optimize only: " + optimized + ": ").appendDescriptionOf(innerFinder);
}
@Override
public CollectionOf<WebElement> findFrom(SearchContext context,
Description notFoundDescription) {
if(!optimized)
throw new AssertionError("Called without optimization");
return innerFinder.findFrom(context, notFoundDescription);
}
}
}