Escrevemos testes de integração de front-end e agilizamos lançamentos

Olá! Meu nome é Vova, sou front-end na Tinkoff. Nossa equipe é responsável por dois produtos para pessoas jurídicas. Posso dizer sobre o tamanho do produto em números: uma regressão completa de cada um dos produtos por dois testadores leva três dias (sem a influência de fatores externos).



O prazo é significativo e implora para ser tratado. Existem várias maneiras de lutar, as principais são:



  • Cortando a aplicação em produtos menores com seus próprios ciclos de liberação.

  • Cobertura do produto com testes de acordo com a pirâmide de teste.



O último ponto se tornou o tópico do meu artigo.



imagem



Pirâmide de teste



Como sabemos, existem três níveis na pirâmide de testes: testes de unidade, testes de integração e testes e2e. Eu acho que muitos estão familiarizados com as unidades, assim como com o e2e, então vou me concentrar nos testes de integração com mais detalhes.



Como parte do teste de integração, verificamos a operação de todo o aplicativo por meio da interação com a interface do usuário, no entanto, a principal diferença dos testes do e2e é que não fazemos solicitações reais de backup. Isso é feito para verificar apenas a interação de todos os sistemas na frente, a fim de reduzir o número de testes e2e no futuro.



Para escrever testes de integração, usamos o Cypress. Neste artigo, não o compararei com outras estruturas, apenas direi por que acabamos com isso:



  1. Documentação muito detalhada.

  2. Depuração fácil de testes (o Cypress criou uma GUI especial para isso com as etapas de viagem no tempo no teste).



Esses pontos foram importantes para nossa equipe, pois não tínhamos experiência em escrever testes de integração e era necessário um início muito simples. Neste artigo, quero falar sobre o caminho que percorremos, quais solavancos foram preenchidos e compartilhar receitas para implementação.



O começo do caminho



No começo, usei o Angular Workspace com um aplicativo para organizar o código. Depois de instalar o pacote Cypress, uma pasta cypress com configuração e testes apareceu na raiz do aplicativo, paramos nessa opção. Ao tentar preparar o script no package.json necessário para executar o aplicativo e executar testes sobre ele, encontramos os seguintes problemas:



  1. Index.html contém alguns scripts que não são necessários nos testes de integração.

  2. Para executar os testes de integração, era necessário verificar se o servidor com o aplicativo estava em execução.



O problema com o index.html foi resolvido através de uma configuração de compilação separada - vamos chamá-la de sypress - na qual especificamos um index.html personalizado. Como implementar isso? Encontramos a configuração do seu aplicativo em angular.json, abrimos a seção de compilação, adicionamos uma configuração separada para o Cypress e não se esqueça de especificar essa configuração para o modo servir.



Exemplo de configuração para construção:



"build": {
 ...
 "configurations": {
   … //  
   "cypress": {
     "aot": true,
     "index": "projects/main-app-integrations/src/fixtures/index.html",
     "fileReplacements": [
       {
         "replace": "projects/main-app/src/environments/environment.ts",
         "with": "projects/main-app/src/environments/environment.prod.ts"
       }
     ]
   }
 }
}


Servir integração:



"serve": {
 ...
 "configurations": {
   … //  
   "cypress": {
     "browserTarget": "main-app:build:cypress"
   }
 }
}


Do principal: para a configuração do cypress, especificamos um assembly aot e substituímos os arquivos pelo ambiente - isso é necessário para criar assemblies semelhantes a produtos durante o teste.



Portanto, com o index.html resolvido, resta aumentar o aplicativo, aguardar a conclusão da compilação e executar os testes sobre ela. Para fazer isso, use a biblioteca start-server-and-test e escreva scripts com base nela:



 "main-app:cy:run": "cypress run",
 "main-app:cy:open": "cypress open",
 "main-app:integrations": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:run",
 "main-app:integrations:open": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:open"


Como você pode ver, existem dois tipos de scripts: abrir e executar. O modo aberto abre a GUI do próprio Cypress, onde você pode alternar entre testes e usar a viagem no tempo. O modo de execução é apenas uma execução de teste e obtém o resultado final dessa execução, ótimo para executar no IC.



Com base nos resultados do trabalho realizado, conseguimos obter uma estrutura inicial para escrever o primeiro teste e executá-lo no IC.



Monorepositório



