Na minha máquina funciona: guia prático de bugs que não se reproduzem

replay de sessão

Um usuário relata que a página quebra. Você segue os passos dele à risca. Tudo funciona. Você fecha o ticket como não reproduzível e, três semanas depois, mais quatro pessoas relatam a mesma coisa.

«Na minha máquina funciona» virou piada, mas é uma afirmação precisa e útil: o bug depende de alguma diferença entre o seu ambiente e o dele. Isso transforma uma pergunta sem resposta em uma que dá para resolver. Não «por que esse bug é tão estranho», e sim «o que é diferente entre essas duas máquinas?».

Não há tantas diferenças possíveis. Em aplicações de navegador, praticamente todas cabem em oito categorias, e cada uma deixa uma assinatura reconhecível.

1. Estado

A causa mais comum de todas. Você reproduz a partir de um carregamento limpo. O usuário está naquela aba há duas horas.

Aplicações de página única que ficam abertas por muito tempo acumulam coisas: entradas velhas num cache do lado do cliente, um websocket que reconectou com outra sessão, uma store alterada por um trecho de código que ele acionou uma hora atrás, um token de acesso renovado no meio do caminho. O seu carregamento novo da página não tem nada desse histórico.

Assinatura: funciona depois de atualizar a página. Se quem reportou diz «atualizar resolve», é quase certo que o caso é esse, e a reprodução de que você precisa não são os passos dele — são os passos dele depois de vinte minutos de uso sem relação nenhuma.

Como pegar: a lista ordenada do que o usuário fez de fato na sessão, não do que ele achou relevante. É isso que um log de ações dá e um campo de passos para reproduzir nunca vai dar, porque o passo relevante é justamente o que ele não pensou em mencionar.

2. Timing

Na sua máquina tudo é rápido. API local, cache aquecido, disco rápido. Num celular dentro de um trem, uma requisição que leva 40ms para você leva 4 segundos — e uma condição de corrida que nunca perde na sua máquina perde sempre na dele.

Assinatura: intermitente. «Acontece mais ou menos uma vez em cinco.» Intermitente quase sempre significa timing, e significa que as duas operações que disputam a corrida têm durações tão próximas que a variação normal troca o vencedor.

Como pegar: os tempos de rede. Veja o que estava em andamento ao mesmo tempo e quanto cada requisição levou. O formato clássico são duas requisições disparadas juntas, num código que supõe que a primeira termina primeiro.

Como reproduzir: faça throttling. O throttling de rede e de CPU do DevTools transforma muitos bugs «não reproduzíveis» em «reproduz toda vez». São os cinco minutos mais valiosos deste post inteiro.

3. Ambiente

Navegador, versão, sistema operacional e, o mais subestimado, o tamanho da viewport.

Você testa em 1440px num Chrome de desktop. O usuário está em 390px no Safari do iOS, ou em 1280px com o zoom do navegador em 125%, o que não é a mesma coisa que 1024px, de maneiras que quebram layouts. O Safari, em especial, ainda diverge no parsing de datas, nos backdrop filters, no comportamento de rolagem e em como trata o armazenamento na navegação privada.

Assinatura: consistente para quem reportou, nunca para você. Reproduz 100% das vezes na máquina dele.

Como pegar: navegador, sistema operacional e viewport registrados automaticamente com o relatório. É a categoria com a maior razão entre «trivial de corrigir quando se sabe» e «impossível de adivinhar» — e é exatamente por isso que perguntar ao usuário a versão do navegador é um desperdício tão grande de uma ida e volta com o suporte.

4. Identidade e permissões

Você está logado como admin, na sua própria conta, na sua própria organização. O usuário é um membro somente leitura numa equipe de quarenta pessoas, num plano com uma feature flag desligada.

Bugs que dependem do papel do usuário escapam sistematicamente, porque desenvolvedores quase nunca navegam pelo próprio produto como o usuário com menos privilégios.

Assinatura: reproduz para um tipo de usuário e não para outro. Muitas vezes é relatado ao mesmo tempo por várias pessoas com o mesmo papel, o que faz parecer uma regressão repentina, quando está quebrado há meses.

Como pegar: anexe ao relatório o papel do usuário, o plano e o estado das flags. Qualquer SDK decente aceita contexto próprio — no BugDump é identify() para quem é o usuário e setContext() para qualquer estado — e papel e plano são os dois campos que valem a pena anexar antes de qualquer outro.

5. Dados

