-
Notifications
You must be signed in to change notification settings - Fork 3
Finders
The core concept used in redsniff is a Finder, and this represents something similar to a WHERE clause in sql.
It is an expression of something we want to find on the page. They typically have 2 parts - the base part,(usually the tag name), and then further specific restrictions, expressed as a hamcrest Matcher - something that matches a condition against a WebElement, such as having a particular id tag.
The following method returns a simple finder that will look for div elements:
MFinder finder = Finders.div();
The Finders class contains a large list of static methods that return finders - similar to the Matchers class in hamcrest. Here weve used div() which returns a finder for <div> elements.
Calling browser.find with this as an argument will return a collection of all the WebElements whose tag is <div>
Collection<WebElement> divs = browser.find( Finders.div());
Since divs are pretty common, this would typically bring back rather a lot so we can limit the results by being more specific and providing further restrictions. We can do this by appending .that(...) and providing a matcher
Collection<WebElement> redDivs = browser.find( Finders.div().that(WebDriverMatchers.hasCssClass("red")));
The WebDriverMatchers class has a large list of methods that return matchers for WebElement objects - the type used by WebDriver to refer to any element.
hasCssClass(String className)
returns a matcher that will pass if the element it is applied to has the specified class among its class attributes.
We can then tidy up by using import static to remove the class names
import static jhc.redsniff.webdriver.Finders.*; import static jhc.redsniff.webdriver.WebDriverMatchers.*;
Collection<WebElement> redDivs = browser.find( div().that(hasCssClass("red")));
Finders can have as many matchers as you like, appended like this:
div() .that( hasCssClass("red") ) .that( hasText("Some text") ); ...
We can also wrap such a finder with one that finds just one item, such as fourth(...) or only(..) :
fourth( div().that( hasName(...)) ) only( button().that( hasId(...)) )
These carry implicit assertions - here there must be at least 4 divs with that name, and only one button with that id.
Note – some finders have attributes that are so central to them that they are the default attribute, and can be accessed by passing the value to the method itself. e.g.
button(“OK”)
- this is equivalent to
button().that(hasValue(“OK”))
and would match
<input type=”button” value=”OK”/>
and similarly
link(“Click here”)
would match
<a href=”....”>Click here</a>
These are only used in a few cases though.
browser.assertPresenceOf(exactly(3), div() ); browser.assertPresenceOf(atLeast(4), span() );
Sometimes you want to get an element and then make an assertion about some aspect of it, rather than just include that aspect in the list of that(..) restrictions.
WebElement okButton= browser.find( only(button("OK")) ); assertThat(okButton, isEnabled());
This can also be written just as:
browser.assertThatThe( only(button("OK")) , isEnabled());
There are two broad categories of Finder class –
- MFinder (M for Multiple) , which are those that could theoretically find multiple results. e.g.
button()
- SFinder (S for Single) which could only return one.
t.find(button())
returns a Collection (It may be the case that there is only one button on the page, but the query -button() does not know this).
and
t.find(only(button()))
returns just a .
When locating the element you want to interact with requires looking at the hierarchy it sits within, we can express that using the "within..." methods.
MFinders have the following methods
that(..)
which takes a Matcher<WebElement> and restricts the results based on the matcher,
withinA(..)
which takes another MFinder expression within which this finder must exist
e.g.
button().withinA(form())
would only find those buttons that are situated within a <form>..</form>
and
withinThe(...)
which takes an SFinder, again within which this finder must exist
e.g.
button().withinThe( only(form().that(hasName(“orderForm”)))
would only find those buttons situated within the unique “orderForm” form.
SFinders have the following methods
(no that method – restrictions would not be done on an SFinder.)
withinThe(...)
same as above, but will only return one, or no results
only(button()).withinThe(only(form())
"there is only one form, and that form has only one button."
withinEach(...)
takes an MFinder, and in each result of the MFinder, finds the single result there
so whilst
t.find ( first(button() ) ) ;
would return the one button on the page,
t.find( first(button()) .withinEach(form()))
would return a collection, consisting of the first button in each form. I.e it will go through each form on the page and include the first button in each one.
This can be useful for, say, a table such as an email inbox, where each row has a checkbox you want to tick.
t.tick( first(checkbox()).withinEach(row());
For example, there might be several "OK" buttons on the page, with similar name etc, but we want the one within the order form:
browser.clickOn(button("OK").withinA(form().that(hasName("orderForm")));
If anything in the hierarchy isnt found, the error message will reflect that, and try to tell you why:
Could not find form that has name "orderForm" within which to search for {button that: {has value "OK"}} because No element with name "orderForm" found at all
If rather than identify an element based on what outer elements it is in, we may also want to do it by identifying what child elements it has within it. For example, you want the div that surrounds the order form:
browser.find(div().that( hasSubElement( form().that(hasName("orderForm")));
We may also want to do the same action in several related places.
The following will try to tick the second box found in each form - (if any forms it finds don't have that many checkboxes within them it will fail..)
browser.inEach(form()).tick( second( checkbox() ));