Combining Cypress, Typescript and Page Object concepts into a simple easy to read end-2-end test suite.
export class PageObject {
constructor(anything: any) {
}
wrap = (anything: any) => this
}
export class LoginPageObject extends PageObject{
clickSignIn = () => this.wrap(cy.get('[data-cy="signin"]').click())
enterEmail = (email: string) => this.wrap(cy.get('[data-cy="email"]').type(email))
enterPassword = (password: string) => this.wrap(cy.get('[data-cy="password"]').type(password))
shouldSeeAuthenticationFailedAlert = () => this.wrap(cy.get('[data-cy="authentication-failed"]').should('be.visible'))
shouldSeeDashboard = () => this.wrap(cy.get('[data-cy="dashboard"]').should('be.visible'))
}
describe('Login', function () {
it('should fail login with message', () => {
goToLogin()
.enterEmail("user@example.com")
.enterPassword("not-right")
.clickSignIn()
.shouldSeeAuthenticationFailedAlert()
});
it('should login', () => {
goToLogin()
.enterEmail("user@example.com")
.enterPassword("real-passw0rd")
.clickSignIn()
.shouldSeeDashboard()
});
});
Cypress Style Chainable Objects
Using this->wrap
in LoginPageObject
creates a chainable object (Fluent Interface) keeping the tests concise and specific
Extending the cypress chainable object would require deep cloning of the cypress objects leading to unwanted complexity. So instead we have opted to encapsulate all of the cypress calls in the PageObjects including the contriversal decision to include the assertions there.
Typescript
Instead of returning this using this->wrap
we can return another page object. This makes sense for thing like navigation or button events that will change the context.
export class NavigationPageObject extends PageObject {
clickNewDocument = () => new DocumentPageObject(cy.get('[data-cy="newDocument"]').click())
clickDashboard = () => new DashboardPageObject(cy.get('[data-cy="dashboard"]').click())
}
export class DocumentPageObject extends PageObject {
clickCreate = () => this.wrap(cy.get('[data-cy="create"]').click())
enterTitle = (title: string) => this.wrap(cy.get('[data-cy="title"]').type(title))
}
it('should Save', () => {
navigationPageObject
.clickNewDocument()
.enterTitle("Test Document")
.clickCreate()
});
Page Object
The goal of the Page Object pattern is to handle all the programming interactions with the web page and make the simple to read and write test against.
In this simple example, all the things a person can do or interact with on the login page are included in the Page Object. Exposing a self documenting API.
Hints
Use live templates of snippets for common page object methods
I use :
cyclick:
$METHOD$ = () => this.wrap(cy.get('[data-cy="$END$"]').click())
cytype:
$METHOD$ = ($VAR$: string) => this.wrap(cy.get('[data-cy="$END$"]').type($VAR$))
Page Objects don’t need to be a whole page, they can represent any logical components in your application