Selenium is a library which allows you to automate user interactions with a web browser. Patient-Selenium is a wrapper built around Selenium which allows for lazily located elements, waiting for elements to be in a desired state, customizable automatic retries of located elements, and customizable page object definition. These features together make it useful when trying to write automated tests for a site that heavily use asynchronous JavaScript. It is intended to be used by a single thread (the same one that is driving the implementing Selenium web driver) and the classes are not intended to be shared across multiple threads. The library has a minimal set of methods exposed and is intended to be sub-classed by users. This allows the end users to expose which methods make sense for the type of browser and/or tests they are writing while still gaining the previously mentioned benefits. See the Patience library for more details regarding the specifics of how waiting occurs.
Via maven:
<dependency>
<groupId>com.redfin</groupId>
<artifactId>patient-selenium</artifactId>
<version>3.1.4</version>
</dependency>
The AbstractPatientElement
type is the wrapper of a Selenium WebElement
. It has a cached reference to a Selenium
element to allow for fewer element location requests which is useful in the case of a networked
Selenium grid architecture. Multiple actions on the same Element
will continue to use the same web element
reference object. In the case of an exception being thrown by the web element, the cache will be cleared, the Selenium
web element will be relocated, and the action attempted again (up to a customizable number of times). Interacting with
the selenium element is done via two methods accept(Consumer)
and apply(Function)
, sub classes that want to
expose additional methods can simply call into those methods directly and the cache manipulation will be done for them.
If the actual backing Selenium WebElement
needs to be looked up and it can't find a matching one,
a NoSuchElementException
will be thrown. There are also two methods on the Element
type that will not
check the cache but trigger another Selenium element location call. These are isPresent
and isAbsent(Duration)
which will return true or false depending on the page state and will never cause a NoSuchElementException
.
if (element.isPresent()) {
element.accept(WebElement::click);
}
The AbstractPatientElementLocator
type is the base class for a type used on page objects and whose job is to
create concrete AbstractPatientElement
instances. It allows for customization of the PatientWait
and Duration
timeout
to be used by the Element
instance when it is locating Selenium WebElement
objects. The get(int)
method creates individual element instances that are lazily initialized (e.g. the actual Selenium element hasn't
been looked up yet). The getAll()
method returns a list of element instances that are eagerly initialized (e.g. the
actual Selenium element list has been located). Note that for an individual element, it will know the particular index
on the page it is for. So, if there are 4 <div>
tags on the page, and you have an element instance that has
the index of 1
attached to it, it will correspond to the second matching Selenium element that is found (0 based
indexing). If there is a filter that is applied to located elements to further refine if they are matches in addition
to the base Selenium element location (e.g. you only want elements that return true for WebElement::isDisplayed
, etc)
then the located elements will only keep applying the filter until the desired n-th matching element is found. This
also reduces network calls in a remote web driver situation if the Selenium By
locator used returns a lot of matches
and you only need the first one that isDisplayed()
.
elementFactory.get(1).accept(WebElement::click);
elementFactory.getAll().size();
The AbstractPageObjectInitializer
type is the base class for an instance that will be used to initialize fields
of an already created instance of a specific type. It is intended to be used to create concrete
AbstractPatientElementLocator
instances on a page object based upon custom annotations on the fields,
but that is entirely customizable. Any field on the page that is also a non-null PageObject
will be initialized
as well. Any null field will be handed to a method implemented by the subclass of the page object initializer.
If that method returns a non-empty optional, then the value in the optional will be set to that field on the page
being initialized. Before being initialized each recursive page object will also be handed to a pre processing
method callback implementing by the concrete subclass.