Como adicionar o teste de captura de tela com Cypress ao seu projeto

Os desenvolvedores geralmente se preocupam com a qualidade do seu código. Existem diferentes tipos de testes que nos ajudam a evitar a quebra de código quando um novo recurso é adicionado a um projeto. Mas o que podemos fazer para garantir que os componentes não pareçam diferentes ao longo do tempo?

Nesta postagem, você aprenderá como usar o Cypress para capturar partes das páginas de um site. Depois disso, você integrará a ferramenta de teste no IC para garantir que, no futuro, ninguém faça alterações indesejadas ao seu projeto.

Minha motivação para criar essa estratégia de teste veio do trabalho. At Thinkific temos um sistema interno de design e adicionamos o Cypress para evitar surpresas ao trabalhar em arquivos CSS / JS.

Até o final deste post, teremos PRs com testes Cypress:

Cypress bot

Antes de começarmos

Eu criei um site de amostra para imitar uma biblioteca de componentes. É um site muito simples, criado com o TailwindCSS e hospedado no Vercel. Ele documenta 2 componentes: crachá e botão.

Você pode conferir o Código fonte no GitHub. O site é estático e está dentro do public pasta. Você pode ver o site localmente executando npm run serve e verificando no navegador http: // localhost: 8000.

Exemplo de site

Adicionando o Cypress e o Cypress Image Snapshot

Comece clonando o repositório de exemplo. Em seguida, crie uma nova ramificação e instale Cypress Image Snapshot, o pacote responsável pela captura / comparação de capturas de tela.

git checkout -b add-cypress
npm install -D cypress cypress-image-snapshot

Após adicionar os pacotes, são necessárias algumas etapas extras para adicionar o Cypress Image Snapshot no Cypress.

Crie um cypress/plugins/index.js arquivo com o seguinte conteúdo:

const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin');

module.exports = (on, config) => {
  addMatchImageSnapshotPlugin(on, config);
};

Em seguida, crie um cypress/support/index.js arquivo contendo:

import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';

addMatchImageSnapshotCommand();

Criando o teste de captura de tela

Hora de criar o teste de captura de tela. Aqui está o plano:

  1. O Cypress visitará cada página (crachá e botão) do projeto.
  2. O Cypress fará uma captura de tela de cada exemplo na página. o Página de selo tem 2 exemplos (Padrão e Comprimido), enquanto o Página do botão tem 3 exemplos (padrão, pílula e estrutura de tópicos). Todos esses exemplos estão dentro de um <div> elemento com um cypress-wrapper. Esta classe foi adicionada com a única intenção de identificar o que precisa ser testado.

O primeiro passo é criar o arquivo de configuração do Cypress (cypress.json):

{
  "baseUrl": "http://localhost:8000/",
  "video": false
}

o baseUrl é o site em execução localmente. Como eu mencionei antes, npm run serve servirá o conteúdo do public pasta. A segunda opção, video desativa a gravação de vídeo Cypress, que não usaremos neste projeto.

Hora de criar o teste. No cypress/integration/screenshot.spec.js, adicionar:

const routes = ['badge.html', 'button.html'];

describe('Component screenshot', () => {
  routes.forEach((route) => {
    const componentName = route.replace('.html', '');
    const testName = `${componentName} should match previous screenshot`;

    it(testName, () => {
      cy.visit(route);
  
      cy.get('.cypress-wrapper').each((element, index) => {
        const name = `${componentName}-${index}`;
  
        cy.wrap(element).matchImageSnapshot(name);
      });
    });
  });
});

No código acima, estou criando dinamicamente testes baseados no routes array. O teste criará uma imagem por .cypress-wrapper elemento que a página possui.

Por último, dentro do package.json vamos criar o comando para acionar os testes:

{
  "test": "cypress"
}  

A partir daqui, existem 2 opções: execute o Cypress no modo sem cabeça com npm run cypress run ou use o Cypress Test Runner com npm run cypress open.

Opção sem cabeça

Usando npm run cypress run, a saída deve ser semelhante à próxima imagem:

Saída do primeiro teste

Os testes serão aprovados e 5 imagens serão criadas sob o /snapshots/screenshot.spec.js pasta.

Opção Test Runner

Usando npm run cypress open, O Cypress Test Runner será aberto e você poderá acompanhar os testes passo a passo.

