Implementamos a automação em algumas horas: TypeScript, Transferidor, Jasmine

Olá Habr!

Meu nome é Vitaliy Kotov, faço muitos testes de automação e gosto. Recentemente participei de um projeto para configurar a automação do zero na pilha TypeScript + Transferidor + Jasmim. Para mim, essa pilha era nova e procurei as informações necessárias na Internet.

Consegui encontrar os manuais mais úteis e sensatos apenas em inglês. Eu decidi que em russo eu também precisava fazer isso. Vou apenas lhe dizer o básico: por que essa pilha, o que você precisa configurar e como é o teste mais simples.

Devo dizer imediatamente que raramente trabalho com NodeJS, npm e com JavaScript do lado do servidor em geral (especialmente com TypeScript). Se você encontrar algum erro na terminologia em algum lugar ou se algumas das minhas decisões puderem ser aprimoradas, ficarei feliz em saber disso nos comentários de pessoas mais experientes.

A propósito, eu já tinha um artigo semelhante: “Implementamos a automação em algumas horas: PHPUnit, Selenium, Composer” .



Desafio


Primeiro de tudo, vamos descobrir qual problema estamos resolvendo. Temos um aplicativo da Web escrito usando o AngularJS. Essa é uma estrutura JavaScript com base na qual os projetos da Web geralmente são gravados.

Neste artigo, não consideraremos os prós e os contras dos projetos AngularJS. Apenas algumas palavras sobre os recursos de tais projetos em termos de escrever testes e2e para eles.

Um aspecto bastante importante da automação de teste é trabalhar com elementos da página, o que acontece com a ajuda dos localizadores. Um localizador é uma linha composta de acordo com certas regras e identificando um elemento da interface do usuário: um ou mais.

Para a web, CSS e Xpath são mais comumente usados. Às vezes, se houver um elemento com um ID exclusivo na página, você poderá pesquisar por ele. No entanto, parece-me que o WebDriver ainda transforma esse ID em um localizador de CSS no final e já trabalha com ele.

Se observarmos o código HTML de algum projeto AngularJS, veremos que os elementos têm muitos atributos que não estão no HTML clássico:



O código é obtido na página demo-transferidor .

Todos os atributos que começam com ng- * são usados ​​pelo AngularJS para trabalhar com a interface do usuário. Uma situação bastante típica é quando os elementos não têm outros atributos além desses atributos de controle, o que complica um pouco o processo de compilação de localizadores de qualidade.

Aqueles que fizeram muita automação sabem sobre o valor dessas interfaces de usuário para as quais os localizadores podem ser facilmente construídos. Afinal, isso é raro para grandes projetos. :)

Na verdade, para esse projeto, também precisamos configurar a automação de teste. Vamos lá!

O que é o que


Primeiro, vamos descobrir por que cada componente da nossa pilha é necessário.

Transferidor é uma estrutura de teste baseada no WebDriverJS. Será ele quem irá lançar nossos navegadores, fazê-los abrir as páginas necessárias e interagir com os elementos necessários.

Essa estrutura é especificamente adaptada para projetos AngularJS. Ele fornece maneiras adicionais de especificar localizadores:

element(by.model('first')); element(by.binding('latest')); element(by.repeater('some')); 

Uma lista completa pode ser encontrada na página do manual .

Esses métodos simplificam a criação e o suporte de localizadores em um projeto. No entanto, você deve entender que "sob o capô" tudo isso é convertido em css. O fato é que o protocolo W3C, com base no qual a interação no WebDriver ocorre, pode funcionar apenas com um conjunto finito de localizadores. Esta lista pode ser vista em w3.org .

TypeScript é uma linguagem de programação criada pela Microsoft. O TypeScript difere do JavaScript na capacidade de digitar variáveis, no suporte ao uso de classes completas e na capacidade de conectar módulos.

Escrito em código TS para trabalhar com o mecanismo V8 é convertido em código JS, que já está em execução. Durante essa transformação, o código é verificado quanto à conformidade. Por exemplo, ele não “compila” se, em vez de int, uma string é explicitamente passada para a função em algum lugar.

Jasmine é uma estrutura para testar o código JavaScript. De fato, é graças a ele que nosso código JS se transforma no que costumávamos chamar de teste. Ele gerencia esses testes.

Abaixo, examinamos suas capacidades.

Montagem e montagem do projeto


Bem, decidimos por um conjunto de estruturas, agora vamos montar tudo isso.

Para trabalhar com o código, escolhi o Visual Studio Code da Microsoft. Embora muitos escrevam no WebStorm ou mesmo no Intellij Idea do JetBrains.