A abordagem descrita tem um problema muito evidente: se o repositório tiver dois ou mais aplicativos, a abordagem com uma pasta não é viável. E assim aconteceu conosco. Mas isso aconteceu de uma maneira bastante interessante. No momento da introdução do Cypress, estávamos nos mudando para o NX, e esse belo acessório permite que você trabalhe com o Cypress. Qual é o princípio do trabalho:



  1. Você tem um aplicativo como main-app, ao lado dele é criado o aplicativo main-app-e2e.

  2. Renomeie main-app-e2e para main-app-integrations - você é incrível.



Agora você pode executar testes de integração com um comando - ng e2e main-app-integrations. O NX exibirá o aplicativo principal automaticamente, aguardará uma resposta e executará os testes.



Infelizmente, aqueles que estão atualmente usando o Angular Workspace permaneceram à margem, mas tudo bem, eu também tenho uma receita para você. Usaremos a estrutura do arquivo como no NX:



  1. Crie a pasta main-app-integrations ao lado do seu aplicativo.

  2. Crie uma pasta src nela e adicione o conteúdo da pasta cypress.

  3. Não se esqueça de mover o cypress.json (inicialmente ele aparecerá na raiz) para a pasta main-app-integrations.

  4. Edite cypress.json, especificando caminhos para novas pastas com testes, plugins e comandos auxiliares (parâmetros integrationFolder, pluginsFile e supportFile).

  5. O Cypress pode trabalhar com testes em qualquer pasta, o parâmetro do

    projeto é usado para especificar a pasta ; portanto, alteramos o comando de cypress run / open para cypress run / open-–project ./projects/main-app-integrations/src .



A solução para o Angular Workspace é mais semelhante à solução para o NX, exceto pelo fato de criarmos a pasta manualmente e esse não é um dos projetos em seu repositório mono. Como alternativa, você pode usar diretamente o construtor NX para Cypress ( um exemplo de repositório no NX com Cypress, é possível ver o uso final do construtor nx-cypress - atenção ao angular.json e ao projeto

cart-e2e e products-e2e).



Regressão visual



Após os cinco primeiros testes, pensamos no teste de captura de tela, porque, de fato, existem todas as possibilidades para isso. Eu digo com antecedência que a palavra "teste de captura de tela" causa muita dor na equipe, pois o caminho para obter testes estáveis ​​não foi o mais fácil. A seguir, descreverei os principais problemas que encontramos e sua solução.



A biblioteca cypress-image-snapshot foi tomada como uma solução . A implementação não demorou muito tempo e agora, após 20 minutos, recebemos a primeira captura de tela do nosso aplicativo com um tamanho de 1000 × 600 px. Havia muita alegria porque a integração e o uso eram fáceis demais e os benefícios poderiam ser enormes.



Depois de gerar cinco capturas de tela de referência, lançamos um teste no CI, como resultado, a compilação desmoronou. Acontece que as capturas de tela criadas usando os comandos abrir e executar são diferentes. A solução foi bem simples: faça capturas de tela apenas no modo CI, por isso removemos as capturas de tela no modo local, por exemplo:



Cypress.Commands.overwrite(
   'matchImageSnapshot',
   (originalFn, subject, fileName, options) => {
       if (Cypress.env('ci')) {
           return originalFn(subject, fileName, options);
       }

       return subject;
   },
);


Nesta solução, examinamos o parâmetro env no Cypress, você pode configurá-lo de diferentes maneiras.



Fontes



Localmente, os testes começaram a passar no reinício, tentamos executá-los novamente no IC. O resultado pode ser visto abaixo:







É bastante simples notar a diferença nas fontes na captura de tela do diff. A captura de tela de referência foi gerada no macOS e, no CI, os agentes instalaram o Linux.



Decisão errada



Nós escolhemos uma das fontes padrão (como se fosse Ubuntu Font), que apresentava uma diferença mínima por pixel, e aplicamos essa fonte para blocos de texto (feitos em

index.html, destinado apenas a testes de ciprestes). Em seguida, aumentamos a diferença geral para 0,05% e a diferença por pixel para 20%. Vivemos com esses parâmetros por uma semana - até o primeiro caso, quando foi necessário alterar o texto no componente. Como resultado, a construção permaneceu verde, embora não tenhamos atualizado a captura de tela. A solução atual se mostrou inútil.



Solução correta



