Simple modern page models for Selenium
- What is a page model?
- Basics: Site
- Basics: Page
- Basics: Finder
- Design principles
- Page components
- Page actions
- Handling multiple windows
- Failure exceptions
A page model is a class that models how a person interacts with a specific page in the UI. The public methods of a page model represent the tasks that users perform on the page -- what they see and what they say to get things done and to understand the results. The private methods of a page model implement the specific interactions with page content needed to perform these tasks.
The page model pattern helps to keep UI tests more resilient to changes in the UI. Because tests use only the public interface to pages, they are isolated from details of page construction. Page layout and content can be revised and reorganized with requiring any changes to test scenarios or test assertions.
Page models are ultimately implemented using the Selenium WebDriver. For details, see the WebDriver API Javadoc.
Use the Site
class to model the overall context for testing a specific Web app. This
context includes not only information about the app itself (for example, the base
URI for app pages) but also information about the testing context (for example the
WebDriver
instance used to access the UI).
All Page
objects must be created in the context of a specific Site
. That's why Page
is a generic type parameterized by the type of its
associated Site
.
A Site
can be configured to reflect the latencies that occur when interacting with the app. For example, the maximum app wait
time can be configured to reflect the maximum timeout to wait for this particular
app to update its page elements. In addition, the driver latency factor can be
configured to reflect additional latencies incurred when using a particular remote WebDriver
.
The Site
class is typically used as a base class for classes that model additional application-specific
context. ResourceSite
implements a special-purpose Site
that provides Web
pages defined by resource files.
The Site
class defines two basic behaviors for tests.
Method | What it does |
---|---|
enter |
Associates the Site with a WebDriver instance |
exit |
Terminates the WebDriver associated with the Site |
The Page
class is the foundation for all page models. This abstract base class offers many different forms
for constructors that model the various ways that a new page can appear in the UI.
- By direct navigation to specific URL
- By a request from a prior page (called the "parent" page)
- By a request to open a new browser window
A Page
is always associated with a Site
instance and inherits many of its properties. For example, Page.getDriver()
returns the
WebDriver
used by the Site
for this page. Similarly, by default, Page.getMaxAppWait()
returns the maximum app wait time defined for
its Site
. (But note that you can replace this default with a different maximum wait time for any specific Page
instance.)
The Page
class defines the basic methods used by a page model to:
- search for page elements, using a
Finder
object, - handle pages in multiple windows, and
- find and perform the page actions represented by page elements.
A Finder
object defines how to search for a specific page element. Fundamentally,
a page element must be identified by matching it to some
By
condition. (For example, By.cssSelector
identifies
elements using a CSS selector expression.) But a Finder
contains many other optional parameters that may be needed to reliably guide the
search.
- The top-level element within which the search is performed
- The maximum time to wait before giving up the search
- How often to check for the presence of matching element(s)
- Any additional condition that matching element(s) must satisfy
The Finder
class defines the following basic search methods.
Method | What it does |
---|---|
findElement |
Returns a specific element that is assumed to exist |
findElements |
Returns a list of all matching elements |
findOptionalElement |
Returns an Optional<WebElement> that may or may not be present |
findVisibleElement |
Equivalent to when( PageUtils.isVisible).findElement |
awaitNoElements |
Returns successfully when no matching elements can be found |
Finder
also provides a convenient "fluent" interface for defining the search context, using sensible defaults for unspecified parameters. For example:
List<WebElement> hasDataDefined =
startingAt( someDivElement)
.waitingFor( 30, SECONDS)
.checkingEvery( 200, MILLISECONDS)
.when( PageUtils.hasAttribute( "data-defined"))
.whenStableFor( 1, SECONDS)
.findElements( By.cssSelector( ".someClass"));
Page models seldom create Finder
instances directly. Instead, it's simpler to use the Page
methods that provide
the same interfaces for defining and executing an element search.
-
Less is more. Crescent is a very minimal extension of the basic
WebDriver
API. It makes no rules about the internal structure of a page model. It does not try to model individual interaction techniques, like menus or various<input>
types. (Although thePageUtils
class offers many useful helper methods for that sort of thing.) Less framework means more freedom for you to choose the right way to model your UI. -
BYO WebDriver. Crescent has no support for creating a
WebDriver
and connecting it to a browser. Many are the ways in which this can be done, depending on your test environment, your preferred tools for configuration, and so on. You are free to choose the best way to handle this part of your test setup. -
When a page is (re)visited, create a new
Page
. Because this is a good practice for anyWebDriver
program, thePage
class assumes that you will create a newPage
instance when the corresponding page becomes available for interaction in the UI. Therefore, every page model constructor leads to the basicPage
constructor, which callsinitPage()
. By default, this callsvisit()
, which performs the following steps.-
Synchronizes the state of the
Page
with the state of the UI. Depending on the constructor arguments, this can entail actively navigating to a specific URL, passively querying the current location of the browser, or switching theWebDriver
to a specified browser window. -
Calls
visited()
. By default, this method does nothing. But you can override it to perform any page-specific actions needed after arriving at a new instance of this page.
-
-
Error states are exceptions. Users make mistakes. So good UIs help them recover by showing an "error state" -- for example, by displaying an error message. And good tests verify that these error states appear as expected. But how? Tests use the public page model interface, which models the user's tasks. But checking for errors is not the user's job! That job belongs to the page model implementation. When an error state is detected (expected or not!), the page model should throw an exception. Crescent provides several basic exception types that are handy for reporting the details of an error state.
For many Web apps, all individual pages share many common graphic design forms or interaction techniques. You can use the
Component
class to model common UI elements that are shared by multiple page
models. A Component
always belongs to a specific Page
instance. That's why Component
is a generic type parameterized by the type of
its associated Page
.
Some page elements implement actions that produce a certain result in the UI. For example, clicking a button can cause a different page to
appear. It's not unusual for a UI to present multiple elements that all implement the same action. The PageAction
class offers a general
way to model this kind of interaction.
PageAction
is a generic type that is parameterized by the type of the source page and the type of the result produced by the action.
There are two basic PageAction
types, each of which are abstract base classes you can use to model app-specific actions.
-
ElementAction
: An action triggered by an interaction with a single element -
NewWindowAction
: An action that produces content in a new browser window
The Page
class defines the following methods for handling page actions.
Method | What it does |
---|---|
getAction |
Returns a specific type of PageAction that is implemented by a specific optional page element |
perform |
Given an optional PageAction expected to be shown, either performs the action or reports a failure. |
Although it's common for Web apps to display content in multiple browser windows, it's tricky to handle that using the basic WebDriver
API. But the WindowProducer
class makes things a bit simpler.
WindowProducer
is an abstract base class for an action that opens a new window. The basic WindowProducer.open()
handles the bookkeeping
needed to discover and return the WindowHandle
for the expected new window. Together with a specific WindowProducer
, you can use the
NewWindowAction
class to model not only the interaction that produces the new window, but also the type of content now
available for interaction.
Given a specific WindowHandle
argument for a new Page
, the basic visit()
method will automatically switch the focus of the
WebDriver
to the specified window. Afterwards, your test must be responsible for keeping track of which Page
instances are modeling
content for different windows. But, for any Page
instance, when you call close()
, it will automatically switch the WebDriver
focus
back to the window for the getParent()
page.
Because error states should cause exceptions, Crescent provides several basic exception types that are handy for reporting the details of an error state.
Exception | Purpose |
---|---|
PageException |
Base class for all error states reported by a Page |
ElementMissingException |
Reports a failure when searching for a specific page element |
InvalidFormException |
Reports a FieldFailure for each invalid value entered into a form |
InvalidStateException |
Reports a general error state in a Page |
RequestException |
Reports a failure in a system request made from a Page |
WindowException |
Reports a failure to create a new browser window |