Imagem de Cypress Test Runner

Nosso primeiro marco foi concluído, então vamos mesclar esse ramo para dominar. Se você quiser ver o trabalho feito até agora, pule na minha Solicitação de recebimento.

Usando o Cypress dentro do Docker

Se você executar o teste acima alternando entre o decapitado e o Test Runner, poderá observar que a captura de tela variará.

Usando o Test Runner com um computador com tela retina, você pode obter imagens retina (2x), enquanto o modo sem cabeça não oferece capturas de tela de alta qualidade.

Ademais, é importante dizer que as capturas de tela podem variar de acordo com o seu sistema operacional.

O Linux e o Windows, por exemplo, têm aplicativos com barras de rolagem visíveis, enquanto o macOS oculta a barra de rolagem.

Se o conteúdo capturado na captura de tela não se encaixar em um componente, você pode ou não ter uma barra de rolagem. Se o seu projeto depender de fontes padrão do SO, as capturas de tela também serão diferentes de acordo com o ambiente.

Para evitar essas inconsistências, os testes serão executados no Docker para que o computador do desenvolvedor não afete as capturas de tela.

Vamos começar criando um novo ramo:

git checkout -b add-docker

O Cypress oferece imagens diferentes do Docker – você pode conferir os detalhes em a documentação deles e o blog deles.

Neste exemplo, usarei o cypress/included imagem, que inclui Electron e está pronta para ser usada.

Precisamos fazer duas alterações: alterar o baseUrl no cypress.json Arquivo:

{
  "baseUrl": "http://host.docker.internal:8000/",
}

e a test comando no package.json Arquivo:

{
  "test": "docker run -it -e CYPRESS_updateSnapshots=$CYPRESS_updateSnapshots --ipc=host -v $PWD:/e2e -w /e2e cypress/included:4.11.0"
}

Corrida npm run test nos trará um problema:

Saída do teste

As imagens são um pouco diferentes, mas por quê? Vamos ver o que está dentro do __diff_output__ pasta:

Diferença do botão

Como mencionei anteriormente, inconsistências tipográficas! O componente Button usa a fonte padrão do SO. Como o Docker está sendo executado no Linux, a fonte renderizada não será a mesma que eu instalei no macOS.

Desde que nos mudamos para o Docker, essas capturas de tela estão desatualizadas. Hora de atualizar os instantâneos:

CYPRESS_updateSnapshots=true npm run test

Observe que estou prefixando o comando test com a variável de ambiente CYPRESS_updateSnapshots.

O segundo marco está concluído. Caso precise de ajuda, consulte o meu solicitação de recebimento.

Vamos mesclar esse ramo e seguir em frente.

Adicionando CI

Nosso próximo passo é adicionar os testes no IC. Existem diferentes soluções de CI no mercado, mas para este tutorial, usarei o Semáforo. Não sou afiliado a eles e uso o produto deles no trabalho, por isso foi uma escolha natural para mim.

A configuração é simples e pode ser adaptada a outras soluções, como CircleCI ou Github Actions.

Antes de criarmos o arquivo de configuração do Semáforo, vamos preparar nosso projeto para execução no CI.

O primeiro passo é instalar start-server-and-test. Como o nome do pacote diz, ele iniciará um servidor, aguardará a URL e, em seguida, executará um comando de teste:

npm install -D start-server-and-test

Segundo, edite o package.json Arquivo:

{
  "test": "docker run -it -e CYPRESS_baseUrl=$CYPRESS_baseUrl -e CYPRESS_updateSnapshots=$CYPRESS_updateSnapshots --ipc=host -v $PWD:/e2e -w /e2e cypress/included:4.11.0",
  "test:ci": "start-server-and-test serve http://localhost:8000 test"
}

No test script, estamos adicionando o CYPRESS_baseUrl variável de ambiente. Isso nos permitirá alterar o URL base usado pelo Cypress dinamicamente. Ademais, estamos adicionando o test:ci script, que executará o pacote que acabamos de instalar.

Estamos prontos para o semáforo. Crie o .semaphore/semaphore.yml arquivo com o seguinte conteúdo:

 1 version: v1.0
 2 name: Cypress example
 3 agent:
 4   machine:
 5     type: e1-standard-2
 6     os_image: ubuntu1804
 7 blocks:
 8   - name: Build Dependencies
 9     dependencies: []
