Scenario:
- Your web site is deployed on several countries. The behaviour of the page you want to test (e.g. sign up) is mostly the same across countries, however some business rules change per country.
- You are using the PageObjects pattern to encapsulate the details of each page. You want to avoid duplicated code.
Our goal was to create a SignUpTemplatePage.java
with the page behaviour that was common across all countries. Then one SignUp<Country>Page.java
per country, with the specifics of each country, while inheriting the common behaviour from the template page.
It wasn’t straightforward to achieve it with JavaScript and Cypress’ async behaviour. Thanks to the help of a friendly developer, we did it:
The test
describe("Sign Up page", () => {
it("creates account when fields are valid", function() {
const userDataObject = {
email: "...",
password: "..."
}
// the E2E test doesn't care about implementation details
const page = getSignupAgencyUserPage()
page.visit() // env var will determine which country to test
page.fillForm(userDataObject) // this is different per country
page.submitForm() // this is common
page.shouldDisplayAccountCreated() // this is common
})
})
The switch (returns the country PageObject)
import { ConfigHelper } from "../utils/configHelper"
import { signupUserPageDE } from "./uk/SignupUserPageDE"
import { signupUserPageFR } from "./uk/SignupUserPageFR"
import { signupUserPagePT } from "./pt/SignupUserPagePT"
import { signupUserPageUK } from "./uk/SignupUserPageUK"
const implementations = {
de: signupUserPageDE,
fr: signupUserPageFR,
pt: signupUserPagePT,
uk: signupUserPageUK
}
export function getSignupUserPage() {
const countryCode = ConfigHelper.getCountryCode() // reads an env var
const page = implementations[countryCode]
if (!page)
throw new Error(`There's no PageObject implementation for the current site: ${countryCode}`)
return page
}
The template (contains what is common)
import { ConfigHelper } from "../utils/configHelper";
// Selectors
const btnSubmit = "#registerSubmit";
export class SignupUserPageTemplate {
constructor() {
// even though we will never instantiate this class
}
// Actions
visit() {
cy.visit(routes.account.signUp);
}
submitForm() {
cy.get(btnSubmit).click();
}
// Assertions
shouldDisplayAccountCreated() {
cy.url().should("include", routes.account.signUpSuccess);
}
}
export const signupAgencyUserPageTemplate = new SignupAgencyUserPageTemplate();
The country page (contains what is different)
import { SignupUserPageTemplate } from "../SignupUserPageTemplate";
// Selectors
const errorInputMessage = "p .errorbox";
const fieldEmail = `input[name="register[email]"]`;
const fieldPhone = `input[name="register[default_phone]"]`;
export class SignupUserPagePT extends SignupUserPageTemplate {
constructor() {
super();
}
// Actions
fillForm(account) {
cy.get(fieldEmail).type(account.email);
cy.get(fieldPhone).type(account.phone);
}
// Assertions
shouldDisplayRequiredFieldsMessage() {
const requiredFieldsLength = 2;
cy.get(errorInputMessage).should("have.length", requiredFieldsLength);
}
}
export const signupUserPagePT = new SignupUserPagePT();