A conta dele tem um registro com um emoji no nome, ou um null onde você supôs uma string, ou 4.000 linhas onde as suas fixtures têm 12, ou uma moeda sem casas decimais, ou uma data em 1970 porque uma importação deu errado.

Assinatura: reproduz num registro específico e em nenhum outro lugar. Muitas vezes, quem reportou diz «só este projeto faz isso», o que já entrega o diagnóstico inteiro.

Como pegar: o ID do registro e a resposta da API que falhou. Um corpo de resposta com o valor problemático resolve essa categoria na hora — e isso explica boa parte da utilidade dos corpos de resposta e do cuidado que eles exigem, já que o mesmo campo que contém a data malformada contém também todo o resto sobre o usuário.

6. Extensões e scripts injetados

Bloqueadores de anúncio bloqueiam o seu endpoint de analytics, e o seu código não tratou a falha dessa requisição. Gerenciadores de senha injetam DOM no seu formulário e quebram o tratamento de eventos. Extensões de tradução reescrevem nós de texto e despedaçam a reconciliação do React. Proxies corporativos reescrevem respostas.

Assinatura: ninguém consegue reproduzir, nem outros usuários, e o console mostra uma requisição que falhou sem status, ou um erro de DOM apontando para um elemento que você nunca renderizou.

Como pegar: requisições com falha e sem resposta são a pista. Um replay de sessão que mostra um DOM que a sua aplicação não gera também.

7. A própria rede

Portais cativos, interceptação de TLS corporativa, falhas de DNS, um edge de CDN servindo um bundle velho para uma região, um deploy em andamento em que o HTML é novo e o chunk que ele referencia já sumiu.

Assinatura: agrupado no espaço ou no tempo. Vários relatórios numa janela curta, ou todos do mesmo país.

Como pegar: os timestamps e a URL do asset que falhou. Um ChunkLoadError no console é essa categoria quase por definição, e significa que a sua estratégia de deploy apaga os chunks antigos antes que as abas antigas terminem de usá-los.

8. Data e hora

Fusos horários, transições de horário de verão, viradas de mês, formatos de data por localidade e a máquina com o relógio onze minutos errado, o suficiente para um token parecer expirado assim que chega.

Assinatura: reproduz só em certos horários, ou só para usuários de certas regiões, ou só perto do fim do mês.

Como pegar: o offset de fuso horário e a hora local de quem reportou, junto com o timestamp do servidor. É barato de registrar e decide essa categoria à primeira vista.

O método

Diante de um relatório que você não consegue reproduzir, o que ajuda não é se esforçar mais. É encaixá-lo numa das oito categorias acima, e cada uma tem uma pergunta que a identifica rápido:

  • Atualizar a página resolve? → estado

  • É intermitente? → timing

  • É 100% consistente para o usuário? → ambiente

  • Ele tem o mesmo papel ou plano que outras pessoas que reportaram? → identidade

  • É um registro específico? → dados

  • Alguma requisição falhou sem status? → extensões ou rede

  • Está agrupado no tempo ou na geografia? → rede

  • Só em certos horários? → data e hora

Repare que todas essas perguntas são respondidas por dados, não por conversa. E esse é o motivo real de o «não reproduzível» ser tão comum: a evidência que encaixaria o bug na categoria certa estava no navegador do usuário no momento em que ele quebrou, e ninguém a capturou.

Capture a diferença, não a descrição

Vale a pena sistematizar isso porque bugs não reproduzíveis não são casos raros. São os que ficam abertos. Bugs reproduzíveis são corrigidos na mesma semana. O backlog é feito do outro tipo, e o outro tipo é quase inteiramente um problema de dados.

Um relatório que chega com navegador, sistema operacional, viewport, fuso horário, papel do usuário, histórico da sessão, console e log de rede não precisa que ninguém adivinhe a categoria — a diferença entre a máquina dele e a sua está escrita no relatório. É isso que o BugDump captura automaticamente com uma única tag de script, e assim as oito perguntas acima são respondidas antes que alguém precise fazer qualquer uma delas.

A coisa mais rara e valiosa desta lista é a segunda pessoa que reporta. Um relatório não reproduzível é um mistério; três, com três ambientes diferentes anexados, são um experimento controlado — as variáveis que eles compartilham são a causa, e as que não compartilham são ruído. Agrupar relatórios duplicados numa única tarefa não é arrumação. Justamente nos bugs que você não consegue reproduzir, é o diagnóstico.

Leia também