O problema original estava em ambientes diferentes, a solução em princípio sugere a si mesma - Docker. O Cypress já tem imagens do docker prontas. Estamos interessados ​​em incluir diferentes variações das imagens, pois o Cypress já está incluído na imagem e não baixa e descompacta o binário do Cypress sempre (a GUI do Cypress é executada em um arquivo binário , e o download e a descompactação levam mais tempo do que o download imagem da janela de encaixe).

Com base na imagem da janela de encaixe incluída, criamos nosso próprio contêiner de janela de encaixe, para isso, fizemos um arquivo de Integration-tests.Dockerfile com conteúdo semelhante:



FROM cypress:included:4.3.0
COPY package.json /app/
COPY package-lock.json app/
WORKDIR /app
RUN npm ci
COPY / /app/
ENTRYPOINT []


Gostaria de observar o zeramento de ENTRYPOINT, devido ao fato de que ele é definido por padrão na imagem cypress / incluída e aponta para o comando cypress run, o que nos impede de usar outros comandos. Também dividimos nosso arquivo docker em camadas para que, toda vez que você reinicie os testes, não seja necessário executar o npm ci novamente.



Nós adicionamos o arquivo .dockerignore (se não estiver) à raiz do repositório e devemos especificar node-modules / e * / node-modules / nele.



Para executar nossos testes no Docker, vamos escrever um script bash integration-tests.sh com o seguinte conteúdo:



docker build -t integrations -f integration-tests.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


Breve descrição: Criamos nosso contêiner do dockerfile de integração-tests.Dockerfile e apontamos o volume para a pasta de testes, para que possamos obter as capturas de tela geradas no Docker.



Fontes novamente



Depois de resolver o problema descrito no capítulo anterior, houve uma pausa nas compilações, mas um dia depois encontramos o seguinte problema (capturas de tela esquerda e direita de um componente tirado em momentos diferentes):







Acho que os mais atentos perceberam que não há título suficiente no pop-up ... O motivo é muito simples - a fonte não teve tempo de carregar, pois não estava conectada via ativos, mas estava localizada na CDN.



Decisão errada



Faça o download de fontes da CDN, coloque-as em ativos para configuração de ciprestes e, em nosso

index.html personalizado, para testes de integração, nós as conectamos. Vivemos com essa decisão por um tempo decente, até mudarmos a fonte corporativa. Não havia desejo de reproduzir a mesma história uma segunda vez.



Solução correta



Decidiu-se iniciar o pré-carregamento de todas as fontes necessárias para o teste em

index.html para a configuração do cipreste.



<link	
      rel="preload"
      href="...."	
      as="font"	
      type="font/woff2"	
      crossorigin="anonymous"
/>


O número de falhas de teste devido a fontes que não tiveram tempo para carregar diminuiu para um mínimo, mas não para zero: de qualquer maneira, às vezes, a fonte não tem tempo para carregar. Uma solução da KitchenSink do Cypress veio em socorro - waitForResource.

No nosso caso, como a pré-carga da fonte já estava conectada, simplesmente redefinimos o comando visit no Cypress, como resultado, ele não apenas navega até a página, mas também aguarda o carregamento das fontes especificadas. Eu também gostaria de acrescentar que waitForResource resolve o problema não apenas das fontes, mas também de todas as estatísticas carregadas, como imagens (por causa delas, as capturas de tela também foram quebradas e o waitForResource ajudou muito). Depois de aplicar esta solução, não houve problemas com fontes e estática de carregamento.



Animações



Nossa dor de cabeça está relacionada com animações, que permanecem até hoje. Em algum momento, as capturas de tela do elemento começarão a aparecer ou uma captura de tela foi tirada antes do início da animação. Tais capturas de tela são instáveis ​​e, a cada próxima comparação com o padrão, haverá diferenças. Então, por que caminho seguimos ao resolver o problema relacionado às animações?



Primeira solução



A coisa mais simples que veio à nossa mente no estágio inicial: antes de criar uma captura de tela, pare o navegador por um certo tempo para que as animações tenham tempo de concluir. Percorremos a cadeia 100ms, 200ms, 500ms e, como resultado, 1000ms. Olhando para trás, entendo que essa decisão foi inicialmente terrível, mas eu só queria alertá-lo contra a mesma decisão. Por que horrível? O tempo de animação é diferente; os agentes no IC também podem ser reproduzidos algumas vezes, e é por isso que o tempo de espera para estabilização da página de tempos em tempos era diferente.



Segunda solução



