Version 5 of WebdriverIO was designed with Page Object Pattern support in mind. By introducing the "elements as first class citizens" principle, it is now possible to build up large test suites using this pattern.
There are no additional packages required to create page objects. It turns out that clean, modern classes provide all necessary features we need:
- inheritance between page objects
- lazy loading of elements
- encapsulation of methods and actions
The goal of using page objects is to abstract any page information away from the actual tests. Ideally, you should store all selectors or specific instructions that are unique for a certain page in a page object, so that you still can run your test after you've completely redesigned your page.
First off, we need a main page object that we call
Page.js. It will contain general selectors or methods which all page objects will inherit from.
We will always
export an instance of a page object, and never create that instance in the test. Since we are writing end-to-end tests, we always consider the page as a stateless construct—just as each HTTP request is a stateless construct.
Sure, the browser can carry session information and therefore can display different pages based on different sessions, but this shouldn't be reflected within a page object. These sorts of state changes should live in your actual tests.
Get -ing Your Selectors#
The first step is to write all important selectors that are required in our
login.page object as getter functions:
Defining selectors in getter functions might look a little weird, but it’s really useful. These functions are evaluated when you access the property, not when you generate the object. With that you always request the element before you do an action on it.
WebdriverIO internally remembers the last result of a command. If you chain an element command with an action command, it finds the element from the previous command and uses the result to execute the action. With that you can remove the selector (first parameter) and the command looks as simple as:
Which is basically the same thing as:
After you've defined the necessary elements and methods for the page, you can start to write the test for it. All you need to do to use the page object is to
require) it. That's it!
Since you exported an already-created instance of the page object, importing it lets you start using it right away.
If you use an assertion framework, your tests can be even more expressive:
From the structural side, it makes sense to separate spec files and page objects into different directories. Additionally you can give each page object the ending:
.page.js. This makes it easy to figure out that you actually require a page object if you execute
const LoginPage = require('../pageobjects/form.page').
This is the basic principle of how to write page objects with WebdriverIO. But you can build up way more complex page object structures than this! For example, you might have specific page objects for modals, or split up a huge page object into different classes (each representing a different part of the overall web page) that inherit from the main page object. The pattern really provides a lot of opportunities to separate page information from your tests, which is important to keep your test suite structured and clear in times where the project and number of tests grows.
You can find this example (and even more page object examples) in the
example folder on GitHub.