Assíncrono vs. Síncrono: Além do Cypress

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.
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:

  1. 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.
  2. Execução Gerenciada: O Cypress então executa esses comandos um por um, na ordem em que foram enfileirados.
  3. 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.
  4. 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, use cy.wrap() sobre a Promise.

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ísticaJavaScript Assíncrono (Promises, async/await)Comandos Cypresscy.then()cy.wrap()
ExecuçãoControlada pelo dev com await ou .then()Enfileirada e gerenciada pelo CypressExecuta 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 ComandoPromisesObjetos “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 awaitComum e necessárioGeralmente um anti-padrão com comandos CypressNã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 UtilidadeLidar com operações demoradas sem bloquearEscrever testes de UI de forma declarativa e robusta com waits automáticosAcessar/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.