Eu já instalei o NodeJS (v11.6.0) e o NPM (6.9.0). Se você não tiver, não há problema; instalá-los não será difícil. Tudo é descrito em detalhes suficientes no site oficial .

O fio pode ser usado no lugar do NPM, embora isso não seja importante para um projeto pequeno.

Em nosso IDE, estamos criando um novo projeto. Criamos o package.json na raiz do projeto - é nele que descreveremos todos os pacotes necessários para o projeto.

Você pode criá-lo usando o comando npm init . Ou você pode simplesmente copiar o conteúdo para um arquivo.

Inicialmente, o package.json fica assim:

 { "name": "protractor", "dependencies": { "@types/node": "^10.5.2", "@types/jasmine": "^3.3.12", "protractor": "^5.4.2", "typescript": "^3.4.1" } } 

Depois disso, executamos o comando npm install para instalar todos os módulos necessários e suas dependências (bem, você se lembra da própria imagem sobre algo que é mais pesado que um buraco negro ...)

Como resultado, devemos ter um diretório node_modules. Se ela apareceu, tudo corre conforme o planejado. Caso contrário, vale a pena examinar o resultado da execução do comando, geralmente tudo é descrito em detalhes.

TypeScript e sua configuração


Para instalar o TypeScript, precisamos do npm:

 npm install -g typescript 

Verifique se ele está instalado:

 $ tsc -v Version 3.4.1 

Tudo parece estar em ordem.

Agora precisamos criar uma configuração para trabalhar com o TS. Também deve estar na raiz do projeto e ser chamado tsconfig.json

Seu conteúdo será assim:

 { "compilerOptions": { "lib": ["es6"], "strict": true, "outDir" : "output_js", "types" : ["jasmine", "node"] }, "exclude": [ "node_modules/*" ] } 

Em resumo, especificamos o seguinte nesta configuração:

  • Em qual diretório colocar o código JS final (no nosso caso, é output_js)
  • Ativar modo estrito
  • Indicado com quais estruturas estamos trabalhando
  • Node_modules excluídos da compilação

O TS tem uma enorme variedade de configurações. Estes são suficientes para o nosso projeto. Você pode aprender mais em typescriptlang.org .

Agora vamos ver como o comando tsc funciona , que transformará nosso código TS em código JS. Para fazer isso, crie um arquivo simples check_tsc.ts com o seguinte conteúdo:

 saySomething("Hello, world!"); function saySomething(message: string) { console.log(message); } 

E, em seguida, execute o comando tsc (para isso, você precisa estar dentro do diretório do projeto).

Veremos que temos o diretório output_js e um arquivo js semelhante com o seguinte conteúdo apareceu dentro:

 "use strict"; saySomething("Hello, world!"); function saySomething(message) { console.log(message); } 

Este arquivo já pode ser iniciado usando o comando node:

 $ node output_js/check_tsc.js Hello, world! 

Então, escrevemos nosso primeiro programa TypeScipt, parabéns. Vamos escrever testes agora. :)

Configuração do transferidor


Para o transferidor, também precisamos de uma configuração. Mas não será mais na forma de json, mas na forma de um arquivo ts. Vamos chamá-lo de config.ts e escrever o seguinte código lá:

 import { Config } from "protractor"; export const config: Config = { seleniumAddress: "http://127.0.0.1:4444/wd/hub", SELENIUM_PROMISE_MANAGER: false, capabilities: { browserName: "chrome", /*chromeOptions: { args: [ "--headless", "--window-size=800,600" ] }*/ }, specs: [ "Tests/*Test.js", ] }; 

Neste arquivo, especificamos o seguinte:

Primeiro, o caminho para o servidor Selenium em execução. É muito simples de executar, você só precisa baixar o arquivo jar do Standalone Server e os drivers necessários (por exemplo, o driver chrome para o navegador Chrome ). Em seguida, escreva o seguinte comando:

 java -jar -Dwebdriver.chrome.driver=/path/to/chromedriver /path/to/selenium-server-standalone.jar 

Como resultado, devemos ver a seguinte conclusão:

 23:52:41.691 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.11.0', revision: 'e59cfb3' 23:52:41.693 INFO [GridLauncherV3$1.launch] - Launching a standalone Selenium Server on port 4444 2019-05-02 23:52:41.860:INFO::main: Logging initialized @555ms to org.seleniumhq.jetty9.util.log.StdErrLog 23:52:42.149 INFO [SeleniumServer.boot] - Welcome to Selenium for Workgroups.... 23:52:42.149 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444 

