Back-End

12 dez, 2017

Primeiros passos com o Protractor – Parte 02

Publicidade

Este é o segundo artigo da série sobre Protractor. Se você ainda não leu o primeiro, veja aqui como dar os primeiros passos com Protractor. Hoje vamos conhecer os locators mais utilizados, saber em qual situação utilizar e quais as vantagens de cada um deles, além de entender como o Protractor trabalha com as promisses. Bora lá?

O Protractor exporta a função global element, que é responsável por encontrar um elemento no DOM. Caso você precise manipular múltiplos elementos, pode usar a função element.all, e com essas funções é possível utilizar os métodos das ações como click(), getText() e sendKeys().

O ElementFinder do Protractor executa as ações de forma assíncrona, ou seja, todas elas são enviadas para o browser, que vai gerenciá-las utilizando o Protocolo do WebDriver. O browser, então, executa essas ações como um usuário comum.

Além de todos os locators específicos do WebDriver, nós temos a opção de utilizar locators específicos para aplicações AngularJS. Na minha opinião, essa é uma das principais vantagens de usar Protractor para automatizar nossos testes. Veja abaixo os principais locators do Protractor e quando utilizar cada um deles:

BY.ADDLOCATOR

Acho que esse é um dos mais legais. Com ele nós podemos criar nosso locator customizado. Isso mesmo, podemos escolher como encontrar nossos elementos na tela. Demais, né? E como utilizar?

Primeiro, imagine que no seu html você pode ter um elemento que utiliza data attributes. Por exemplo:

<input type="text" ng-model="PokemonsController.filtro" data-test="filtro">

Repare que temos um atributo data-test=”filtro”. Quando queremos manipular esse elemento via JS, podemos encontrá-lo dessa forma:

document.querySelector('[data-test="filtro"]')

Beleza Cássio, você já falou sobre data-attributes, já falou como encontrar esse elemento usando JS, mas queremos utilizar o Protractor para encontrar esse elemento nos nossos testes.

Pois bem, agora acontece a “mágica”.

Primeiro, temos que ir ao nosso arquivo de configuração do Protractor e adicionar esse trecho de código dentro da nossa função onPrepare:

by.addLocator('dataTest',function(text) {
 	return document.querySelector(`[data-test="${text}"]`)
 });

Pronto, feito isso você já pode utilizar o seu locator customizado dentro dos seus testes, da seguinte forma:

element(by.dataTest('filtro'));

Legal, né? Isso pode ser uma ótima forma de deixar seus testes mais resilientes contra falhas, pois muitas vezes id’s e name’s acabam sendo alterados e quebram nossos testes. Pode até adotar como sendo um padrão dentro do seu projeto a utilização de data attributes para facilitar a manipulação dos elementos.

BY.BINDING E BY.EXACTBINDING

Para elementos que contenham a diretiva ng-bind (do próprio AngularJS), podemos utilizar os locators by.binding ou by.exactBinding:

<span>{{pokemon.name}}</span>
<span ng-bind="pokemon.name"></span>

E para encontrar elementos com essa diretiva, basta fazer da seguinte forma:

element(by.binding('pokemon.name'));

Ao utilizar o locator by.binding, podemos passar como parâmetro apenas uma parte do valor da diretiva, como:

element(by.binding('name'));

Já para o by.exactBinding, temos que passar exatamente o valor que está declarado na diretiva.

BY.MODEL

Encontra um elemento no DOM que tenha a diretiva ng-model (do próprio AngularJS):

<input type="text" ng-model="pokemon.name">

Encontramos esse elemento da seguinte forma no teste com Protractor:

element(by.model('pokemon.name'))

BY.BUTTONTEXT E BY.PARTIALBUTTONTEXT

Ambos encontram um elemento no DOM baseado pelo texto do botão:

<button>Carregar mais Posts</button>

Para localizar esse elemento, usamos:

element(by.buttonText('Carregar mais Posts'));

Ou, por apenas uma parte do texto do botão, utilizamos o by.partialButtonText:

element(by.partialButtonText('Carregar'));

BY.REPEATER E BY.EXACTREPEATER

Encontra elemento com a diretiva do AngularJS ng-repeat:

