This repo is a test/demonstration of three patterns and one anti-pattern for using Page-Objects (reference needed) or Page-Object-like testing patterns in Cypress.
Disclaimer: I am a JavaScript developer not an experienced e2e tester and new to Cypress myself.. so take it for what it is.
Pattern A is an anti-pattern. When navigating from page to page within a single test flow, new page instances are created from page classes each time. This breaks the Cypress' internal async queuing and necessitates excessive use of .then
s and/or callbacks.
Do not do this!
import BasePage from './BasePageClass';
import QueryPage from './NavigationMenuClass';
export default class MainPage extends BasePage {
constructor() {
super();
this.mainElement = 'body > .banner';
}
verifyElements() {
return cy.get(this.mainElement).find('.container h1').should('be.visible').then(() => {
return this.navMenu.verifyElements();
});
}
switchToQueryingPage() {
return this.navMenu.switchToQueryingPage().then(() => {
cy.log('==> Finding Query Page');
const queryPage = new QueryPage();
return queryPage.verifyElements()
.then(() => queryPage);
});
}
};
import MainPage from '../support/pageObjects-A/MainPageClass';
describe('Page Object Pattern - A', () => {
context('Querying', () => {
let mainPage;
beforeEach(() => {
return cy.visit('https://example.cypress.io/')
.get('body > .banner h1').should('be.visible').then(() => {
mainPage = new MainPage();
return mainPage.verifyElements();
});
});
it('cy.get() - query DOM elements', () => {
mainPage.switchToQueryingPage();
});
});
});
In Pattern B we keep the page class definitions, which may be useful for inheritance, but each page class module exports a single page object instance of the class. These instances can be used throughout the testing and within other classes.
Do this if you must have page classes and inheritance.
import { BasePage } from './BasePageClass'
import { navMenu } from './NavigationMenuClass';
import { queryPage } from './QueryPageClass';
export class MainPage extends BasePage {
constructor() {
super();
this.mainElement = 'body > .banner';
}
verifyElements() {
super.verifyElements();
cy.get(this.mainElement).find('.container h1').should('be.visible');
}
switchToQueryingPage() {
navMenu.switchToQueryingPage();
queryPage.verifyElements();
}
};
export const mainPage = new MainPage();
import { mainPage } from '../support/pageObjects-B/MainPageClass';
describe('Page Object Pattern - B', () => {
context('Querying', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/')
.get('body > .banner h1').should('be.visible');
mainPage.verifyElements();
});
it('cy.get() - query DOM elements', () => {
mainPage.switchToQueryingPage();
});
});
});
In Pattern C we eliminate classes entirely. Instead each page module creates and exports single page object instance. These instances are used in the same way as pattern B.
Do this if you don't need page classes and inheritance.
import { navMenu } from './navigationMenu';
import { queryPage } from './queryPage';
const bannerElement = 'body > .banner';
export const mainPage = {
verifyElements() {
cy.get(bannerElement).find('.container h1').should('be.visible');
navMenu.verifyElements();
},
switchToQueryingPage() {
navMenu.switchToQueryingPage();
queryPage.verifyElements();
}
}
import { mainPage } from '../support/pageObjects-C/mainPage';
describe('Page Object Pattern - C', () => {
context('Querying', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/')
.get('body > .banner h1').should('be.visible');
mainPage.verifyElements();
});
it('cy.get() - query DOM elements', () => {
mainPage.switchToQueryingPage();
});
});
});
In Pattern D we eliminate the objects themselves. Instead each page module creates and exports public functions. These public function can be imported and used in an almost identical way the as pattern B and C.
Do this if you don't need page objects.
import * as navMenu from './navigationMenu';
import * as queryPage from './queryPage';
const bannerElement = 'body > .banner';
export function verifyElements() {
cy.get(bannerElement).find('.container h1').should('be.visible');
navMenu.verifyElements();
}
export function switchToQueryingPage() {
navMenu.switchToQueryingPage();
queryPage.verifyElements();
}
import * as mainPage from '../support/pageObjects-D/mainPage';
describe('Page "Object" Pattern - D', () => {
context('Querying', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/')
.get('body > .banner h1').should('be.visible');
mainPage.verifyElements();
});
it('cy.get() - query DOM elements', () => {
mainPage.switchToQueryingPage();
});
});
});
In Pattern D we (will) show how to emulate page object pattern using cypress commands. To be completed later, contributions welcome.