Uma API aceita uma escrita, mas a consulta seguinte ainda mostra o estado anterior. Isso pode ser normal quando a leitura passa por uma réplica, um índice de busca, um cache ou uma projeção atualizada por evento. O teste fica instável quando trata essa janela como se fosse um atraso fixo.

Para testar consistência eventual, defina o que inicia a propagação, qual leitura deve refletir a mudança, qual condição prova que ela chegou e qual é o limite total da espera. Depois faça polling dessa condição. O teste deve parar quando o estado correto aparecer e parar de tentar quando surgir uma falha que não é transitória.

Essa é uma decisão sobre a fronteira do teste. A comparação entre testes unitários e de integração em Node.js ajuda a escolher quais componentes precisam permanecer reais. Aqui, a prova atravessa a leitura que o usuário ou outro serviço realmente fará.

Diagrama mostra uma escrita, uma leitura antiga, polling e um limite que termina em pronto ou timeout.

Resposta curta

  • Registre a escrita, a leitura observada, a condição de sucesso e o limite total.
  • Use polling com um único prazo, não sleep fixo reiniciado a cada tentativa.
  • Trate estados transitórios como transitórios e erros de autorização ou validação como falhas imediatas.
  • Guarde as últimas observações quando o prazo acabar.
  • Teste a ausência durante toda a janela quando o requisito for que algo não apareça.

O que o teste precisa provar?

O teste não precisa provar que duas leituras acontecem ao mesmo tempo. Ele precisa provar que a mudança fica observável no caminho que o contrato público promete, dentro de uma janela conhecida. Antes do código, escreva quatro itens: gatilho, observação, predicado e limite.

  • Gatilho: a escrita ou comando que inicia a propagação.
  • Observação: o endpoint, consulta, índice ou tela que o consumidor usará.
  • Predicado: o estado que significa que a mudança chegou, como um id, uma versão ou um status.
  • Limite: o tempo máximo aceitável para essa condição aparecer.

Um 200 não basta. Uma resposta bem-sucedida pode continuar carregando uma versão antiga. O predicado precisa comparar o valor que a escrita produziu. Se o fluxo usa versões, valide também a versão esperada. Se usa estados, liste quais estados intermediários são permitidos.

No DynamoDB, por exemplo, a documentação DynamoDB read consistency explica que uma leitura eventualmente consistente pode não refletir imediatamente uma escrita concluída. A mesma documentação descreve quando uma leitura forte pode ser solicitada e quando um índice global continua apenas eventualmente consistente. O teste precisa seguir essa fronteira do provedor, não uma suposição genérica.

Por que um sleep fixo produz um teste ruim?

Um sleep(1000) não verifica o estado. Ele apenas compra uma aposta de tempo. Se a projeção ficar pronta em 50 milissegundos, cada execução paga o segundo inteiro. Se ela precisar de mais tempo sob carga, o mesmo teste falha mesmo que a janela documentada ainda não tenha terminado.

O problema também aparece quando cada tentativa reinicia o relógio. Um helper que espera um segundo depois de cada erro pode ficar vivo por muito mais tempo do que o autor imaginou. Use um limite calculado uma vez e faça cada consulta consumir o mesmo orçamento.

A documentação Assertions do Playwright recomenda asserções que tentam novamente quando a interface é assíncrona e documenta expect.poll para repetir uma função até o matcher passar ou o timeout acabar. Isso não transforma qualquer erro em atraso aceitável. A função ainda precisa classificar as respostas.

O guia de testes de APIs eventualmente consistentes, consultado em 13/10/2026, propõe a mesma separação entre estados esperados, prazo total e erros terminais. É uma síntese prática, não a definição do contrato do seu sistema.

Como usar expect.poll no Playwright?

Use expect.poll quando o que precisa ser repetido é uma leitura assíncrona e o matcher final consegue expressar a condição de convergência. O exemplo abaixo é ilustrativo: substitua a rota, o estado e a classificação de 404 pelo contrato da sua API.

import { test, expect } from "@playwright/test";