Mesmo com uma espera de 1 segundo, a página nem sempre conseguiu se tornar estável. Após uma pequena pausa, encontramos a ferramenta em Angular - Testabilidade. O princípio é baseado no rastreamento da estabilidade do ZoneJS:



Cypress.Commands.add('waitStableState', () => {
   return cy.window().then(window => {
       const [testability]: [Testability] = window.getAllAngularTestabilities();

       return new Cypress.Promise(resolve => {
           testability.whenStable(() => {
               resolve();
           }, 3000);
       });
   });
});


Assim, ao criar capturas de tela, chamamos dois comandos: cy.wait (1000) e cy.waitStableState ().



Desde então, não houve uma única captura de tela descartada aleatoriamente, mas vamos contar juntos quanto tempo foi gasto no navegador inativo. Suponha que você tenha 5 capturas de tela tiradas no teste, para cada um há um tempo de espera estável de 1 segundo e algum tempo aleatório, suponha 1,5 segundos em média (eu não medi o valor médio na realidade, então tirei da cabeça de acordo com meus próprios sentimentos) ... Como resultado, gastamos mais 12,5 segundos para criar capturas de tela no teste. Digamos que você já tenha escrito 20 cenários de teste, nos quais cada teste contém pelo menos 5 capturas de tela. Concluímos que o pagamento em excesso pela estabilidade é de ~ 4 minutos com 20 testes disponíveis. 



Mas este nem é o maior problema. Como discutido acima, ao executar testes localmente, as capturas de tela não são perseguidas, mas no IC elas são perseguidas e, devido às expectativas, retornos de chamada no código, por exemplo, no tempo de rebounce, foram acionados para cada captura de tela, que já criava randomização nos testes, porque no CI e localmente eles passou de maneiras diferentes.



Solução atual



Vamos começar com animações angulares. Nossa estrutura favorita durante a animação no elemento DOM trava a classe ng-animating. Essa foi a chave da nossa solução, porque agora precisamos garantir que não haja classe de animação no elemento agora. Como resultado, resultou em uma função:



export function waitAnimation(element: Chainable<JQuery>): Chainable<JQuery> {
   return element.should('be.visible').should('not.have.class', 'ng-animating');
}


Parece nada complicado, mas foi isso que formou a base de nossas decisões. O que quero prestar atenção nessa abordagem: ao criar uma captura de tela, você deve entender qual animação do elemento pode tornar sua captura de tela instável e, antes de criar uma captura de tela, adicionar uma asserção que verifique se o elemento não está animado. Mas as animações também podem estar em CSS. Como o próprio Cypress diz, qualquer afirmação sobre um elemento aguarda a conclusão da animação - mais informações aqui e aqui . Ou seja, a essência da abordagem é a seguinte: temos um elemento animado, adicionamos uma asserção - should ('be.visible') / should ('not.be.visible')- e o próprio Cypress aguardará a conclusão da animação no elemento (talvez, a propósito, não seja necessária uma solução com animação em ng e apenas as verificações do Cypress sejam suficientes, mas, por enquanto, usamos o utilitário - waitAnimation).



Conforme declarado na própria documentação, o Cypress verifica se há uma alteração na posição de um elemento em uma página, mas nem todas as animações sobre a mudança de posição, também existem animações fadeIn / fadeOut. Nesses casos, o princípio da solução é o mesmo: verificamos se o elemento está visível / invisível na página. 



Ao passar de cy.wait (1000) + cy.waitStableState () para waitAnimation e Cypress Assertion, tivemos que gastar ~ 2 horas para estabilizar capturas de tela antigas, mas, como resultado, obtivemos + 20 a 30 segundos em vez de +4 minutos para o tempo de execução do teste ... No momento, estamos abordando cuidadosamente a revisão das capturas de tela: verificamos que elas não foram executadas durante a animação dos elementos DOM e adicionamos verificações no teste para aguardar animação. Por exemplo, geralmente adicionamos a exibição de "esqueletos" à página antes dos dados serem carregados. Portanto, a revisão recebe imediatamente um requisito de que, ao criar capturas de tela, um esqueleto não esteja presente no DOM, pois há uma animação de desbotamento. 



O problema dessa abordagem é um: nem sempre é possível prever tudo ao criar uma captura de tela e ainda pode cair no IC. Há apenas uma maneira de lidar com isso - vá e edite imediatamente a criação de uma captura de tela, você não pode adiar, caso contrário, ela se acumulará como uma bola de neve e, no final, você simplesmente desligará os testes de integração.



