Assíncrono vs. Síncrono: Entendendo .then()
e cy.wrap()
no Cypress
No mundo do JavaScript, e consequentemente no Cypress, entender como as operações são executadas – se uma após a outra (síncrona) ou se algumas podem “esperar” enquanto outras continuam (assíncrona) – é crucial. O Cypress tem um jeito especial de lidar com isso, que é diferente do JavaScript “puro”.
JavaScript Tradicional: Síncrono vs. Assíncrono
- Síncrono (Synchronous):
- Imagine uma fila única no banco. Uma pessoa é atendida de cada vez. A próxima só é atendida quando a anterior termina.
- No código:
console.log('Primeiro');
console.log('Segundo'); // Só executa depois do primeiro
console.log('Terceiro'); // Só executa depois do segundo
- Cada linha espera a anterior terminar. Simples, mas pode “travar” tudo se uma tarefa demorar muito (ex: buscar dados de um servidor).
- Assíncrono (Asynchronous):
- Imagine fazer um pedido em um restaurante. Você faz o pedido (inicia uma tarefa) e pode fazer outras coisas (conversar, olhar o celular) enquanto a cozinha prepara sua comida (a tarefa demorada). Você é “notificado” quando está pronto.
- No JavaScript, isso é comum com operações que levam tempo: chamadas de rede, leitura de arquivos, timers.
- Mecanismos comuns:
- Callbacks: Funções passadas como argumento para serem executadas quando a operação assíncrona termina.Promises: Objetos que representam a eventual conclusão (ou falha) de uma operação assíncrona. Permitem um código mais limpo (
.then()
para sucesso,.catch()
para erro).Async/Await: Açúcar sintático em cima das Promises, tornando o código assíncrono mais parecido com o síncrono, mas sem travar a execução.
- Callbacks: Funções passadas como argumento para serem executadas quando a operação assíncrona termina.Promises: Objetos que representam a eventual conclusão (ou falha) de uma operação assíncrona. Permitem um código mais limpo (
console.log('Iniciando pedido...');
fetch('https://api.exemplo.com/dados') // Operação assíncrona
.then(response => response.json())
.then(data => console.log('Dados recebidos:', data))
.catch(error => console.error('Erro no pedido:', error));
console.log('Pedido feito! Aguardando resposta enquanto faço outras coisas...');
O Jeito Cypress: Comandos Enfileirados, Não Promessas Diretas
O Cypress lida com a assincronicidade de uma maneira muito particular e elegante para testes:
- Comandos Enfileirados: Quando você escreve
cy.visit('/')
,cy.get('button')
,cy.click()
, esses comandos não são executados imediatamente como no JavaScript síncrono. Em vez disso, eles são adicionados a uma fila de comandos do Cypress. - Execução Gerenciada: O Cypress então executa esses comandos um por um, na ordem em que foram enfileirados.
- Waits e Retries Automáticos: O mais importante é que o Cypress espera automaticamente que os elementos apareçam no DOM, que as condições sejam atendidas (ex: um botão se torne clicável) antes de prosseguir ou falhar o teste. Ele tem sua própria lógica de “promessas internas” e novas tentativas.
- Chainability (Encadeamento): É por isso que você pode encadear comandos:
cy.get('input#username') // 1. Enfileira o get
.type('meuUsuario') // 2. Enfileira o type (só executa após o get ter sucesso)
.should('have.value', 'meuUsuario'); // 3. Enfileira a asserção
Você não usa await
antes de cy.get()
ou cy.type()
porque eles não retornam Promises padrão que o await
do JavaScript “entende” diretamente para o fluxo de controle do seu script. Eles retornam objetos “chainable” do Cypress.
Entra em Cena o .then()
do Cypress
Apesar do Cypress gerenciar a maior parte da assincronicidade para você, às vezes você precisa “pausar” a cadeia de comandos do Cypress para:
- Trabalhar com o valor/elemento obtido pelo comando anterior usando JavaScript puro.
- Realizar alguma lógica condicional.
- Armazenar um valor para usar depois.
- Interagir com bibliotecas de terceiros.
É para isso que serve o cy.then(callbackFunction)
.
- Como funciona:
- Ele recebe uma função de callback.Essa função de callback só é executada depois que todos os comandos anteriores na cadeia do Cypress foram concluídos com sucesso.O argumento da sua função de callback será o “sujeito” (o resultado) do comando anterior.
cy.get('input[data-cy="input-username"]').type('Aluno123');
cy.get('[data-cy="input-username"]')
.invoke('val') // Pega o valor atual do input
.then((valorDoInput) => { // 'valorDoInput' aqui é o que .invoke('val') retornou
// Agora estamos 'dentro' do .then(), podemos usar JavaScript normal
console.log('O valor do input é:', valorDoInput);
expect(valorDoInput).to.equal('Aluno123'); // Usando asserção Chai (que vem com Cypress)
// Podemos fazer lógica condicional
if (valorDoInput.length > 5) {
cy.log('O nome de usuário é longo!'); // Enfileirando um novo comando Cypress
}
// Importante: Se você quiser continuar a cadeia do Cypress COM o valor original,
// não precisa retornar nada ou pode retornar o valor.
// Se retornar undefined (ou nada), o sujeito original é passado adiante.
// Se retornar um valor (que NÃO seja uma promise), esse novo valor se torna o sujeito.
// NUNCA retorne uma Promise de dentro de um .then() esperando que o Cypress a aguarde automaticamente.
})
.should('be.visible'); // Este .should() ainda se refere ao [data-cy="input-username"] original
- Diferença do
.then()
de Promises Padrão:- Embora pareça um
.then()
de Promise, ele está integrado ao sistema de enfileiramento e repetição do Cypress. - Você não deve retornar Promises de dentro de um
cy.then()
esperando que o Cypress as gerencie como faria com uma Promise padrão. Se precisar lidar com Promises externas, usecy.wrap()
sobre a Promise.
- Embora pareça um
Trazendo de Volta com cy.wrap()
E se você tem um valor, um objeto, um elemento DOM (talvez obtido de um cy.then()
, de uma variável JavaScript, ou de uma API externa via cy.request()
) e quer usar comandos Cypress nele?
É aí que o cy.wrap()
brilha! Ele “embrulha” esse objeto e o transforma no “sujeito” atual da cadeia de comandos do Cypress, permitindo que você chame .click()
, .type()
, .should()
, etc. nele.
- Exemplo:
let meuBotao;
cy.get('[data-cy="botao-login"]')
.then(($btn) => { // $btn aqui é um elemento jQuery
// Suponha que você faça algo com $btn ou o armazene
meuBotao = $btn; // Armazenando o elemento jQuery
console.log('Elemento jQuery do botão:', meuBotao);
});
// Mais tarde no teste, ou em outro teste (se 'meuBotao' for acessível no escopo)
// Se tentássemos meuBotao.click() diretamente, seria o click do jQuery, não do Cypress.
// Para usar os superpoderes do Cypress (waits, retries, logging), usamos cy.wrap():
// ... (algum tempo depois no teste, se meuBotao foi definido)
// cy.then() garante que o código dentro dele só rode após o cy.get() acima
cy.then(() => {
if (meuBotao) {
cy.wrap(meuBotao) // Agora 'meuBotao' é o sujeito do Cypress
.should('be.visible')
.and('contain.text', 'Entrar')
.click(); // Agora é o .click() do Cypress!
}
});
// Outro uso comum é com variáveis ou objetos
const usuario = { nome: "Cypress User", id: 123 };
cy.wrap(usuario)
.its('nome') // .its() é um comando do Cypress para acessar propriedades
.should('eq', 'Cypress User');
cy.wrap(usuario)
.should('have.property', 'id', 123);
Em Resumo:
Característica | JavaScript Assíncrono (Promises, async/await) | Comandos Cypress | cy.then() | cy.wrap() |
---|---|---|---|---|
Execução | Controlada pelo dev com await ou .then() | Enfileirada e gerenciada pelo Cypress | Executa código JS com o resultado do comando anterior, dentro do fluxo Cypress. | Traz um objeto/valor para dentro do fluxo de comandos Cypress. |
Retorno do Comando | Promises | Objetos “chainable” do Cypress (não Promises diretas para await ) | O que você retornar (não-Promise) pode se tornar o novo sujeito, ou o sujeito original é passado adiante. | Retorna um sujeito Cypress “embrulhado”, pronto para novos comandos. |
Uso de await | Comum e necessário | Geralmente um anti-padrão com comandos Cypress | Não use await dentro do callback para esperar Promises do Cypress. | Não aplicável diretamente; ele cria um ponto de partida para comandos Cypress. |
Principal Utilidade | Lidar com operações demoradas sem bloquear | Escrever testes de UI de forma declarativa e robusta com waits automáticos | Acessar/manipular o sujeito com JS puro; lógica condicional; interagir com escopo externo. | Tornar objetos/variáveis/elementos DOM “Cypress-áveis” para encadear comandos Cypress. |
Entender essa distinção é chave para escrever testes Cypress eficientes e evitar armadilhas comuns ao tentar misturar o modelo de execução do Cypress com padrões assíncronos tradicionais do JavaScript de forma inadequada. O Cypress abstrai a complexidade para você na maioria das vezes, e .then()
e cy.wrap()
são suas ferramentas para os momentos em que você precisa de um pouco mais de controle ou flexibilidade.