Easy E2E Testing with Cypress.io
Javier Villanueva
Reacticon 2018
About me...
Javier Villanueva
@jahvi
http://jahvi.com
javier@medialounge.co.uk
kateandtoms.com
What is testing?
- Increase code quality
- Find bugs early
- Makes changes easier
- Reduces costs
- Many more...
Testing is hard...
- More time writing tests than actual functional code
- Learning new programming languages
- Learning new tooling
- Learning about the right type of test (unit, functional, integration, etc...)
Most tests don’t give us enough confidence
E2E
UNIT
INTEGRATION
Increasing scope
More confidence
Faster
Better isolation
What is Cypress.io?
Fast, easy and reliable testing for anything that runs in a browser
Time Travel
Debuggability
Real time reloads
Automatic waiting
Best Features
beforeEach(() => {
cy.viewport(1440, 900);
cy.visit('https://demo.vuestorefront.io');
cy.get('[data-testid="accountButton"]').click();
});
describe('Login', () => {
it('displays error message on invalid credentials', () => {
cy.get('input[name="email"]').type('test@email.com');
cy.get('input[name="password"]').type('test{enter}');
cy.get('[data-testid="notificationMessage"]').should(
'contain',
'You did not sign in correctly or your account is temporarily disabled'
);
});
it('displays success message on valid credentials', () => {
cy.get('input[name="email"]').type('test@email.co.uk');
cy.get('input[name="password"]').type('Letmein!{enter}');
cy.get('[data-testid="notificationMessage"]').should(
'contain',
'You are logged in'
);
});
});
describe('Quick buy', () => {
it('can add product to cart from category page', () => {
cy.get('.products .item')
.first()
.find('.size .swatch-option')
.first()
.click();
cy.get('.products .item')
.first()
.find('.color .swatch-option')
.first()
.click();
cy.get('.products .item')
.first()
.find('button[type="submit"]')
.click({ force: true });
cy.get('.message-success')
.invoke('text')
.should('match', /You added (.*) to your shopping cart/);
});
});
describe('Quick buy', () => {
it('can add product to cart from category page', () => {
cy.server();
cy.route('POST', '**/checkout/cart/add/**').as('addToCart');
cy.wait('@addToCart');
// Run assertions
});
});
Best Practices
Focus on strong tests
Repeatable
Independent
Use data-* attributes as selectors
<button id="main" class="btn btn-large" data-testid="submitBtn">Submit</button>
cy.get('button').click();
cy.get('.btn.btn-large').click();
cy.get('#main').click();
cy.contains('Submit').click();
cy.get('[data-testid="submitBtn"]').click();
Make A/B Tests toggle-able
beforeEach(() => {
cy.setCookie('theme', 'a');
});
describe('Button', () => {
it('can be clicked', () => {
cy.get('[data-testid="buttonA"]').click();
});
});
beforeEach(() => {
cy.setCookie('theme', 'b');
});
describe('Button', () => {
it('can be clicked', () => {
cy.get('[data-testid="buttonB"]').click();
});
});
Variant A
Variant B
Fake things sparingly
beforeEach(() => {
cy.visit('https://demo.deity.io/customer/account/login');
cy.login('test@email.co.uk', 'testtest123!').then(() =>
cy.visit('https://demo.deity.io/customer/account')
);
});
describe('Login', () => {
it('can log user in', () => {
cy.get('.icon-logout').should('exist');
cy.get('.col-md-9 > h1').should('contain', 'My Dashboard');
});
});
beforeEach(() => {
cy.visit('https://demo.deity.io/customer/account/login');
cy.get('input[name="email"]').type('test@email.co.uk');
cy.get('input[name="password"]').type('testtest123!{enter}');
});
describe('Login', () => {
it('can log user in', () => {
cy.get('.icon-logout').should('exist');
cy.get('.col-md-9 > h1').should('contain', 'My Dashboard');
});
});
Cypress.Commands.add('login', (email, password) => {
return cy.request('POST', 'https://demo.deity.io/api/customer/sign-in', {
email,
password
});
});
Commands
describe('Search', () => {
it('shows empty message when no results', () => {
cy.get('.right-icons > [data-testid="openSearchPanel"]').click();
cy.focused().type('product does not exist');
cy.get('[data-testid="searchPanel"]').should(
'contain',
'No results were found'
);
});
});
describe('Search', () => {
it('shows empty message when no results', () => {
cy.server();
cy.route(
'POST',
'**/api/catalog/vue_storefront_catalog/product/_search?**',
{
took: 0,
timed_out: false,
_shards: { total: 5, successful: 5, skipped: 0, failed: 0 },
hits: { total: 0, max_score: null, hits: [] }
}
);
cy.get('.right-icons > [data-testid="openSearchPanel"]').click();
cy.focused().type('yoga');
cy.get('[data-testid="searchPanel"]').should(
'contain',
'No results were found'
);
});
});
Not everything needs testing
Additional Features
- Parallelisation
- CI / CD Integration
- Screenshot / Video Recordings
- Dashboard Service
- Install Browser Extensions
Limitations
- Limited <iframe> support
- Can't run multiple browsers at the same time
- Can't mock async requests using fetch (workaround available)
- Browser support *
Alternatives
TL;DR
Focus on real tests, tools like Cypress will help make this easier.
Thank you!
Easy E2E Testing with Cypress.io
By Javier Villanueva
Easy E2E Testing with Cypress.io
Presentation for Reacticon 2018
- 1,845