A porta 4444 é padrão. Pode ser definido usando o parâmetro -port ou através do parâmetro de configuração "seleniumArgs" => "-port".

Se você quiser mais fácil e mais rápido: você pode fazer o download do pacote npm webdriver-manager .

E, em seguida, gerencie o servidor usando os comandos iniciar, desligar e assim por diante. Não há muita diferença, só que estou mais acostumado a trabalhar com um arquivo jar. :)

Em segundo lugar , indicamos que não queremos usar o gerenciador de promessas. Mais sobre isso mais tarde.

Em terceiro lugar , especificamos recursos para o nosso navegador. Comentei uma parte, por exemplo, de que podemos facilmente iniciar o navegador no modo sem cabeça. Esse é um recurso interessante, mas não permitirá que você observe visualmente nossos testes. Enquanto isso, estamos apenas aprendendo - eu gostaria. :)

Quarto , especificamos uma máscara para especificações (testes). Tudo o que está na pasta Testes e termina com Test.js. Por que em js, não ts? Isso ocorre porque, no final, o Node funcionará especificamente com arquivos JS, e não com arquivos TS. É importante não se confundir, principalmente no início do trabalho.

Agora crie a pasta Tests e escreva o primeiro teste. Ele fará o seguinte:

  • Desativa a verificação de que esta é uma página angular. Sem isso, recebemos a seguinte mensagem de erro: Erro ao executar testForAngular. Obviamente, para páginas angulares, essa verificação não é necessária para desativar.
  • Vai para a página do Google.
  • Verifique se há um campo de entrada de texto.
  • Digite o texto "transferidor".
  • Clique no botão enviar (ele tem um localizador bastante complicado, pois existem dois botões e o primeiro é invisível).
  • Espera-se que o URL contenha a palavra "transferidor" - isso significa que fizemos tudo certo e a pesquisa começou.

