Testes de Regressão Visual: Obtendo olhos à prova de erros

Bruno Tanoue
Ship It!
Published in
10 min readApr 2, 2018

Escrito por Bruno Tanoue e Vinicius Ortiz

Antes de começar o post, temos um pequeno desafio para você!

Analise as duas imagens acima, tente identificar as seis diferenças e marque o tempo que foi necessário para você ter certeza de que encontrou todas as diferenças. Preparado? Valendo.

Conseguiu achar todas as diferenças? Mantenha anotado o tempo que você levou e o número de diferenças que encontrou. Voltaremos a falar desse problema mais tarde.

Introdução

O mundo perfeito de qualquer desenvolvedor é ter um código bem escrito e de fácil leitura, baixo acoplamento e com testes cobrindo todos os níveis possíveis na pirâmide de testes.

Contudo, esse mundo ideal geralmente não acontece, principalmente pela grande quantidade de código legado e por falhas do passado, como priorizar a entrega rápida em produção (para poder provar valor e ganhar o mercado), em detrimento de uma entrega com um pouco mais de qualidade e mais segura. Sabemos que a entrega rápida também tem um grande valor (que na grande maioria dos casos se justifica), mas às vezes, o tradeoff é grande e um dia os fantasmas do passado voltam para nos assombrar.

Implementações novas em features e correções de bugs para estes casos são coisas bem preocupantes, já que qualquer alteração no código pode causar um efeito colateral em outra parte, que dificilmente imaginaríamos.

Existem casos em que nem mesmo é possível criar toda a base de suíte de testes em um código legado para uma refatoração mais segura. O código está com uma arquitetura tão ruim e acoplada, que se torna impossível até criar uma suíte de testes unitários para cobrí-lo antes da refatoração.

O Problema

Em uma feature comum, uma saída razoável seria cobrir todos os cenários mais usuais com testes de aceitação (testes funcionais). Assim, em qualquer alteração de código, o teste de alto nível falharia se algum cenário principal de uso fosse quebrado, evitando um possível incidente. Claro que isso por si só não evitaria todos os problemas, mas em contrapartida, garantiria que a feature continuasse a funcionar corretamente, mesmo com refatorações complexas e correções de bugs.

E se a feature em questão tiver um comportamento de caráter muito mais visual, com mais foco em cenários extremamente visuais? Mas como assim? Um editor de texto!

Um editor de texto, por exemplo, teria como um fluxo principal:

Contudo, cobrindo este cenário e até pensando em mais alguns outros fluxos principais, ainda não temos a garantia de que a nossa feature está com qualidade suficiente para ser entregue em produção.

Um editor de texto completo (como o Word/Docs por exemplo) pode se tornar algo complexo para validar, já que existem vários utilitários para podermos fazer a customização que quisermos nos textos. Podemos inserir um título, uma fonte de outra cor, uma fonte de outro tamanho, uma imagem, um link, uma tabulação, um espaçamento…Ufa, funcionalidades que nem conseguimos contar!

Além de existirem várias funcionalidades dentro de um editor, todas elas possuem um caráter bem visual, dificultando a validação através de matchers padrões de qualquer framework de testes. Como podemos validar, por exemplo, se ao adicionarmos uma determinada imagem no corpo do texto, ela não apareceu quebrada, ou em um tamanho menor ou maior do que esperávamos? Poderíamos tentar utilizar os matchers padrões também. Mas, como ponto negativo teríamos que usar vários matchers em um único cenário para validar que a imagem foi carregada, largura e altura, por exemplo.

Esse problema pode ser encontrado facilmente em várias outras funcionalidade que lidam com coisas como: dashboards, gráficos e imagens.

Mas como diminuímos os riscos para esses casos? Deixamos essa bola de neve aumentar cada vez mais?

O que é um teste de regressão visual?

Testes de regressão visual (Visual Regression Tests/VRT) são testes de alto nível que conseguem expor qualquer alteração visual de um sistema, seja em uma página específica ou em um determinado fluxo que o usuário irá percorrer. Para que isso seja feito, esse tipo de teste faz uma captura da página que está sendo testada e compara este screenshot com sua versão estável (baseline).