Tamanho da captura de tela



Você deve ter notado um recurso interessante: a resolução padrão das capturas de tela é 1000 × 600 px. Infelizmente, há um problema com o tamanho da janela do navegador ao iniciar no Docker: mesmo que você altere o tamanho da janela de exibição através do Cypress, isso não ajudará. Encontramos uma solução para o navegador Chrome (para a Electron, não conseguimos encontrar rapidamente uma solução funcional e não conseguimos a proposta nesta edição ). Primeiro, você precisa alterar o navegador para executar testes no Chrome:



  1. Não para o NX, fazemos isso usando o argumento --browser chrome ao iniciar o comando cypress open / run e, para o comando run, especificamos o parâmetro --headless.

  2. Para o NX, na configuração do projeto em angular.json com testes, especificamos o parâmetro browser: chrome e, para a configuração que será executada no CI, especificamos headless: true.



Agora, fazemos as alterações nos plugins e obtemos capturas de tela com um tamanho de 1440 × 900 px:



module.exports = (on, config) => {
   on('before:browser:launch', (browser, launchOptions) => {
       if (browser.name === 'chrome' && browser.isHeadless) {
           launchOptions.args.push('--disable-dev-shm-usage');
           launchOptions.args.push('--window-size=1440,1200');

           return launchOptions;
       }

       return launchOptions;
   });
};


datas



Tudo é simples aqui: se em algum lugar a data associada à corrente for exibida, a captura de tela feita hoje cairá amanhã. Fixim é simples:



cy.clock(new Date(2025, 11, 22, 0).getTime(), ['Date']);


Agora os temporizadores. Não nos incomodamos e usamos a opção de blecaute ao criar capturas de tela, por exemplo:



cy.matchImageSnapshot('salary_signing-several-payments', {
   blackout: ['.timer'],
});


Testes escamosos



Usando as recomendações acima, você pode obter a estabilidade máxima do teste, mas não 100%, porque os testes são afetados não apenas pelo seu código, mas também pelo ambiente em que são executados.



Como resultado, uma certa porcentagem de testes ocasionalmente cairá, por exemplo, devido a uma queda no desempenho do agente no IC. Antes de tudo, estabilizamos o teste do nosso lado: adicionamos as asserções necessárias antes de fazer capturas de tela, mas durante o período de reparo desses testes, você pode tentar novamente os testes com falha usando tentativas de plugins de ciprestes.



Nós bombeamos CI



Nos capítulos anteriores, aprendemos como executar testes com um comando e aprendemos sobre como trabalhar com o teste de captura de tela. Agora podemos olhar para a otimização de IC. Nossa compilação será definitivamente executada:



  1. Comando Npm ci.
  2. Aumentando o aplicativo no modo aot.
  3. Execute testes de integração.


Vamos dar uma olhada nos primeiro e segundo pontos e entender que etapas semelhantes são executadas em sua outra compilação no IC - compilação com o conjunto de aplicativos.

A principal diferença não é executar ng serve, mas construir. Portanto, se podemos obter um aplicativo já montado em uma compilação com testes de integração e elevar o servidor com ele, podemos reduzir o tempo de execução da compilação com testes.



Por que precisamos disso? É que nossa aplicação é grande e satisfatória

O npm ci + npm inicia no modo aot no agente no IC levou aproximadamente 15 minutos, o que, em princípio, exigia muito esforço do agente, e os testes de integração foram executados sobre isso. Suponha que você já tenha gravado mais de 20 testes e no 19º teste o navegador no qual os testes são executados trava devido à carga pesada no agente. Como você entende, reiniciar a construção está novamente aguardando a instalação de dependências e o lançamento do aplicativo.



A partir de agora, falarei apenas de scripts do lado do aplicativo. Você precisará resolver o problema de transferência de artefatos entre tarefas no IC, portanto, tenha em mente que a nova compilação com testes de integração terá acesso ao aplicativo montado a partir da tarefa na compilação do seu aplicativo.



Servidor estático



Precisamos de uma substituição para ng serve para aumentar o servidor com nosso aplicativo. Existem muitas opções, começarei com o nosso primeiro - angular-http-server . Não há nada complicado em sua configuração: instalamos a dependência, indicamos em qual pasta nossas estatísticas estão localizadas, indicam em qual porta aumentar o aplicativo e estamos felizes.



