Sleepy Test.
Um Sleepy Test pausa a execução com um atraso fixo no código (`Thread.sleep`, `setTimeout`, `cy.wait(2000)`, `page.waitForTimeout`) para esperar por um trabalho assíncrono em vez de esperar pela condição real.
##Signs and Symptoms
Você vê um atraso com número mágico posicionado entre uma ação e sua asserção, com um comentário se desculpando por ele:
test('order is processed', async () => {
submitOrder(order);
// dá tempo para o worker terminar
await new Promise((r) => setTimeout(r, 2000));
expect(await getOrderStatus(order.id)).toBe('processed');
});
Sinais reveladores:
- Sleeps fixos no corpo dos testes ou em hooks:
Thread.sleep(500),await sleep(1000),time.sleep(2),cy.wait(3000),await page.waitForTimeout(2000),browser.pause(2000). - Comentários como
// espera a animação,// deixa o banco se atualizar,// instável sem isto. - A duração do sleep cresce ao longo do tempo (
1000vira2000vira5000) à medida que as pessoas combatem falhas intermitentes acolchoando o atraso. - O número é a única sincronização — não há asserção, polling ou evento ao qual a espera esteja de fato atrelada.
- Uma suíte lenta cujo tempo de relógio é dominado por sleeps em vez de trabalho real.
O smell é sobre espera incondicional. cy.wait('@apiAlias') ou await expect(locator).toBeVisible() esperam por uma condição e estão ok; cy.wait(2000) e page.waitForTimeout(2000) esperam pelo relógio e não estão.
##Reasons for the Problem
Por que acontece
- Uma operação assíncrona (uma fila, um timer, uma animação, uma chamada de rede, uma thread em segundo plano) não tem um gancho óbvio em que esperar, então um atraso é o caminho de menor resistência.
- Um teste estava falhando de forma intermitente e alguém o "consertou" inserindo ou aumentando um sleep até ele ficar verde na máquina dessa pessoa.
- O teste interage com um sistema externo real (relógio, agendador, sistema de arquivos, serviço HTTP) cuja conclusão não é observável, então o tempo é adivinhado.
Por que prejudica
- Confiabilidade / falsa confiança. Um sleep codifica uma suposição — "o trabalho termina em N ms." O tempo de processamento varia entre máquinas, carga de CI e execuções, então o teste é não determinístico: passa localmente e falha em um agente de CI sobrecarregado, ou pior, o sleep é curto demais e a asserção corre contra o código, passando apenas por sorte de tempo. Essa é uma das causas-raiz mais citadas de testes instáveis.
- Lentidão. Um sleep fixo sempre espera a duração completa, mesmo quando o trabalho terminou em 20 ms. Multiplicados por toda uma suíte, os sleeps transformam segundos de trabalho real em minutos de espera ociosa, o que desestimula rodar os testes com frequência.
- Manutenibilidade. O número mágico é frágil: acelerar ou desacelerar o sistema sob teste quebra silenciosamente o contrato de tempo, e a única "correção" à qual as pessoas recorrem é aumentar o número — uma catraca que deixa a suíte mais lenta e ainda assim instável.
- Legibilidade. O atraso esconde o que o teste está de fato esperando. Um leitor não consegue dizer se
sleep(2000)protege uma escrita no banco, uma renderização ou nada, então a intenção do teste fica obscurecida.
##Treatment
Substitua "esperar um tempo fixo" por "esperar pela condição." Identifique o sinal observável de que o trabalho assíncrono terminou e, então, bloqueie nele com um timeout generoso.
- Faça polling pela condição. Use um helper de polling/retry — Awaitility (JVM),
vi.waitFor/waitFor(Vitest, Testing Library), owaitFordo Jest ou as asserções com retry automático do seu executor de testes — para que o teste prossiga no instante em que a condição for verdadeira e só falhe após um timeout. - Prefira asserções aguardadas nativas. Ferramentas de E2E web já fazem retry: as asserções web-first do Playwright (
await expect(locator).toBeVisible()) e os comandos com retry automático do Cypress eliminam totalmente a necessidade de esperar. - Espere por eventos, não pelo relógio. Aguarde a promise, faça
awaitda resposta de rede, ou use umCountDownLatch/callback/waitFor('@alias')que o código de produção de fato sinaliza. - Controle o tempo em vez de gastá-lo. Quando o atraso for um timer real no código sob teste, use fake timers (
jest.useFakeTimers(),vi.useFakeTimers(),vi.advanceTimersByTimeAsync) para avançar o relógio de forma determinística em vez de dormir.
Antes:
test('order is processed', async () => {
submitOrder(order);
await new Promise((r) => setTimeout(r, 2000)); // sleepy
expect(await getOrderStatus(order.id)).toBe('processed');
});
Depois (faça polling pela condição com um timeout limitado):
import { vi } from 'vitest';
test('order is processed', async () => {
submitOrder(order);
await vi.waitFor(
async () => expect(await getOrderStatus(order.id)).toBe('processed'),
{ timeout: 5000, interval: 50 },
);
});
Equivalentes em Playwright/Cypress:
// Playwright — a asserção web-first faz retry automático até ficar visível ou estourar o timeout
await expect(page.getByText('processed')).toBeVisible();
// Cypress — espere pela requisição, não por um número
cy.intercept('POST', '/orders').as('createOrder');
cy.wait('@createOrder');
O resultado não espera mais do que o necessário, falha rapidamente com uma mensagem de timeout clara e não depende mais da velocidade da máquina.
##Detected by
- sonar java:S2925 — "Thread.sleep" não deve ser usado em testes
- eslint-plugin-playwright playwright/no-wait-for-timeout — no-wait-for-timeout (proíbe page.waitForTimeout)
- eslint-plugin-cypress cypress/no-unnecessary-waiting — no-unnecessary-waiting (proíbe cy.wait com um número)
- eslint-plugin-ui-testing ui-testing/no-hard-wait — no-hard-wait (cy.wait, page.waitForTimeout, browser.pause, t.wait)