<tr ng-repeat="item in PokemonsController.pokemons|filter: PokemonsController.filtro">
   <td>
       <a href="#!/details/{{item.id}}">
           <strong>
               #{{item.id}}
           </strong>
       </a>
   </td>
   <td>
       <a href="#!/details/{{item.id}}">
           <img ng-src="{{item.image}}" width="60px" alt="">
       </a>
   </td>
   <td>
       <a href="#!/details/{{item.id}}">
           <span ng-bind="item.name"></span>
       </a>
   </td>
   <td>
       <a href="#!/details/{{item.id}}">
           <span class="label bg-{{item.type1}}" ng-bind="item.type1"></span>
           <span class="label bg-{{item.type2}}" ng-bind="item.type2"></span>
       </a>
   </td>
</tr>

Esse locator é muito útil e ajuda muito quando precisamos lidar com uma lista de elementos. Com ele podemos extrair várias informações úteis, como:

Quantidade de registros listados

const resultados =  element.all(by.repeater('item in PokemonsController.pokemons'));
resultados.count();

Retornar um dado específico da listagem:

element(by.repeater('item in PokemonsController.pokemons').row(0).column('item.name'));

Retornar todos os valores de uma determinada coluna

element.all(by.repeater('item in PokemonsController.pokemons').column('item.name'));

Para usar o by.exactRepeater, como o próprio nome sugere, devemos passar por parâmetro o valor exatamente igual ao que está no valor da diretiva. Nesse caso, fica assim:

element.all(by.repeater('item in PokemonsController.pokemons|filter: PokemonsController.filtro'));

Observação: Podemos continuar extraindo as informações da mesma forma que o by.repeater.

Em breve teremos um artigo com algumas dicas de como trabalhar listas usando Protractor e alguns métodos auxiliares.

BY.CSSCONTAININGTEXT

Esse locator encontra elemento por CSS que contenha um determinado texto. Recebe dois parâmetros, o primeiro é o seletor css e o segundo é o texto que vai filtrar. Veja abaixo um exemplo:

Dado que tenha mais de um elemento html com a mesma class:

<ul>
 <li class="pokemon">Bulbasaur</li>
 <li class="pokemon">Charizard</li>
</ul>

Para filtrar um específico, usamos da seguinte maneira:

var bulbasaur = element(by.cssContainingText('.pokemon', 'Bulbasaur'));

BY.OPTIONS

Encontra um elemento com a diretiva do AngularJS ng-options

<select  ng-options="pokemon.type for type in types"></select>

Para encontrar esse elemento utilizando Protractor:

element(by.options('pokemon.type for type in types'));

Como o Protractor trabalha com as Promisses?

Como já falei anteriormente, todos as ações do Protractor são assíncronas, isso significa que todos os métodos retornam uma promisse. Então, se quisermos saber o valor de um determinado elemento, precisamos fazer assim:

var pokemonName = element(by.model('pokemon.name'));
pokemonName.getText().then(function(text) {
console.log(text);
});

Se quisermos saber se um elemento está habilitado:

var saveButton = element(by.buttonText('Salvar'));
saveButton.isEnabled().then(function(enable) {
console.log(enable);
});

Se quisermos saber se um elemento está visível:

var saveButton = element(by.buttonText('Salvar'));
saveButton.isDisplayed().then(function(visible) {
console.log(visible);
});

Observação: Alguns locators do Protractor ainda não estão funcionando adequadamente com as novas versões do AngularJS. Tem uma issue aberta no Github do Protractor sobre esse assunto, e se você quiser acompanhar é só clicar aqui.

O Protractor possui uma grande variedade de locators e saber qual podemos utilizar em cada situação é uma grande vantagem para melhorar nossos testes. Então, antes de sair utilizando XPATH e CSS Selector (só usem em último caso e ainda sim, saibam como utilizar com inteligência), tentem utilizar ao máximo os locators do Protractor. Fechado? Se tiver alguma dúvida ou tem algo a acrescentar, é só usar os campos abaixo. Até a próxima!

Quer saber mais? Acesse:

***

Este artigo foi publicado originalmente em: https://www.concrete.com.br/2017/11/20/primeiros-passos-com-o-protractor-parte-2/