10     task:
11       jobs:
12         - name: NPM
13           commands:
14             - sem-version node 12
15             - checkout
16             - npm install
17   - name: Tests
18     dependencies: ['Build Dependencies']
19     task:
20       prologue:
21         commands:
22           - sem-version node 12
23           - checkout
24       jobs:
25         - name: Cypress
26           commands:
27             - export CYPRESS_baseUrl="http://$(ip route | grep -E '(default|docker0)' | grep -Eo '([0-9]+.){3}[0-9]+' | tail -1):8000"
28             - npm run test:ci

Quebrando a configuração em detalhes:

  • As linhas 1-6 definem que tipo de instância usaremos em seu ambiente
  • As linhas 8 e 16 criam 2 blocos: o primeiro bloco, “Build Dependencies” será executado npm install, baixando as dependências que precisamos. O segundo bloco, “Tests”, rodará o Cypress, com algumas diferenças.
  • Na linha 27, estamos definindo dinamicamente o CYPRESS_baseUrl variável de ambiente baseada no IP Docker está usando no momento. Isso substituirá http://host.docker.internal:8000/, que pode não funcionar em todos os ambientes.
  • Na linha 28, finalmente executamos o teste usando start-server-and-test: quando o servidor estiver pronto para as conexões, o Cypress executará o conjunto de testes.

Mais um marco: a hora de fundir nossa filial! Você pode conferir o Solicitar pull que contém todos os arquivos desta seção e verifique o construir dentro do Semáforo.

Gravando os testes no cypress.io

Ler a saída dos testes no IC não é muito amigável. Nesta etapa, integraremos nosso projeto com cypress.io.

As etapas a seguir são baseadas no Documentação do Cypress.

Vamos começar obtendo um ID do projeto e uma chave de registro. No terminal, crie uma nova ramificação e execute:

git checkout -b add-cypress-recording
CYPRESS_baseUrl=http://localhost:8000 ./node_modules/.bin/cypress open

Mencionei anteriormente que estaríamos usando o Cypress dentro do Docker. Mas aqui estamos abrindo o Cypress localmente, pois essa é a única maneira de integrar-se ao painel do site.

Dentro do Cypress, vamos para a guia Execuções, clique em “Configurar projeto para gravar” e escolha um nome e visibilidade. Teremos um projectId adicionado automaticamente no cypress.json arquivo e uma chave de registro privada. Aqui está um vídeo das etapas:

No Semáforo, adicionei a chave de registro como uma variável de ambiente chamada CYPRESS_recordKey. Em seguida, vamos atualizar nosso script de teste para usar a variável:

{
  "test:ci": "start-server-and-test 'serve' 8000 'npm run test -- run --record --key $CYPRESS_recordKey'"
}

Isso é praticamente tudo o que precisa ser feito. No Solicitar pull podemos ver a integração do cypress.io nos comentários. Existe até um link direto que nos leva ao painel deles e mostra todas as capturas de tela. Confira o vídeo abaixo:

Hora de mesclar nosso trabalho, e esse é o fim de nossa integração.

Testando na vida real

Imagine que estamos trabalhando em uma mudança que afeta o preenchimento dos botões: hora de testar se o Cypress captará a diferença.

No site de exemplo, vamos dobrar o preenchimento horizontal de 16 para 32 pixels. Essa alteração é bem simples, pois estamos usando o Tailwind CSS: px-4 é substituído por px-8. Aqui está isso Solicitar pull.

Como poderíamos esperar, Cypress capturou que o botão não corresponde às capturas de tela. Visitando a página, podemos verificar a captura de tela do teste quebrado:

O arquivo diff mostra a captura de tela original à esquerda, o resultado atual à direita e elas são combinadas no meio. Também temos a opção de baixar a imagem para que possamos ver melhor o problema:

Botão antes e depois

Se isso não for um problema, atualize as capturas de tela:

CYPRESS_updateSnapshots=true npm run test

O fim

Por hoje é isso. Espero que você tenha aprendido como o Cypress pode ser útil para garantir que ninguém esteja adicionando alterações inesperadas a um projeto.

Também publicado em meu blog. Se você gosta deste conteúdo, siga-me no Twitter e GitHub.