Using Page Objects in Cypress

Using Page Objects in Cypress
Photo by Diana Polekhina / Unsplash


Page Objects is a pattern in test automation that allow the automation engineer to encapsulate the data and methods used to support automation of a page. Typically each page of the application will have an automation class that contains data, methods, and locators needed for automation of that page.

In this post I will take a look at a sample Cypress script that does not use Page Objects, and walk you through the steps of converting to the Page Object pattern.


So why do we want to use Page Objects? Some advantages of this pattern include:

  • separating the implementation complexity of automation from the more business-focused details of the test script steps
  • providing a single location for automation details, so if something like a page locator changes in the application, we only need to change it in one place in the automation code

But enough talking, let's get started. Hopefully we can see these advantage in action!


To make this easy to follow along, I'm going to be starting with the sample test scripts that are included when you install Cypress. Let's do that:

  1. make sure you have node installed
  2. create a new folder for our example and from the command line run: npm init -y
  3. install cypress with: npm install cypress --save-dev
  4. start the test runner with: npx cypress open

After starting the test runner, you'll see a cypress folder structure like this:

Cypress folder structure

In the 1-getting-started folder you'll see todo.spec.js. This is a sample test that comes with Cypress and demonstrates some basic functionality using as the application under test.

To complete the setup, let's just make sure this test runs properly. Run the test from the command line with npx cypress run --spec cypress/integration/1-getting-started/todo.spec.js. If things go as planned, you should see messages that 6 tests ran and something like this towards the end:

Image description

There we go, setup complete!

Planning our page class - round 1

Let's take a closer look at the todo.spec.js file. The beforeEach() hook is not all that interesting, but let's start with that since it's pretty easy to understand.

beforeEach(() => {

Instead of having the cy command and the destination URL in the spec, let's create a page object method to deal with all that.

Creating the page object class

The class we're going to create is just a JavaScript class. Create a new folder in the integration folder and name it something like page-objects. Create a new file names todo-page.js in the page-objects folder.

Add the following to the new class file:

export class TodoPage {

    navigateToHome() {

Update the spec to use the page object

We have a new method named navigateToHome() that we're going to call from our spec file. To make that happen, we just need to make a few updates to the todo.spec.js file.

  1. add the following import:
import { TodoPage } from "../page-objects/todo-page"
  1. instantiate the ToDoPage object prior to the beforeEach hook:
const todoPage = new TodoPage()
  1. update the beforeEach hook to call the page object method:
  beforeEach(() => {

Those are the changes. Run the test again and make sure it's still green:
npx cypress run --spec cypress/integration/1-getting-started/todo.spec.js

And that's about it! We've created a page object class, and used it from our test spec.

Planning our page class - round 2

There are other examples in the spec that might benefit more from Page Objects. Take a look at the first line of the first test:

cy.get('.todo-list li').should('have.length', 2)

This step is verifying that our app has two to-do items. The Cypress interaction detail is right there in the spec, with a lot of locator and cypress detail that could be moved to the page object.

Also what about other tests that follow this pattern? The locator for the to-do list items will be scattered throughout our tests. What if that locator changes in the future? Yup, lots of updates and all the work and risk that comes with that.

I want to move all that detail into one place, the page object. Our spec will become more clear, with a statement such as:


See how clear that is? It's so clear I am not even going to explain it. After our first example, it's probably pretty clear how to do this in the page object class. Let's take a look.

Updating the page object class

Since we already had the implementation details in the spec, we can just copy/paste/tweak that for our new Page Object method:

    validateTodoCount(expectedLength) {
            .get('.todo-list li')
            .should('have.length', expectedLength)

So with those changes to the spec and page object class, run the test again and make sure you are still seeing green tests.

Cool, right? And the great thing? Any test that needs to validate the to-do count in the future can just use this method without worrying about locators or Cypress.


So with those basic examples I hope you can see the value of encapsulating your automation code into Page Objects. Feel free to experiment on your own and convert ALL the tests in that that spec to use Page Objects. It's a great exercise.

I should also point out that since our Page Object class does not have any instance data, we could have just used functions instead of the class. To learn about this option and much more, I highly recommend you look at Cypress courses Introduction to Cypress by Gil Tayer and Advanced Cypress by Filip Hric, available for free from Test Automation University. Much of my initial Cypress interest and learning came from these 2 courses.

And finally, I would be remiss if I did not mention the article by the Gleb Bahmutov Stop using Page Objects and Start using App Actions where he explores some alternatives to Page Objects.

That's all for now, thanks!