Photo by Kevin Martin Jose on Unsplash
Como refatorei testes do Cypress utilizando comandos customizados
Motivação
Desde o final do ano passado o time em que participo no meu atual emprego vem se alinhando bastante em algumas ideias para a melhoria do nosso produto, e uma delas é a implementação de testes em todo o nosso sistema, desde o Backend até o Frontend, com testes unitários e de integração. Como isso já vem desde o ano passado, já tínhamos uma codebase de testes de integração, fazendo o fluxo de compra completo (pesquisar, selecionar um voo, preencher dados dos passageiros, dados de pagamento e finalizar compra). Tudo isso usando cypress. Foi me passada uma tarefa de criar mais testes, dessa vez para testar especificamente o que chamamos de "frame de pesquisa", que é onde nossos clientes fazem a pesquisa dos voos na home do site. Com isso, acabei me encontrando pela primeira vez com essa codebase que já tínhamos, estava bem estruturada até, mas tinham algumas coisas claras que poderíamos mudar para ficar algo mais fácil de codar e organizado. Após essas mudanças que fiz, venho mostrar para vocês o motivo pelo qual fiz isso e como
Adição de Typescript e estrutura atual de comandos
Sim, o projeto não estava em typescript. Foi a primeira coisa que fiz, instalar e configurar o typescript para ai sim começar a refatoração. Essa parte foi bem simples, sem mistério (se não conhece um jeito de fazer isso, consulte a documentação oficial). Após isso, parti para a reorganização dos comandos específicos de rotinas que já existiam (pesquisa, preenchimento de dados de passageiros, pagamento e etc). A pasta estava organizada da seguinte maneira:
Sendo:
commands.js o arquivo que em teoria deveria ter comandos personalizados do cypress, mas não estava sendo utilizado;
common.js contendo funções selecionar um voo, preencher dados de passageiros e etc. (o que eu não considero "common");
desktop-workflow.js contendo funções de praticamente todo o fluxo de uma compra feita no desktop;
index.js que em teoria deveria importar todos os comandos personalizados para serem usados sem a necessidade e importar funções nos seus arquivos de teste;
mobile-workflow.js mesmo que o desktop-workflow.js mas para mobile;
utils.js contendo todo tipo de função utilitária que possa imaginar, desde formatação de datas, até escolhas randômicas de dados de passageiros para preencher e finalizar a compra.
Para mim, tinham 3 problemas:
Não utilização dos comandos personalizados que o cypress oferece. Se tínhamos um fluxo tão bem definido, por que não colocar funções como selecionar um voo ou preencher dados de um passageiro como algo que poderia ser usado pelo proprio cypress? Do mesmo jeito que usamos o cy.get() poderiamos usar um cy.selectFlight(). Essas funções já existiam, mas estavam sendo importadas nos nossos arquivos de teste e sendo chamadas como funções simples;
Junção de muitas funções em um escopo abrangente. Bom, o arquivo desktop-workflow.js continha todo o fluxo que um cliente faria para comprar em nosso site, desde a pesquisa até a finalização da compra. Se a gente parar pra pensar, isso já está separando em um escopo, sendo dividido entre desktop ou mobile. Já é alguma separação, mas dá para melhorar. Como em qualquer e-commerce, você tem um fluxo muito bem definido sendo ele:
Pesquisa > Selecionar Produto > Preencher dados > Finalizar Compra
Com esse fluxo bem definido, temos uma nova separação de escopo para os nossos comandos. Ao fazer essa mudança, em vez de termos apenas essas duas separações entre mobile e desktop, a nível de código, teríamos uma separação de rotinas entre: pesquisa, seleção de produto, preenchimento e finalização de compra. Particularmente, acredito que com essa estruturação, fica mais fácil de entender o projeto e dar manutenção.
- Arquivo de utils muito grande. Como disse, o arquivo de utils levava todas as funções possíveis. Foi bem simples minha mudança, apenas separei ele em escopos menores, no caso dois: datas e dados de itinerário. Sendo dados de itinerário tudo que diz respeito a geração de dados randômicos para pesquisa, passageiro, pagamentos e etc.
Após identificar isso, fui implementar as mudanças.
Nova estrutura de comandos
Após minha pequena refatoração, a estrutura da pasta ficou assim:
Como podem ver, "resolvi" os três pontos principais que na minha concepção eram problemas. Talvez o que mais chamou sua atenção foram esses arquivos de tipagem, mas vou explicar o motivo deles ali.
Como eram feitos os comandos antigos
Antes os comandos eram funções simples do javascript, como esse abaixo:
export const fulfillContact = (email) => {
cy.get('[name=name]').type(randomData('names'))
cy.get('[name=email]').type(email)
cy.get('#confirmation').type(email)
cy.get('[name=phone]').type(randomData('phones'))
}
isso acarretava em ter que importar essa função dentro do arquivo de teste para ai sim poder utiliza-lá e ainda assim "às cegas" por ser javascript.
Como são feitos os comandos novos
Agora eles são feitos usando o Cypress.Commands.add(), que deixa eles nativos no cypress, podendo ser chamados através do cy:
Cypress.Commands.add('fulfillContact', (email: string) => {
cy.get('[name=name]').type(randomData('names'))
cy.get('[name=email]').type(email)
cy.get('#confirmation').type(email)
cy.get('[name=phone]').type(randomData('phones'))
})
já que agora ele é um comando customizado do cypress, podendo ser acessado direto do cy e estamos utilizando typescript, nada mais justo do que fazer uma tipagem personalizada, correto ? Pois aqui está o nosso types.d.ts:
// load type definitions from Cypress module
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable {
/**
* Comando customizado para preencher campos de contato
* @example cy.fulfillContact(caiogrossi6@gmail.com)
*/
fulfillContact(email: string): Chainable<Element>
}
}
com isso, alem de termos o auto-complete usando o cy, ainda teremos um pop-up do vscode escrito exatamente essa descrição que colocamos, uma espetáculo, não?
Considerações finais
Essa foi minha primeira aventura refatorando algo real de um produto real, mesmo não sendo uma refatoração grande e complexa, acredito que contribuiu para a legibilidade. Sempre prezei ao máximo pela organização do código e também de usar todas as features que estiverem disponíveis para facilitar o trabalho. Acredito que consegui alcançar isso com essa refatoração.