Essa solução foi suficiente para nós por 20 minutos e, em seguida, percebemos que queríamos fazer proxy de algumas solicitações para o circuito de teste. Falha na conexão do proxy para o servidor http angular. A solução final foi aumentar o servidor para o Express . Para resolver o problema, usamos express e express-http-proxy em si. Distribuiremos nossas estatísticas usando

express.static, como resultado, obteremos um script semelhante a este:



const express = require('express');
const appStaticPathFolder = './dist';
const appBaseHref = './my/app';
const port = 4200;
const app = express();

app.use((req, res, next) => {
   const accept = req
       .accepts()
       .join()
       .replace('*/*', '');

   if (accept.includes('text/html')) {
       req.url = baseHref;
   }

   next();
});
app.use(appBaseHref, express.static(appStaticPathFolder));
app.listen(port);


O ponto interessante aqui é que, antes de ouvir a rota na baseHref do aplicativo, também processamos todas as solicitações e procuramos por index.html. Isso é feito nos casos em que os testes vão para uma página do aplicativo cujo caminho é diferente de baseHref. Se você não fizer esse truque, quando você for a qualquer página do seu aplicativo, exceto a principal, um erro 404 chegará. Agora vamos adicionar uma pitada de proxy:



const proxy = require('express-http-proxy');

app.use(
   '/common',
   proxy('https://qa-stand.ru', {
       proxyReqPathResolver: req => '/common' + req.url,
   }),
);


Vamos dar uma olhada no que está acontecendo. Existem constantes:



  1. appStaticForlderPath é a pasta onde estão localizadas as estáticas do seu aplicativo. 
  2. appBaseHref - seu aplicativo pode ter um baseHref; caso contrário, você pode especificar '/'.


Procuramos proxy em todas as solicitações começando com / common e, ao fazer a proxy, salvamos o mesmo caminho que a solicitação tinha usando a configuração proxyReqPathResolver. Se você não usá-lo, todas as solicitações irão simplesmente para https://qa-stand.ru.



Personalização de Index.html



Precisávamos resolver o problema com o index.html personalizado que usamos quando o ng serve o aplicativo no modo Cypress. Vamos escrever um script simples em node.js. Tínhamos index.modern.html como parâmetros iniciais, precisávamos transformá-lo em index.html e remover scripts desnecessários de lá:



const fs = require('fs');
const appStaticPathFolder = './dist';

fs.copyFileSync(appStaticPathFolder + '/index.modern.html', appStaticPathFolder + '/index.html');

fs.readFile(appStaticPathFolder + '/index.html', 'utf-8', (err, data) => {
   const newValue = data
       .replace(
           '<script type="text/javascript" src="/auth.js"></script>',
           '',
       )
       .replace(
           '<script type="text/javascript" src="/analytics.js"></script>',
           '',
       );

   fs.writeFileSync(appStaticPathFolder + '/index.html', newValue, 'utf-8');
});


Scripts



Eu realmente não queria fazer o npm ci de todas as dependências novamente para executar testes no CI (afinal, isso já foi feito na tarefa com a compilação do aplicativo), então surgiu a idéia de criar uma pasta separada para todos esses scripts com nosso próprio package.json. Vamos nomear a pasta, por exemplo, integração-testes-scripts e soltar três arquivos lá: server.js, create-index.js, package.json. Os dois primeiros arquivos foram descritos acima, agora vamos analisar o conteúdo do package.json:



{
 "name": "cypress-tests",
 "version": "0.0.0",
 "private": true,
 "scripts": {
   "create-index": "node ./create-index.js",
   "main-app:serve": "node ./server.js",
   "main-app:cy:run": "cypress run --project ./projects/main-app-integrations ",
   "main-app:integrations": "npm run create-index && start-server-and-test main-app:serve http://localhost:4200/my/app/ main-app:cy:run"
 },
 "devDependencies": {
   "@cypress/webpack-preprocessor": "4.1.0",
   "@types/express": "4.17.2",
   "@types/mocha": "5.2.7",
   "@types/node": "8.9.5",
   "cypress": "4.1.0",
   "cypress-image-snapshot": "3.1.1",
   "express": "4.17.1",
   "express-http-proxy": "^1.6.0",
   "start-server-and-test": "1.10.8",
   "ts-loader": "6.2.1",
   "typescript": "3.8.3",
   "webpack": "4.41.6"
 }
}


O package.json contém apenas as dependências necessárias para executar testes de integração ( com suporte para testes de texto e de tela) e scripts para iniciar o servidor, criando index.html e o start-server-and-test conhecido no capítulo sobre execução de testes de integração no Angular Workspace ...