test("a projeção fica visível depois da escrita", async ({ request }) => {
  const create = await request.post("/orders", {
    data: { reference: "order-for-test" },
  });
  expect(create.ok()).toBeTruthy();

  const { id, version } = await create.json();

  await expect.poll(
    async () => {
      const response = await request.get(`/order-search/${id}`);

      // Neste contrato, 404 significa "ainda não indexado".
      if (response.status() === 404) {
        return { state: "not_visible" };
      }

      // Para esta API, estes status não fazem parte da propagação normal.
      if ([401, 403, 422].includes(response.status())) {
        throw new Error(`falha terminal: ${response.status()}`);
      }

      if (!response.ok()) {
        throw new Error(`endpoint de leitura falhou: ${response.status()}`);
      }

      const body = await response.json();
      return { state: body.state, version: body.version };
    },
    {
      intervals: [100, 250, 500, 1_000],
      timeout: 10_000,
      message: "a projeção precisa convergir dentro do contrato",
    },
  ).toEqual({ state: "ready", version });
});

O código não espera até o fim quando a versão correta aparece. Também não transforma uma resposta de autorização em uma nova tentativa. O intervalo e o timeout são exemplos de configuração, não garantias universais. Derive-os do objetivo do serviço e da carga que a suíte pode gerar.

O teste acima prova a leitura de uma projeção. Ele não prova que o sistema inteiro é rápido nem que toda réplica convergiu. Essa diferença impede que uma asserção funcional vire um benchmark acidental.

Como separar atraso esperado de falha real?

Classifique a observação antes de escolher o intervalo. Um registro ausente pode ser transitório em um índice recém-atualizado. O mesmo 404 pode ser uma falha permanente em um endpoint que deveria retornar o registro primário. Status HTTP ajudam, mas o contrato do recurso decide.

Uma classificação útil costuma incluir not_visible, indexing, ready e failed. O teste pode continuar em not_visible e indexing, parar em ready e falhar em failed. Para respostas como 503, escolha explicitamente entre uma política de retry e uma falha imediata. Não esconda um incidente repetindo qualquer status até o timeout.

Registre uma observação compacta por tentativa: horário relativo, status, estado, versão, identificador de correlação e uma mensagem sanitizada. Não grave tokens, corpos sensíveis ou dados pessoais. Quando o prazo acabar, a mensagem deve mostrar a última condição observada, não apenas "timeout".

Isso também ajuda a separar este problema de testar a lógica de retry em JavaScript sem esperar. Retry verifica quando uma chamada é refeita. Consistência eventual verifica quando o efeito de uma escrita se torna visível em outra leitura. Um fluxo pode precisar dos dois testes, mas eles respondem a perguntas diferentes.

Como testar estados intermediários e versões?

Consistência eventual não significa que qualquer sequência seja aceitável antes da convergência. Se o recurso deve passar de queued para indexing e depois ready, o teste deve rejeitar uma volta de ready para queued quando a API promete progresso monotônico.

Uma forma simples de expressar essa regra é guardar o último estado permitido e verificar cada nova observação. Para versões numéricas, rejeite uma versão menor que a anterior. Para estados sem ordem total, liste transições permitidas em vez de comparar strings por acaso.

Se a instabilidade vem de duas operações locais que disputam o mesmo recurso, a fronteira é outra. A fixture para testar condições de corrida em JavaScript ajuda a controlar a ordem das operações. O polling deste artigo serve quando a ordem interna já terminou, mas o resultado ainda não está visível no caminho de leitura.

Como testar que um estado proibido não aparece?

Uma afirmação positiva pode terminar na primeira leitura correta. Uma afirmação negativa precisa observar a janela inteira. Ver um recurso ausente uma vez não prova que ele não aparecerá depois de a fila ou o índice processar o evento.

Para testar revogação, exclusão ou isolamento entre clientes, defina uma janela de observação e falhe assim que o estado proibido aparecer. Só declare sucesso quando toda a janela passar. Se o sistema oferece uma verificação determinística de autorização, prefira essa prova a consultar repetidamente uma projeção atrasada.

Não use o mesmo timeout da afirmação positiva por conveniência. A pergunta "o registro aparecerá?" e a pergunta "o registro nunca ficará visível?" têm riscos diferentes. A primeira pode parar cedo. A segunda precisa completar o período que o contrato realmente cobre.

Quando polling não é o melhor teste?