Aqui está o código que recebi:

 import { browser, by, element, protractor } from "protractor"; describe('Search', () => { it('Open google and find a text', async () => { //       let EC = protractor.ExpectedConditions; //    AngularJS await browser.waitForAngularEnabled(false); //   Google await browser.get('https://www.google.com/'); //    css = input[role='combobox'] let input_button = element(by.css("input[role='combobox']")); //     ( presenceOf) await browser.wait(EC.presenceOf(input_button), 5000); //     “protractor” await input_button.sendKeys("protractor"); //      css let submit_button = element(by.css(".FPdoLc input[type='submit'][name='btnK']")); //      ( ,     input-,   ) await browser.wait(EC.presenceOf(submit_button), 5000); //     await submit_button.click(); // ,  URL    'protractor' await browser.wait(EC.urlContains('protractor'), 5000); }); }); 

No código, vemos que tudo começa com a função descrevem (). Ela veio até nós da estrutura Jasmine. Este é um invólucro para o nosso script. Dentro dele, pode haver funções beforeAll () e beforeEach () para executar qualquer manipulação antes de todos os testes e antes de cada teste. Tantas funções quanto () são, de fato, nossos testes. No final, se definido, afterAll () e afterEach () serão executados para manipulações após cada teste e todos os testes.

Não vou falar sobre todos os recursos do Jasmine, você pode ler sobre eles no site jasmine.imtqy.com

Para executar nosso teste, primeiro você precisa transformar o código TS em código JS e, em seguida, execute-o:

 $ tsc $ protractor output_js/config.js 

Nosso teste começou - somos ótimos. :)



Se o teste não foi iniciado, vale a pena conferir:

  • Que o código está escrito corretamente. Em geral, se houver erros críticos no código, nós os capturaremos durante o comando tsc.
  • O Selenium Server está em execução. Para fazer isso, você pode abrir o URL http: //127.0.0.1-00-00444/wd/hub - deve haver uma interface para as sessões do Selenium.
  • Esse Chrome inicia normalmente com a versão baixada do chrome-driver. Para fazer isso, na página wd / hub /, clique em Criar sessão e selecione Chrome. Se não iniciar, é necessário atualizar o Chrome ou baixar outra versão do driver do chrome.
  • Se tudo isso falhar, você poderá verificar se o comando npm install foi concluído com êxito.
  • Se tudo estiver escrito corretamente, mas ainda assim nada começar - tente pesquisar o erro no google. Na maioria das vezes ajuda. :)

Scripts NPM


Para facilitar a vida, você pode fazer parte dos aliases de comandos npm. Por exemplo, eu gostaria de excluir o diretório com arquivos JS anteriores e recriá-lo com novos antes de cada teste.

Para fazer isso, adicione o item de scripts ao package.json:

 { "name": "protractor", "scripts": { "test": "rm -rf output_js/; tsc; protractor output_js/config.js" }, "dependencies": { "@types/node": "^10.5.2", "@types/jasmine": "^3.3.12", "protractor": "^5.4.2", "typescript": "^3.4.1" } } 

Agora, inserindo o comando npm test , o seguinte acontecerá: o diretório output_js com o código antigo será excluído, será criado novamente e um novo código JS será gravado nele. Após o qual os testes serão iniciados imediatamente.

Em vez deste conjunto de comandos, você pode especificar qualquer outro que você pessoalmente precise trabalhar. Por exemplo, você pode iniciar e desativar um servidor selênio entre as execuções de teste. Embora isso, é claro, seja mais fácil de controlar dentro do próprio código de teste.

Um pouco sobre Promise


No final, falarei um pouco sobre Promise, assíncrono / aguardar e como a gravação de testes no NodeJS difere do mesmo Java ou Python.

JavaScript é uma linguagem assíncrona. Isso significa que o código nem sempre é executado na ordem em que é gravado. Isso inclui solicitações HTTP, e lembramos que o código se comunica com o Selenium Server via HTTP.

Promessa (geralmente chamada de "promessas") fornece uma maneira conveniente de organizar código assíncrono. Você pode ler mais sobre eles em learn.javascript.ru .

De fato, esses são objetos que tornam um código dependente da execução de outro, garantindo uma certa ordem. O transferidor trabalha muito ativamente com esses objetos.

Vejamos um exemplo. Suponha que executemos o seguinte código:

 driver.findElement().getText(); 

Em Java, esperamos retornar um objeto do tipo String. No Transferidor, não é bem assim, retornaremos um objeto Promise. E assim, imprimi-lo com o objetivo de depuração não funcionará.

Normalmente, não precisamos imprimir o valor resultante. Precisamos passá-lo para outro método que já funcione com esse valor. Por exemplo, ele verificará o texto quanto à conformidade com o esperado.

Métodos semelhantes no Transferidor também aceitam objetos Promise como entrada, portanto, não há problema. Mas, se você ainda deseja ver o valor, () será útil.

É assim que podemos imprimir o texto do botão em uma página do Google (observe que, como esse é um botão, o texto está dentro do atributo value):

 //   let submit_button = element(by.css(".FPdoLc input[type='submit'][name='btnK']")); //    await browser.wait(EC.presenceOf(submit_button), 5000); //   then()   await submit_button.getAttribute("value").then((text) => { console.log(text); }); 

Quanto às palavras-chave assíncronas / em espera, essa é uma abordagem um pouco mais nova para trabalhar com código assíncrono. Ele permite que você evite o inferno da promessa, que foi formado anteriormente no código devido ao grande número de aninhamentos. No entanto, você não poderá se livrar completamente do Promise e precisará trabalhar com eles. Isso é compreensível e detalhado pode ser encontrado no artigo Design assíncrono / aguardado em JavaScript: pontos fortes, armadilhas e recursos de uso .

Lição de casa


Como lição de casa, sugiro escrever testes para uma página escrita no AngularJS: transferidor-demo .

Não se esqueça de remover a linha do código sobre como desativar a verificação de página no AngularJS. E não se esqueça de trabalhar com localizadores projetados especificamente para o AngularJS. Não há mágica em particular nisso, mas é bastante conveniente.

Sumário


Vamos fazer um balanço. Conseguimos escrever testes que funcionam em um monte de TypeScript + Transferidor + Jasmine. Aprendemos como criar um projeto assim, criar as configurações necessárias e escrevemos o primeiro teste.

Ao longo do caminho, discutimos um pouco sobre como trabalhar com testes automáticos de JavaScript. Parece bom por algumas horas. :)

O que ler, onde procurar


O transferidor tem um manual muito bom com exemplos de JavaScript: https://www.protractortest.org/#/tutorial
Jasmine tem um manual: https://jasmine.imtqy.com/pages/docs_home.html
O TypeScipt começou bem: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html

No Medium, há um bom artigo em inglês sobre TypeScript + Transferidor + Pepino: https://medium.com/@igniteram/e2e-testing-with-protractor-cucumber-using-typescript-564575814e4a

E no meu repositório, publiquei o código final do que discutimos neste artigo: https://github.com/KotovVitaliy/HarbProtractorJasmineJasmine .

Na Internet, você pode encontrar exemplos de projetos maiores e mais complexos nessa pilha.

Obrigado pela atenção! :)

Source: https://habr.com/ru/post/pt451522/


All Articles