Testes E2E e Cypress: como testar uma aplicação web utilizando boas práticas

Hugo Albrecht
OPANehtech
Published in
8 min readApr 26, 2023

Há poucas semanas atrás inicializamos em meu time a automação de testes de nossa aplicação web para facilitar e nos permitir realizar testes regressivos com mais agilidade.

Nossa escolha foi utilizar o framework Cypress como ferramenta principal, e após vários testes escritos, aqui vai um guia rápido de como você pode configurar e testar uma aplicação web utilizando algumas features interessantes que o Cypress nos oferece e guiado por boas práticas.

Mas afinal, para que servem os testes E2E?

A sigla E2E vem de end-to-end ou ponta a ponta em português, e essa nomenclatura já nos permite entender melhor seu funcionamento: testar comportamentos de uma aplicação do início ao fim, simulando ações que o usuário final realizará garantindo que a aplicação se comporte como o esperado.

Existem vantagens ao utilizar Cypress sobre outras ferramentas?

A resposta é sim. Se comparado a outras ferramentas de testes como Selenium e Protractor, a experiência de desenvolvimento utilizando Cypress e também a performance dos testes se torna vantajosa tendo em vista diversas features como:

  • Não necessita de diversas configurações e instalação de dependências a parte, possui asserts, mocks, stubs por padrão.
  • Debug de fácil leitura devido aos logs de erro e stack traces muito legíveis.
  • Espera automática dos asserts, fazendo com que não seja necessário o uso de diversos async/await espalhados pelo código.
  • Screenshots dos testes falhos e vídeos de toda a execução de um conjunto de testes.
  • Testes executados no navegador, sem emular ou rodar os testes em outro processo. Suportando Chrome, Firefox, Edge entre outros.

Vale ressaltar que assim como a maioria das coisas no mundo da programação, nenhuma ferramenta é uma bala de prata, e cabe a você e seu time colocar na balança o que melhor se encaixa com seu projeto.

Já que falamos sobre alguns conceitos, vamos ao exemplo:

Para esse guia, irei utilizar de exemplo uma aplicação muito simples desenvolvida utilizando React.js + Typescript (no final desse post, você encontrará o link para o repositório desse exemplo) para interagirmos com o DOM através dos nossos testes utilizando Cypress. Aqui vai uma demonstração de nosso exemplo que será testado:

Formulário de aceite dos termos antes de acessar a aplicação; Simulador.

1. Instalação e configuração

Para instalar o Cypress em seu projeto, basta instalar o pacote como dependência de desenvolvimento com o comando abaixo:

npm install cypress --save-dev

Depois de instalado, você pode gerar o arquivo de configuração e outros arquivos de exemplo executando o comando abaixo e seguindo os passos:

npx cypress open

Ao ser aberto o aplicativo desktop do Cypress, basta clicar em E2E Testing na tela abaixo:

Quando selecionada a opção acima, será detectado que não existem arquivos de configuração nem o diretório /cypress, basta clicar em Continuar e os arquivos serão criados.

Clique em ”Continuar” para criar os arquivos padrão

Pronto! Os arquivos necessários foram criados e agora podemos partir para algumas propriedades dentro do arquivo cypress.config.ts. Para nosso exemplo, ele ficará assim:

import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
specPattern: 'cypress/e2e/**/*.spec.ts',
baseUrl: 'http://localhost:5173',
experimentalRunAllSpecs: true,
viewportHeight: 640,
viewportWidth: 360
}
});
  • specPattern: caminho e extensões que deveram ser considerados arquivos contendo os testes.
  • baseUrl: path base para onde está a aplicação que iremos testar, nesse caso iremos executar o projeto localmente.
  • experimentalRunAllSpecs: habilita ou não a opção de rodar todos os testes existentes no projeto (comportamento padrão ao executar os testes com o comando run em modo headless, porém ao executar com o comando open sem essa opção habilitada temos que rodar um arquivo por vez).
  • viewportHeight e viewportWidth: número de pixels de altura e largura respectivamente na qual será acessada a aplicação no navegador. Semelhante ao modo responsivo do DevTools.

Com essas configurações, estamos prontos para começar a escrever nossos primeiros testes.

2. Escrevendo nossos testes

Contextualizando, a aplicação de exemplo que será usada aqui é um simulador de financiamentos, e possui duas abas: uma de veículos e outra de imóveis, o que é alterado é a taxa de juros das duas modalidades. Vale lembrar que é apenas um exemplo simples, mas que possui interações de input, botões e abas. Sem mais enrolação, vamos pro código.

Para começar, criaremos um arquivo simulator.spec.ts dentro do diretório /cypress/e2e. Para quem já é familiarizado com testes unitários com Javascript reconhece as funções describe() e it(), mas brevemente explicando: o describe() encapsula diversos testes — cada função it() — e é utilizado para agrupar cenários de um mesmo contexto ou feature.

Nossa primeira interação será acessar a aplicação na rota raíz (lembra que falamos sobre o baseUrl? O método cy.visit() utiliza ele, sendo assim podemos passar apenas a rota que queremos visitar), validar se o texto de aviso está presente, aceitar os termos por um checkbox, e por fim clicar no botão para acessar o simulador.

Como isso precisa ser executado antes de todos os testes que virão pela frente, será executado dentro do hook beforeEach, que executa antes de cada teste.