Lançamento



Envolvemos a execução de testes de integração em um novo Dockerfile - integration-tests-ci.Dockerfile :



FROM cypress/included:4.3.0
COPY integration-tests-scripts /app/
WORKDIR /app
RUN npm ci
COPY projects/main-app-integrations /app/projects/main-app-integrations
COPY dist /app/dist
COPY tsconfig.json /app/
ENTRYPOINT []


A linha inferior é simples: copie e expanda a pasta de integração-testes-scripts para a raiz do aplicativo e copie tudo o que é necessário para executar os testes (esse pacote pode ser diferente para você). As principais diferenças do arquivo anterior são que não copiamos todo o aplicativo dentro do contêiner do docker, apenas otimização mínima do tempo de execução do teste no IC.



Crie um arquivo integration-tests-ci.sh com o seguinte conteúdo:



docker build -t integrations -f integration-tests-ci.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


Quando o comando com testes é executado, o package.json da pasta integration-tests-scripts se tornará a raiz e o comando main-app: integrations será iniciado nele. Assim, como essa pasta se expandirá para a raiz, os caminhos para a pasta com as estáticas do seu aplicativo devem ser indicados com a ideia de que tudo será iniciado a partir da raiz e não da pasta de integração-testes-scripts.



Também quero fazer uma pequena observação: chamei o script bash final para executar testes de integração, pois evoluía de maneira diferente. Você não precisa fazer isso, isso foi feito apenas para a conveniência de ler este artigo. Você sempre deve ter um arquivo restante, por exemplo, integration-tests.sh, que você já está desenvolvendo. Se você tiver vários aplicativos no repositório e seus métodos de preparação diferirem, poderá usar as variáveis ​​no bashou arquivos diferentes para cada aplicativo, dependendo de suas necessidades.



Resultado



Havia muita informação - acho que agora vale a pena resumir com base no exposto acima.

Preparando ferramentas para gravação local e execução de testes com uma pitada de teste de captura de tela:



  1. Adicione dependência para o Cypress.
  2. Prepare a pasta com testes:

    1. Aplicativo único angular - deixe tudo na pasta cypress.
    2. Área de trabalho angular - crie o nome da pasta do aplicativo de integrações ao lado do aplicativo que os testes estarão perseguindo e transfira tudo da pasta cypress para ele.
    3. NX - renomeie o projeto do nome do aplicativo-e2e para o nome das integrações de aplicativos.


  3. cypress- — build- Cypress, aot, index.html, environment prod- serve- Cypress ( , - prod-, ).
  4. :

    1. Angular Single Application — serve- cypress- , start-server-and-test.
    2. Angular Workspace — Angular Single Application, cypress run/open.
    3. NX — ng e2e.


  5. -:

    1. cypress-image-snapshot.
    2. CI.
    3. Nos testes, não fazemos capturas de tela aleatoriamente. Se a captura de tela for precedida por animação, aguarde - por exemplo, adicione Cypress Assertion ao elemento animado.
    4. A data é marcada pelo cy.clock ou usamos a opção de blecaute ao tirar uma captura de tela.
    5. Esperamos qualquer estatística carregada em tempo de execução por meio do comando customizado cy.waitForResource (imagens, fontes, etc.).


  6. Nós agrupamos tudo no Docker:

    1. Dockerfile de cozinha.
    2. Crie um arquivo bash.




 Execute os testes em cima do aplicativo montado:



  1. No CI, aprendemos a lançar artefatos do aplicativo montado entre compilações (ele permanece em você).
  2. Preparando a pasta de integração-testes-scripts:

    1. Um script para aumentar o servidor do seu aplicativo.
    2. O script para alterar seu index.html (se você estiver satisfeito com o index.html original - poderá ignorá-lo).
    3. Adicione à pasta package.json os scripts e dependências necessários.
    4. Preparando um novo Dockerfile.
    5. Crie um arquivo bash.


Links Úteis



  1. Angular Workspace + Cypress + CI — Angular Workspace CI , ( typescript).

  2. Cypresstrade-offs.

  3. Start-server-and-test — , .

  4. Cypress-image-snapshot — -.

  5. Cypress recipes — Cypress, .

  6. Flaky Tests — , Google.

  7. Github Action — Cypress GitHub Action, README , — wait-on. docker.




All Articles