O tempo passou e o aprendizado com Protractor continua evoluindo.
Hoje iremos demonstrar em um projeto público que está em desenvolvimento, como criar testes automatizados com Protractor utilizando o padrão PageObjects, amplamente utilizado na comunidade para a criação dos mais diversos tipos de testes.
Primeiro, vamos entender melhor o que é e para que serve este padrão.
O padrão PageObjects
Representa as telas/páginas de sua aplicaçãoweb como uma série de objetos, com seus atributos e métodos. Este padrão é utilizado por alguns motivos, dentre eles: a reutilização de código, o desenvolvimento de código limpo e para tornar os testes em si mais fáceis de serem lidos.
Não ficou claro? Vamos para alguns exemplos a partir do projeto previamente mencionado.
Este projeto foi desenvolvido desde seu início pensando no uso do padrão PageObjects, portanto, neste post vamos ver PageObjects “em ação” e depois fazer uma engenharia reversa para ver como seriam os mesmos testes se não fossem criados dessa forma.
Dentro do diretório tests há um arquivo chamado contact.spec.js, o qual será utilizado para este exemplo. O conteúdo deste arquivo é demonstrado abaixo:
/**
* @file contact.spec.js
*/
var ContactPage = require('./pages/contact.page');
browser.ignoreSynchronization = true;
describe ('Contact', function () {
beforeEach (function () {
ContactPage.get();
});
it ('should successfully submit the contact form', function () {
ContactPage.submit('Ana', 'ana@banana.com', 'teste lorem ipsum');
expect(ContactPage.successMessage.isDisplayed()).toBe(true);
});
it ('should not successfully submit the contact form because of empty mandatory fields', function () {
ContactPage.submit('', '', '');
expect(ContactPage.successMessage.isPresent()).toBe(false);
});
});
Vamos ler este arquivo:
Inicialmente, já podemos ver que um arquivo chamado contact.page é requerido, na linha 5.
A linha 7 já foi explicada no post Refatorando testes com Protractor JS… e testando aplicações não-AngularJS!. Esta é utilizada para testes de aplicações não-Angular, neste caso, para testes de uma aplicação Drupal.
Na linha 9 a funcionalidade ‘Contact’ é descrita e dentro deste describeficarão todos os testes desta funcionalidade, ou seja, todos os testes do formulário de contato.
Nas linhas 10 a 12 o método beforeEach do Protractor é utilizado para gerar a pré-condição de cada teste, neste caso o acesso à página de contato.
Nas linhas 14 a 17 é descrito o teste que submete este formulário com sucesso, o qual utiliza do método submit da contact.page (linha 15) e verifica que o atributo successMessage está sendo exibido (linha 16).
Nas linhas 19 a 22 é descrito o teste em que o formulário não é submetido com sucesso pela falta do preenchimento dos campos obrigatórios. Na linha 20 o mesmo método submit é utilizado, porém passando valores vazios para cada campo da página (Nome, Email e mensagem) e então, na linha 21 é feita a verificação de que a successMessage não está presente.
Apesar da minha explicação, se você parar para ler este arquivo com atenção, verá que ele é praticamente auto descritivo. Isto ocorre devido ao uso do padrão PageObjects.
Vamos agora ao arquivo o qual exporta este objeto ContactPage:
Dentro da pasta tests/pages existe um arquivo chamado contact.page.js. O mesmo possui o seguinte conteúdo:
/**
* @file contact.page.js
*/
var ContactPage = function () {
this.name = element(by.css('.webform-client-formit-submitted-nome'));
this.email = element(by.css('.webform-client-form #edit-submitted-email'));
this.message = element(by.css('.webform-client-form textarea'));
this.submitButton = element(by.css('.webform-client-form input[value="Submit"'));
this.successMessage = element(by.cssContainingText('.webform-confirmation p', 'Thank you, your submission has been received.'));
this.get = function () {
browser.get('/?q=node/3’);
};
this.fillForm = function (name, email, message) {
browser.driver.sleep(100);
this.name.sendKeys(name);
browser.driver.sleep(100);
this.email.sendKeys(email);
browser.driver.sleep(100);
this.message.sendKeys(message);
browser.driver.sleep(100);
};
this.submit = function (name, email, message) {
this.fillForm(name, email, message);
browser.driver.sleep(100);
this.submitButton.click();
};
};
module.exports = new ContactPage();
Vejamos:
Na linha 5 o objeto é armazenado na variável ContactPage.
Nas linhas 7 a 11 os atributos deste objeto são definidos (campos de nome, email e mensagem; botão de submit; e mensagem de sucesso);
Nas linhas 13 a 15 o método get é definido, o qual simplesmente leva o usuário à página de contato.
Nas linhas 17 a 25 o método fillForm é definido, o qual preenche cada campo do formulário de contato com os argumentos que recebe por função.
Nas linhas 27 a 31 o método submit é definido, o qual utiliza do método fillForm para preencher o formulário e então clica no botão de submit.
E por fim na linha 35 um novo objeto ContactPage é exportado para ser utilizado nos testes. Ou seja, aqui podemos voltar na linha 5 do arquivo contact.spec.js onde o PageObject é requerido, e é neste momento que o teste já tem à sua disposição uma novo objeto ContactPage.
Agora vamos à engenharia reversa.
Imagine como seriam estes mesmos testes se não estivéssemos utilizando PageObjects.
Eles seriam algo como:
/**
* @file contact.spec.js
*/
browser.ignoreSynchronization = true;
describe ('Contact', function () {
beforeEach (function () {
browser.get('/?q=node/3 });
it ('should successfully submit the contact form', function () {
element(by.css('.webform-client-form #edit-submitted-nome')).sendKeys('Ana');
element(by.css('.webform-client-form #edit-submitted-email')).sendKeys('ana@banana.com');
element(by.css('.webform-client-form textarea')).sendKeys('teste lorem ipsum');
element(by.css('.webform-client-form input[value="Submit"')).click();
expect(element(by.cssContainingText('.webform-confirmation p', 'Thank you, your submission has been received.')).isDisplayed()).toBe(true);
});
it ('should not successfully submit the contact form because of empty mandatory fields', function () {
element(by.css('.webform-client-form #edit-submitted-nome')).sendKeys('');
element(by.css('.webform-client-form #edit-submitted-email')).sendKeys('');
element(by.css('.webform-client-form textarea')).sendKeys('');
element(by.css('.webform-client-form input[value="Submit"')).click();
expect(element(by.cssContainingText('.webform-confirmation p', 'Thank you, your submission has been received.')).isPresent()).toBe(false);
});
});
Perceba o quão maior ficou cada teste no arquivo contact.spec.js e como ele ficou mais difícil de ser lido. Além disso, já não está mais se considerando a reutilização de código, o que gera dificuldade de manutenção quando uma alteração é feita na aplicação, a qual requer alterações nos testes também.
Portanto, ao utilizarmos o padrão PageObjects estamos escrevendo testes de uma forma mais robusta no que diz respeito à manutenabilidade, ou seja, caso um elemento na tela de contato seja alterado, por exemplo, digamos que a mensagem de sucesso venha a ser traduzida, neste caso, somente o arquivo contact.page.js precisa ser modificado, não interferindo nos testes que o utilizam, sejam lá quantos testes forem. Ou seja, teremos que alterar somente uma linha de código, em vez de várias. Além disso, com o uso de PageObjects estamos escrevendo testes de forma mais limpa e legível, podendo ser facilmente entendível ao “bater o olho” nos testes.
Em resumo, com PageObjects atingimos:
- Padronização;
- Melhora na manutenibilidade dos testes;
- Código limpo;
- Legibilidade.
Caso tenha ficado interessado no assunto, recomendamos a série de videos Aprendendo Protractor. Lá você encontrará materiais básicos e intermediários (todos mão na massa) sobre o uso do Protractor, inclusive utilizando o padrão PageObjects.
Além disso, você também pode dar uma olhada nestes outros conteúdos sobre Protractor (os videos se repetem, mas tem vários outros conteúdos no formato de texto).
Deixe seu feedback.