Skip to main content

Using the Page Object Model in Webdriver.IO

Author by Scott Kauffman

Using the Page Object Model in automation is a great best practice when automating pretty much any software or web-based project.

Conceptually, the idea is that you want to have one reference for each of your object selectors or locators in one convenient place. As any automation engineer can attest, one of the biggest maintenance headaches we have to deal with is that software is in constant flux. So let’s say the selector that you so carefully set up a month ago, or a week ago or even a few days ago (!) is no longer working.

Why? Because the code was refactored to rename those selectors you were relying on, the structure of the page was updated, or for any number of other completely legitimate or not-so-legitimate reasons.

Granted, if you only have one automated test script that is accessing that now-broken selector, it would be no big deal to simply update your reference to that page object, fix your script, and continue on your merry way.

But what if that reference is to the site search function, for example, and you have five scripts that depend on that selector? What if your site is really large, and you have fifteen or twenty scripts that rely on it?

Without the Page Object Model, you would need to go into each of those scripts and make an update to each one to get your scripts working again. Sounds terrible, doesn’t it?

This is where the Page Object Model comes in. In your project, you simply create a class with “getter” methods that return text identifiers for the page objects you’re using in your script. Very often you would create a base page class with some functions common to all your pages, then extend that class for all of your page objects.

I am currently using with Typescript, so if we wanted to create a selector for a ‘new order’ button that appears on the site home page, a code example would look like this:



use strict’;


class HomePage extends Page {


get newOrderButton() {

return ‘button=New Order’;




export default new HomePage();


Why is this a good thing?


  • It instantly gives a natural organization to your selectors. Wondering where the selector is for the login password field? In the login page object, of course!

  • You only have to fix one line of code. Broken checkout button? Fix it in your cart page object, and it will be fixed for any scripts that rely on the checkout button.

Once you have created your first page object, you then just load it up with all the selectors you may need to identify elements on your site, so that your automation scripts can reliably interact with them.

To use your selectors, you would then just have a step definition file or files that reference your new selector using the dot operator (just a fancy way of saying that if your page object is for your home page, it would typically be called HomePage, and if you have a selector for a new order button, that might be called newOrderButton.  To reference that selector in your step file, you would reference it using $(HomePage.newOrderButton) ). I am using cucumber and asynchronous calls, so my step definition would look something like this:


Then(‘I click to create a new order on the home page’, async function() {

const newOrderButton = await $(HomePage.newOrderButton);

await newOrderButton.waitForClickable();



In the example above, if the UX folks decide that they want to change the name of the new order button to be ‘Submit Order’ instead of ‘New Order’, any scripts that use the Gherkin statement above (“I click to create a new order on the home page”) would be broken, since they would still be searching for a button with the text ‘New Order’.

If we didn’t have the Page Object Model, we may have hard-coded the lines into each our scripts any time we wanted to click on this button, like so:


const newOrderButton = await $(button=New Order);


That means we would have to hunt through our code, and anywhere we tested clicking on the New Order button, we would have to update our code in each and every script to this:


const newOrderButton = await $(button=Submit Order);


As you can see, this approach is time consuming and error prone.

However, since we implemented the Page Object Model, we update the selector in one place - our HomePage object – and any script that uses it is automatically using the shiny new selector:


use strict’;


class HomePage extends Page {


get newOrderButton() {

return ‘button=Submit Order’;




export default new HomePage();



There are lots of posts and tutorials on how to set your project up with the Page Object Model, but hopefully this will give you a good start.