Usando Page Objects em testes com CasperJS

Você usa/já usou/pretende usar o CasperJS? Neste post, vamos ver uma abordagem simples para usar o padrão Page Objects nos seus testes com o Casper.

Este post tem como base o ótimo post "PageObject pattern with CasperJS", do Jean-Sébastien Franck, porém, vou complementar algumas coisas e disponibilizar um projeto de exemplo no GitHub no final do post :)

Importante:

Esse post presume que você saiba, pelo menos, o básico sobre como usar o CasperJS.

Algumas ótimas fontes para aprender sobre são:


PageObject pattern

Page Objects é um padrão de projeto para desenvolver testes de UI (User Interface). Esse padrão foi preconizado pelo próprio Selenium WebDriver e o objetivo principal consiste em separar os seus testes da complexidade da UI. Além disso, o uso do padrão promove o reuso de código e, principalmente, a redução do custo de manutenção dos testes.

Basicamente, cada página da sua interface será representada por um objeto (uma classe ou, no nosso caso, um script .js). Cada objeto desses terá métodos ou funções que representam ações que podem ser feitas na página. Com isso, os testes irão apenas referenciar esses objetos e, consequentemente, não irão saber de detalhes das páginas (como ids de elementos).

O custo de manutenção é reduzido pois, quando houver mudança em uma página (alteração do id de um elemento, novo elemento na tela, novo passo para efetuar uma ação, etc.), será necessário alterar apenas em UM lugar. ;)

Dois posts essenciais para aprender sobre Page Objects:


A função injectJs do PhantomJS

O PhantomJS oferece a função injectJs, que permite injetar um script .js na página. Com esse recurso, podemos injetar o .js dos nossos page objects no nosso script de teste, como veremos no exemplo. :)


Exemplo

Vamos usar como página de exemplo a Loja Exemplo de Livros. Nosso cenário de teste consistirá nos seguintes passos:

  • Abrir a página da loja
  • Clicar no menu "Entrar" para abrir a página de login
  • Efetuar um login com sucesso
Teste SEM PageObjects

Um script do CasperJS para esse teste poderia ser algo como:

casper.test.begin('Teste CasperJS SEM PageObjects', function(test) {  
    casper.start('http://www.lojaexemplodelivros.com.br/');

    casper.then(function() {
        this.click('a[title="Entrar"]');
    });

    casper.then(function() {
        this.waitForSelector('#email', function() {
            test.assertTitle('..:: Login de Cliente ::..', 'Pagina de login carregada com sucesso');
        });
    });

    casper.then(function() {
        this.sendKeys('#email', 'stefanfk@gmail.com');
        this.sendKeys('#pass', 'teste123');
        this.click('button[title="Entrar"]');
    });

    casper.then(function() {
        this.waitForSelector('a[title="Sair"]', function() {
            test.assertTitle('..:: Minha Conta ::..', 'Login efetuado com sucesso');
        });
    });

    casper.run(function() {
        test.done();
    });
});

Mesmo sendo um exemplo simples, é fácil observar como o teste expõe detalhes da interface. Além disso, não é logo de cara que percebemos exatamente o que o teste está fazendo. Imagina como isso tudo seria agravado em um cenário complexo?

Teste COM PageObjects

No nosso exemplo, acessamos 3 telas diferentes:

  • Home page, a tela inicial da loja
  • Página de login
  • Página da conta do usuário

Dessa forma, vamos criar três page objects, contendo as ações específicas de cada página:

  • HomePage.js
  • LoginPage.js
  • MinhaContaPage.js

Os scripts dos page objects seriam:

  • HomePage.js: nessa página, clicamos no menu "Entrar" para abrir a página de login
function HomePage() {  
    this.abrirPaginaLogin = function() {
        casper.then(function() {
            this.click('a[title="Entrar"]');
        });
    };
}
  • LoginPage.js: nessa página, aguardamos a mesma ser carregada e efetuamos um login com sucesso
function LoginPage() {  
    this.aguardarCarregamentoPagina = function() {
        casper.then(function() {
            this.waitForSelector('#email', function() {
                casper.test.assertTitle('..:: Login de Cliente ::..', 'Pagina de login carregada com sucesso');
            });
        });
    };
    this.login = function(email, senha) {
        casper.then(function() {
            this.sendKeys('#email', 'stefanfk@gmail.com');
            this.sendKeys('#pass', 'teste123');
            this.click('button[title="Entrar"]');
        });
    };
}
  • MinhaContaPage.js: nessa página não efetuamos nenhuma ação, apenas garantimos que a mesma foi carregada