Antes de escrever todos as nossas ações, aqui irá a primeira dica de ouro para você utilizar quando estiver escrevendo testes: utilizar atributos data-cy, data-test, data-testid… para selecionar os elementos no DOM.

Componente de termos utilizando id para identificar o parágrafo.

Como a função cy.get funciona igual ao document.querySelector, se fizermos uma busca pelo elemento da maneira abaixo, tudo funcionará normalmente, buscando pelo elemento strong dentro do elemento com o id terms-warning:

describe('Simulator', () => {
beforeEach(() => {
// método que irá visitar uma url ou rota de nosso app
cy.visit('/')
.get('#terms-warning strong')
.should('contain.text', 'Essa aplicação é apenas um experimento')
});
});

Porém, vamos dar uma olhada no que a própria documentação nos alerta sobre essa abordagem:

Selecionar o elemento acima utilizando classes, id ou tags é muito volátil e altamente sujeito a mudanças. Você talvez troque o elemento, talvez possa refatorar o CSS e atualize os id’s, talvez você possa adicionar ou remover classes que afetam o estilo do elemento.

Ao invés disso, adicionando o atributo data-cy no elemento, lhe proporcionará um seletor que é utilizado apenas para testes.

O atributo data-cy não alterará nenhuma estilização ou comportamento de Javascript.

Agora que sabemos disso, vamos realizar uma mudança e ver como selecionar elementos seguindo uma das boas práticas que o Cypress nos encoraja.

Após a refatoração, passamos o atributo data-cy para o elemento que desejamos selecionar nos testes.
describe('Simulator', () => {
beforeEach(() => {
// método que irá visitar uma url ou rota de nosso app
cy.visit('/')
.get('[data-cy=terms__main-warning]')
.should('contain.text', 'Essa aplicação é apenas um experimento')
});
});

Pronto! Daqui pra frente, todos os elementos que desejarmos interagir ou validar em nossos testes, devem estar com o atributo data-cy que nos deixará blindado de outras modificações.

Agora, você pode olhar para esse código acima e entender que não há como ficar melhor, porém, vamos pensar.. essa sintaxe do atributo data-cy pode parecer um pouco “complicada” para escrever. Novamente, te mostro que podemos melhorar nosso código de teste com uma outra feature muito interessante que o Cypress nos proporciona: comandos personalizados.

Iremos criar um comando que receberá nosso seletor e irá retornar o cy.get interpolando com a sintaxe de atributo, conforme abaixo:

No arquivo cypress/support/commands.ts inclua o código abaixo:

Cypress.Commands.add('getByDataCy', (selector: string, ...args) => {
return cy.get(`[data-cy=${selector}]`, ...args)
})

Como estamos utilizando Typescript, criaremos um arquivo chamado index.d.ts dentro do mesmo diretório do arquivo acima, com a seguinte definição de tipos:

/// <reference types="cypress" />

declare namespace Cypress {
interface Chainable {
/**
* It's a custom command to get elements by data-cy tag.
* @example cy.getByDataCy('my-element-selector')
* @param selector: string
*/
getByDataCy(selector: string): Cypress.Chainable<JQuery<HTMLElement>>;
}
}

Após realizar esses passos, basta utilizarmos nossa função getByDataCy que agora se encontra dentro do objeto cy, assim como todos os outros comandos nativos. Agora nosso teste ficará assim:

describe('Simulator', () => {
beforeEach(() => {
// método que irá visitar uma url ou rota de nosso app
cy.visit('/', ...args)
.getByDataCy('terms__main-warning')
.should('contain.text', 'Essa aplicação é apenas um experimento')
});
});

Muito mais limpo e semântico. Usando nossa criatividade e se baseando na documentação, conseguimos melhorar muito a escrita.

Ainda falando de comandos personalizados, por que não criarmos um comando para realizar o aceite dos termos e abrir o simulador? Faz sentido, uma vez que iremos realizar essa ação sempre antes de iniciar novos cenários. Olha só como ficou o resultado depois de incluir os novos elementos:

Criação do comando personalizado para aceitar os termos antes de abrir o simulador

E o nosso hook beforeEach agora:

describe('Simulator', () => {
beforeEach(() => {
cy.openSimulator();
});
});

É muito legal ver como podemos fazer um bom code split e escrever bons testes com essas dicas!

Com isso, basta escrever nossos testes da simulação como o exemplo abaixo:

De forma encadeada, selecionamos elementos do DOM utilizando nosso comando personalizado cy.getByDataCy e interagimos com o mesmo, realizando digitações, cliques, validações de texto entre inúmeras possibilidades.

3. Executando

Após escrevermos nossos testes, chegou a hora de descobrir se tudo está se comportando como o esperado. Para isso, vamos inicializar a aplicação com o comando npm run dev e também inicializar o Cypress utilizando a UI com a opção open: npx cypress open. Após isso basta clicar em qual suíte de testes desejamos executar:

Testes sendo executados e visualização do passo a passo após execução.

Sucesso!

Conclusão

Com a ajuda desse artigo espero que você tenha compreendido um pouco mais sobre testes end-to-end utilizando Cypress e que as boas práticas e dicas possam te ajudar na sua caminhada desenvolvendo testes em sua aplicação. Você pode encontrar todo o código escrito nesse repositório. Muito obrigado e até a próxima.

--

--