Use uma leitura forte quando a pergunta é apenas se a escrita foi persistida e o provedor oferece essa garantia para o caminho escolhido. No DynamoDB, por exemplo, índices globais não aceitam a mesma opção de leitura forte descrita para tabelas e índices locais. Nesse caso, trocar a consulta pode mudar o objeto do teste.

Use um evento, webhook ou assinatura de fila quando esse é o contrato público do consumidor. O teste deve então verificar correlação, duplicatas e prontidão do assinante. Depois do evento, ainda pode fazer sentido consultar a leitura pública para provar o estado que o usuário verá.

Por fim, não use um teste funcional de convergência para medir percentis de latência. Ele mostra se a condição apareceu dentro de um limite. Um teste de desempenho precisa controlar carga, concorrência e coleta de duração separadamente. Misturar as duas perguntas produz um timeout difícil de interpretar.

Checklist para publicar esse teste no CI

Antes de colocar o teste na execução principal, confira:

  • a escrita produz um identificador ou versão que a leitura consegue localizar;
  • a consulta usa o mesmo caminho que o consumidor real usa;
  • o predicado verifica o valor correto, não apenas 200;
  • as respostas transitórias estão listadas e as terminais falham cedo;
  • existe um único deadline, sem renovar o prazo a cada tentativa;
  • as observações finais são sanitizadas e úteis para diagnóstico;
  • estados proibidos e regressões de versão têm asserções próprias;
  • a janela de uma afirmação negativa está escrita no requisito;
  • polling não está sendo usado para substituir um evento ou um teste de carga;
  • a suíte é executada com dados isolados e limpeza conhecida.

O objetivo não é fazer o teste esperar mais. É fazer a espera representar o contrato que o sistema realmente oferece. Quando a leitura converge, o teste termina. Quando não converge, a falha conserva evidência suficiente para investigar se o defeito está na escrita, na propagação ou na leitura.

Perguntas frequentes

Posso usar sleep em um teste de consistência eventual?

Um sleep curto pode fazer parte de uma fixture controlada, mas não deve ser a prova principal da convergência. A documentação do Playwright oferece expect.poll com timeout e intervalos configuráveis. Use uma condição observável e um limite total para que o teste termine cedo quando o estado correto aparecer.

Devo repetir qualquer erro até o timeout?

Não. Repita somente estados que o contrato classifica como transitórios. Falhas de autenticação, autorização, validação ou schema devem falhar cedo. Uma resposta de servidor pode ser retryable em um sistema e terminal em outro, por isso a classificação precisa estar documentada junto do teste.

Como escolho o timeout?

Comece pelo objetivo de propagação que o sistema promete, não por um número conveniente para o CI. Use um deadline total, registre a última observação e investigue quando o teste precisar de mais tempo. Se o valor vier apenas de tentativas locais, trate-o como hipótese, não como garantia do provedor.

Polling substitui um teste de evento?

Não. Polling verifica uma condição observável quando esse é o caminho do cliente ou quando a leitura atrasada é justamente o objeto da prova. Se o produto promete um webhook ou evento, teste a entrega, a correlação, as duplicatas e a recuperação separadamente.

Conclusão

Testar consistência eventual não é escolher um sleep grande o bastante. É escrever o contrato de visibilidade e dar ao teste um orçamento de tempo único. O polling consulta a condição certa, termina cedo no sucesso e não esconde falhas que não pertencem à propagação normal.

Esse desenho deixa a falha mais útil. Você sabe qual escrita iniciou o fluxo, qual leitura estava atrasada, qual estado foi observado por último e qual limite foi ultrapassado. A mesma disciplina vale para réplicas, caches, índices de busca e projeções atualizadas por eventos.

Como este artigo foi produzido

Samuel Fajreldines é o autor responsável por este texto. A pesquisa combinou a documentação oficial do Playwright, a documentação do DynamoDB, a API de temporização do Node.js, resultados atuais em português, inglês e espanhol e discussões públicas de desenvolvedores. Os trechos de Playwright são exemplos ilustrativos de contrato e não representam uma medição de um sistema de produção.

Usei assistência de IA para descoberta de fontes, organização, redação, geração da imagem e revisão de consistência. A assistência não executou uma arquitetura distribuída real, não mediu latência de propagação e não substituiu a verificação das fontes oficiais.

Fontes consultadas