function MinhaContaPage() {  
    this.aguardarCarregamentoPagina = function() {
        casper.then(function() {
            this.waitForSelector('a[title="Sair"]', function() {
                casper.test.assertTitle('..:: Minha Conta ::..', 'Login efetuado com sucesso');
            });
        });
    };
}

Simples, não? O nosso teste ficaria da seguinte forma:

phantom.page.injectJs('./pageobjects/HomePage.js');  
phantom.page.injectJs('./pageobjects/LoginPage.js');  
phantom.page.injectJs('./pageobjects/MinhaContaPage.js');

var homePage = new HomePage();  
var loginPage = new LoginPage();  
var minhaContaPage = new MinhaContaPage();

casper.test.begin('Teste CasperJS COM PageObjects', function(test) {  
    casper.start('http://www.lojaexemplodelivros.com.br/');

    homePage.abrirPaginaLogin();
    loginPage.aguardarCarregamentoPagina();
    loginPage.login('stefanfk@gmail.com', 'teste123');
    minhaContaPage.aguardarCarregamentoPagina();

    casper.run(function() {
        test.done();
    });
});

Repare que nas três primeiras linhas estamos injetando os scripts dos page objects que iremos usar no teste. Em seguida, instanciamos os page objects para poder usá-los.

Observe também que o teste é completamente legível, não precisamos ler mais de uma vez para entender o que o teste faz. :)


Asserts: dentro dos testes ou dos PageObjects?

Como o Martin Fowler menciona em seu artigo, existem opiniões distintas. Por um lado, vimos que colocar os asserts dentro dos page objects torna o código de teste mais legível. Entretanto, isso faz com que os page objects tenham duas responsabilidades: efetuar ações nas páginas E fazer asserções.

Particularmente, defendo a posição do Martin Fowler e da própria documentação do Selenium, a de separar essas responsabilidades. Lembrando que não existe opção "errada": sinta-se livre para usar a abordagem que julgar mais conveniente.

No nosso exemplo, usamos dois asserts: um para validar o título da tela de login e outro para validar o título da tela "Minha Conta". O script de teste com asserts ficaria assim:

// Injects e page objects
casper.test.begin('Teste CasperJS COM PageObjects', function(test) {  
    casper.start('http://www.lojaexemplodelivros.com.br/');

    homePage.abrirPaginaLogin();
    loginPage.aguardarCarregamentoPagina();

    casper.then(function verificarTituloPaginaLogin() {
        test.assertTitle('..:: Login de Cliente ::..', 'Pagina de login carregada com sucesso');
    });

    loginPage.login('stefanfk@gmail.com', 'teste123');
    minhaContaPage.aguardarCarregamentoPagina();

    casper.then(function verificarTituloPaginaMinhaConta() {
           test.assertTitle('..:: Minha Conta ::..', 'Login efetuado com sucesso');
    });

    casper.run(function() {
        test.done();
    });
});

Projeto de exemplo

Criei um repositório no meu GitHub contendo os scripts do exemplo do post. Como sempre, recomendo que leia os links da seção de Referências.

Qualquer dúvida, basta entrar em contato ou comentar :)

Abraços e até o próximo post!


Referências

http://jsebfranck.blogspot.fr/2014/03/page-object-pattern-with-casperjs.html
http://casperjs.readthedocs.org/en/latest/modules/index.html
https://code.google.com/p/selenium/wiki/PageObjects
http://martinfowler.com/bliki/PageObject.html
http://www.dextra.com.br/page-objects-padrao-de-projeto-para-organizacao-de-testes-funcionais/
http://blog.caelum.com.br/organizacao-de-testes-de-aceitacao-com-pageobjects/


Sobre o autor: Stefan Teixeira trabalha como QA Engineer e, desde o final de 2014, tem se aventurado no mundo DevOps. É Bacharel em Ciência da Computação pela UFRJ e MBA em Garantia de Qualidade de Software pela Escola Politécnica da UFRJ. Entusiasta de Testes Automatizados (e de tudo que possa ser automatizado!), Agile Testing e da cultura DevOps.

Contatos: stefanfk@gmail.com | Twitter | LinkedIn


comments powered by Disqus