Advanced tips for testing with Cypress

Alberto Basalo
5 min readJun 23, 2023

--

Testing web applications with Cypress is easy and could be a pleasure. Following a few guides can accelerate your development and increase your confidence in your code.

๐ŸŽ’ Prerequisites.

Basic knowledge of Cypress syntax and the foundations of testing. If you need some basement, read my other introductory articles.

๐Ÿ—‚๏ธ Use folders and code names

I prefer having more small tests than a few big ones and putting each of them in different files. Yes, you will end with dozens of files.

Group your tests in a folder to organize and find them easily. Also, consider giving a name that includes an issue or feature identification.

e2e/
โ”œโ”€ 01-home-page/
โ”‚ โ”œโ”€ 01_01-header.cy.ts
โ”‚ โ”œโ”€ 01_02-footer.cy.ts
โ”‚ โ”œโ”€ 01_03-show-data.cy.ts
โ”œโ”€ 02-login/
โ”‚ โ”œโ”€ 02_01-can-login.cy.ts
โ”‚ โ”œโ”€ 02_02-can-logout.cy.ts

This way, you can call them easily from the command line

# run home page tests
cypress run --spec '**/01-*.cy.ts'
# test feature 01_02
cypress run --spec '**/01_02.*.cy.ts'

๐Ÿ‘ฎ๐Ÿผ Intercept your API requests to isolate the front

Yes, I know that Cypres is for end-to-end testing, and end-to-end means to really End. But.

These kinds of tests are:

  • Hard to prepare (you will need a database and a server to access it).
  • Slowly to execute (because it needs to travel between processes and maybe machines or networks).
  • Hard to clean up (any side effect like inserted or deleted info must be restored)
  • Prone to unexpected errors. (Errors that may happen and produce a false fail on your test)

In web apps made with client-side frameworks, like Angular, it is common to call a remote API to send and receive data. The good news is that Cypress gives you an easy-to-use technique that avoids any call to the server; it is the interception mechanism.

// simulate getting data
cy.intercept("GET", 'http://api.acme.com/things', {
fixture: "things",
});
// simulate a login returning a token
cy.intercept("POST", 'http://api.acme.com/login', {
statusCode: 201,
fixture: "token",
});
// simulate deleting a resource
cy.intercept("DELETE", 'http://api.acme.com/things/666', {
statusCode: 204,
body: {}
});
// No data was harmed during those tests...

๐Ÿท๏ธ Tag with an Alias all your essential stuff

Alias is how Cypress allows you to create variables that reference execution contexts. They are helpful to:

  • Wait for their executions.
  • Reuse at any place of your code.
  • Scream out loud as feedback during interactive execution.
cy.get('header #company').as('companyName');
// later on your code...
cy.get('@companyName');
cy.intercept("POST", API_URL, {
statusCode: 201,
fixture: "token",
}).as("postLogin");
// wait for the interception
cy.wait('@postLogin');

๐Ÿ“ฆ Use fixtures for inputs and expected results

Input data and expected results are sometimes too big to be defined on your code files. In such cases, it is preferable to load them from files.

But you donโ€™t have to reinvent the wheel. Only place your files below the fixtures folders, and you are done.

// Simplest uses the content 
// Of a file called: cypress\fixtures\my-data.json
cy.intercept('', { fixture: 'my-data'} );
// If you need to process or use the data,
// You can load the content async
cy.fixture("my-data").then((myData) => {
console.log('Do things with', myData);
});

๐ŸŒฒ Prefer Cypress over CSS and JQuery

Testing web applications requires you to know and write a lot of web queries to access the content inside the DOM.

You can leverage all your CSS selectors knowledge, and having a JQuery background will help you. But you should know that Cypress offers you ways to make any selection using simple chainable commands.

Doing so lets you debug your selections one command at a time. The interactive feedback will let you track and discover the actual DOM and help you write the correct selection to get what you want.

const LIST_CONTENT = 'main[name="list-content"]';
cy.get(`${LIST_CONTENT}`).as("listContent");
cy.get("@listContent").find("div").first().children('[name="title"]')

You can mix and match this technique with an alias, predefined constants, and other support tools.

๐Ÿ—๏ธ Create your own Commands

If you find yourself repeating the same code, Cypress has you covered. Creating Commands is easy to do and even easier to use.

Cypress.Commands.add("force404", (endPoint: string) => {
cy.intercept("GET",
`http://api.acme.com/${endPoint}`,
{ statusCode: 404 })
.as("get401");
});
declare global {
namespace Cypress {
interface Chainable {
force401(endPoint: string): Chainable<void>;
}
}
}

After this, you can use it anywhere as if it was a core Cypress method.

describe("Given a user calling a NotFound resource", () => {
it("Then should show an error message", () => {
cy.force404('something_weird');
cy.get('#error').should("be.visible");
});
}
}

๐Ÿ“š Have a library of useful functions

Tests are not the same as production code, so you should write them, avoiding any weird abstraction. But sometimes you can find repeating some code that could be easy to reuse.

The trick part is to use some convention that makes obvious where the abstractions are placed to make them easy to find, change or reutilize.

I usually start by creating a file called utils and saved in the support folder.

// at /support/utils.ts
// returns the current user-access-token stored at local storage
export const getUserAccessToken = (): any => {
const userAccessToken = localStorage.getItem(TOKEN_KEY);
if (!userAccessToken) {
return TOKEN;
}
return JSON.parse(userAccessToken);
};

๐Ÿ“„ Use Page Objects on complex scenarios

Sooner or later, you will be faced with that page. One that may need multiple tests generated on all files. The page needs complex queries to traverse its content.

In such cases, hiding all interaction with the DOM in the methods of a class created only for that purpose is advisable.

Do not abuse this technique, and always create a class for any of your pages. Remember that the test code must have as few abstractions as possible.

And always follow a convention, such as having a pages folder with all your Page Objects under the support folder.

class HomePage{
visit(){
cy.visit('/');
}
getTitle(){
return cy.get('header .app-title')
}
search(term: string){
cy.get('#search-box').clear().type(term);
}
}

๐Ÿงฝ Prepare before and clean after

The first and last things should be the most important ones. And that is the case for the Cypress hooks: before, beforeEach, after, afterEach.

Place code to arrange your tests in the before hooksโ€ฆ and clean or ensure some logic on the after events. Be careful with the latest because the after-hooks never get called if your test breaks.

Need help testing your appsโ“
I offer a coaching service with Cypress.๐ŸŒฒ

Contact me๐Ÿ“ง.

๐ŸŒ… Summary.

Your development experience with Cypress can be improved by following a small set of guidelines. this will lead to a better structure, reuse, and maintenance of your test.

--

--

Alberto Basalo

Advisor and instructor for developers. Keeping on top of modern technologies while applying proven patterns learned over the last 25 years. Angular, Node, Tests