O primeiro passo é automatizar as ações do usuário utilizando alguma ferramenta como Selenium ou Capybara. Com as ações do usuário automatizadas, podemos configurar nosso teste para realizar quantas capturas de tela quisermos a cada momento que seja necessário garantir que não existam mudanças indesejáveis na página (ou em um ponto específico dentro deste fluxo). Assim, garantimos que páginas com comportamento dinâmico, baseado em ações do usuário também estejam cobertas por esse tipo de teste.

No exemplo do editor de texto, digamos que ao carregar a barra de tarefas, o ícone de alinhamento de texto não esteja visível para o usuário, por alguma alteração feita em nosso css. Mesmo que seja uma mudança pequena de estilo, difícil de ser vista pelo fato da nossa barra de tarefas possuir inúmeros outros botões, isso não deixa de ser um problema grave para o usuário, praticamente escondendo uma funcionalidade e o impedindo de usá-la. Neste caso, nosso teste de regressão visual irá falhar ao fazer a validação desta página/barra de tarefas, durante a comparação com a baseline.

Se o nosso editor de texto, durante uma refatoração de código, não possuir nenhum tipo de teste para garantir de forma rápida que todas nossas funcionalidades continuam saudáveis e sem efeitos indesejados, regressão visual é algo que precisamos para que toda pirâmide de testes seja construída!

Escolhendo a ferramenta

Nos dias atuais, temos uma grande variedade de produtos e bibliotecas voltados para a implementação de testes de regressão visual, cada um com suas características, pontos fortes e pontos fracos. Vamos citar aqui algumas delas:

Aqui na Resultados Digitais, durante nossa implementação de testes de regressão visual optamos por realizar duas POCs envolvendo o Applitools e o Percy, por serem ferramentas que possuem uma curva de implementação mais rápida e por contarem com uma gama maior de funcionalidades, já que são ferramentas comercializadas com foco em “menos configuração, mais resultados”.

O Percy possui alguns pontos positivos, como uma integração com o github bem interessante que possibilita ligar seus testes em seu build de maneira praticamente plug-and-play. Faz o processo de tirar screenshots e comparar com sua baseline (ou ambiente de CI, se estiver executando no build), através de envio de código (html/css/etc) renderizado para a API, o que torna o processo um pouco mais rápido. Porém, suas configurações são mais básicas e muitas vezes executamos métodos alternativos (envolvendo linhas de código) para exclusão de regiões para comparação ou comparações de screenshots entre branches diferentes.

O Applitools trabalha através de uma API (Applitools Eyes) que irá receber as screenshots(geradas localmente no ambiente de desenvolvimento/CI) e executará um algoritmo de comparação, realizando um match entre a nova imagem e a imagem base. Logo depois, um retorno é realizado, dizendo se a comparação foi bem sucedida ou não, junto com um link para acessar as informações do teste em questão na ferramenta.

Caso a alteração de código tiver a intenção de alterar algum componente visual da página, o teste quebrará também. Você pode aprovar este último screenshot como um novo baseline diretamente na interface da ferramenta, através de um simples clique. Assim, todos os testes seguintes farão comparações com este novo padrão que você configurou.

É possível também ignorar certas áreas dentro de um snapshot. Caso uma página da sua aplicação, por exemplo, possua um campo do tipo captcha que terá um valor diferente todas as vezes que a página for aberta, essa área pode ser ignorada através de um simples “clique e arraste” na tela de resultado de execução do teste, no próprio Applitools. É extremamente fácil ignorar regiões que não são necessárias e nem devem ser observados dentro de uma página.

Na imagem abaixo, por exemplo, o retângulo de contorno preto é a região que estamos ignorando nos nossos testes.

Decidimos pelo Applitools, por ser uma solução mais completa para o nosso contexto, mas cabe a você decidir qual ferramenta escolher, baseado no seu orçamento e na complexidade do seu problema.

Implementação

Implementamos os Testes de Regressão Visual utilizando a stack Ruby/Rails, Rspec e a gem eyes_selenium.

Começamos utilizando o Capybara para nos ajudar a automatizar o testes (porque já trabalhamos com ela na criação dos testes de aceitação) e realizamos o tutorial do próprio Applitools para nos familiarizarmos com a ferramenta. Tudo foi muito simples, mas tivemos um grande aprendizado inicial: “Treino é treino, jogo é jogo”.

Temos dois grandes aprendizados que gostaríamos de compartilhar com vocês!

A sdk do eyes para Ruby não se comporta muito bem com o Capybara para eventos de navegação e redirecionamento para uma nova aba:

Essa linha de código retorna o seguinte erro:

O método que deveria implementar essa ação de troca de aba não se encontra implementado até o momento na sdk!

Levamos um certo tempo para descobrir isso e gostaríamos que você não tivesse o mesmo problema!

Baseado no nosso impedimento, já que as ações de troca de janela no nosso cenário eram importantes, resolvemos tentar a sorte com o Selenium e tivemos uma sorte melhor. A gem de VRT com o Selenium se comportou bem para esses eventos que eram tão importantes para os nossos cenários.

O segundo aprendizado é relacionado ao método de execução do navegador: real/visual ou headless. Começamos utilizando o Chrome em modo visual, porque em tese teríamos uma curva de aprendizado mais fácil, podendo sempre verificar todas as ações que estavam sendo executadas na tela. Assim, conseguimos verificar quando nossos testes quebravam mais facilmente do que em um modo de execução em memória (headless). Mas, percebemos que o processo de tirar screenshots e enviar para o Applitools estava um pouco instável, nos mostrando vários falsos positivos. O motivo disso era que, como nós estávamos desenvolvendo os testes juntos e em máquinas diferentes, executamos os testes em tamanhos de tela distintas. Mesmo que definíssemos um tamanho específico de execução nas configurações do teste (como tela cheia ou um tamanho de tela maior do que nosso monitor, por exemplo), algumas vezes a tela do navegador se adaptava ao tamanho da nossa tela, tirando screenshots em tamanhos diferentes, resultando em falsos positivos.

Resolvemos tentar a sorte com o Chrome em modo headless e também tivemos um sucesso melhor, já que em modo memória, o navegador não executava essas adaptações que navegador em modo visual realizava. As execuções se mostraram constantes, acabando com nossos falsos positivos e mostrando erros que realmente importavam.

Resultados

Utilizamos o Visual Regression Test como um piloto para realizarmos uma refatoração de frontend da nossa feature de Landing Pages e o resultado foi promissor. Como essa feature possui um código legado grande e é extremamente visual, achamos melhor ter uma garantia de cobertura de alto nível desse tipo, além de cobrir os critérios de aceite.

A cada pequena refatoração e commit de código, o build executava os testes e nos dava a sensação de segurança de que nenhum pixel foi quebrado e nenhum bug novo foi criado nas nossas telas.

Conseguimos refatorar, criar uma base um pouco mais confiável de testes e subir nossas alterações em um ponto crítico do nosso sistema com uma segurança maior de que nossas alterações não impactariam nossos usuários.

O principal ganho foi o feedback rápido que esse tipo de teste nos fornece. Olhar atentamente todos os pontos de uma tela visual e verbosa é cansativo, demora e é muito suscetível a erros (principalmente pelos vícios que vamos ganhando conforme testamos várias vezes a mesma tela manualmente).

Apesar de algumas funcionalidades do nosso editor serem mais secundárias, não fazendo parte de fluxos principais e critérios de aceite, elas são relevantes para a máxima customização de Landing Pages de alguns dos nossos clientes e por isso consideramos tão importante quanto. O melhor disso é que podemos validar a tela inteira com um único matcher.

Conclusão

Lembra da imagem do início do post? Quantos erros você encontrou? Quanto tempo você levou para encontrar todos? Em termos visuais, as duas imagens possuem um total de 25000 pixels diferentes. Com um teste de regressão visual essa análise levou menos de 2 segundos. As diferenças estão marcadas em rosa.

Os testes de regressão visual podem ser uma adição de muito valor à suíte de testes em determinados contextos em que o comportamento visual da feature é relevante, falhando em qualquer pequeno pixel que esteja diferente da baseline original. Além disso, melhora a produtividade de uma maneira geral, já que testes manuais cansativos de validação de layouts, por exemplo, são feitos de maneira automatizada e são mais seguros por não estarem suscetíveis a falhas humanas.

Contudo, mesmo que esse teste seja mais rápido do que o olhar humano, ele é um teste de alto nível que possui um custo maior e um tempo considerável de build em relação aos demais testes (unitário, integração). É necessário ponderar e ver se esse tipo de teste é relevante para o seu contexto, pensando também se um teste de aceitação comum já não seria o suficiente.

E você, o que acha de testes de regressão visual? Já chegou a trabalhar com esse tipo de teste em algum momento?

Referências

--

--

Bruno Tanoue
Ship It!

Quality Assurance Engineer @ Resultados Digitais