sexta-feira, 15 de outubro de 2010

Integração Selenium e Mantis

For foreing raders, please clique here to read this post in english (google translator)

Não deixe de ler também a Integração do Selenium e Testlink.

Escopo
Este tutorial vai apresentar como efetuar a integração entre o Selenium RC e o Mantis, para que seja possível reportar automaticamente um bug quando um erro em um script do Selenium falhar.

Criaremos um projeto em Java com o suporte do JUnit para a criação do script de teste com o Selenium. abaixo há uma lista de itens necessários para esse tutorial.

O que é necessário?

2. Preparando seu ambiente de Desenvolvimento
A preparação do ambiente vai apresentar somente as bibliotecas necessárias para fazer a integração funcionar. Não é intuito deste item ensinar alguma coisa sobre o Eclipse ou sobre Java.

2.1 Selecionando as bibliotecas necessárias
Para rodar o script de teste com a integração e necessário os seguintes arquivos (bibliotecas e libraries) das seguintes API's / frameworks:
  • mantisconnect: necessário adicionar as seguintes bibliotecas
    • mantisconnect-client-api-1.1.1.0.jar
    • todas as libs, exeto a junit, da pasta lib
  • Selenium RC: necessário adicionar as seguintes bibliotecas
    • selenium-server.jar
    • selenium-java-client-driver.jar
  • JUnit: necessário adicionar o JUnit 4 que já vem com o Eclipse (Add Libraries)
Abaixo segue a imagem das bibliotecas que foram adicionadas ao Eclipse.


 Para adicionar cada biblioteca você precisa clicar com o botão direito no nome do projeto e selecionar Build Path/Configure Build Path...



Adicone também a biblioteca do JUnit, indo ao mesmo local (Configure Build Path...) e clicando no botão Add Library. Selecione JUnit e, em seguida, JUnit 4.

 2.1 Desenvolvendo o código-fonte
A aplicação de exemplo, que está no final do post traz o código-fonte do script de teste e mais dois arquivos de código-fonte, que serão explicados abaixo.

A primeira coisa a fazer é entender o script de teste primeiro. O package, os imports e comentários em javadoc foram excluídos para exemplificar o funcionamento da classe.
Existem duas classes de teste, uma com execução OK e outra que forçará o erro para que seja possível o report automático do bug.
Abaixo será apresentado o script que força o erro.

1:  public class CasoTesteMantisNOK extends TestCase implements IConstantes {  
2:      
3:    Selenium selenium;  
4:    SeleniumServer server;  
5:      
6:    String serverHost = "localhost";  
7:    int serverPort = 4444;  
8:    String browserStartCommand = "*firefox";  
9:    String browserURL = "http://www.lojaexemplodelivros.com.br/";  
10:      
11:    boolean erro;  
12:    String msgErro;  
13:    String evidenciaErro;  
14:      
15:    public void setUp() throws Exception {  
16:      selenium = new DefaultSelenium(serverHost, serverPort, browserStartCommand, browserURL);  
17:      server = new SeleniumServer();  
18:        
19:      server.start();  
20:      selenium.start();  
21:    }  
22:      
23:    @Test  
24:    public void testPesquisaLivro() throws Exception {  
25:        
26:      try {  
27:        selenium.open("/");  
28:        selenium.click("//ul[@id='nav']/li[1]/ul/li[2]/ul/li[1]/a/span");  
29:        selenium.waitForPageToLoad("30000");  
30:          
31:        assertEquals("3 Item(s)", selenium.getText("//div[@id='main']/table[1]/tbody/tr/td[1]/strong"));  
32:        assertEquals("[PRODUTO] - Use a Cabeça! Java", selenium.getText("link=[PRODUTO DE EXEMPLO] - Use a Cabeça! Java"));  
33:        assertEquals("[PRODUTO DE EXEMPLO] - Entendendo e Dominando o Java: para Internet", selenium.getText("link=[PRODUTO DE EXEMPLO] - Entendendo e Dominando o Java: para Internet"));  
34:        assertEquals("[PRODUTO DE EXEMPLO] - Ajax com Java", selenium.getText("link=[PRODUTO DE EXEMPLO] - Ajax com Java"));  
35:          
36:        selenium.click("//img[@alt='[PRODUTO DE EXEMPLO] - Ajax com Java']");  
37:        selenium.waitForPageToLoad("30000");  
38:        assertTrue(selenium.isTextPresent("2x R$ 222,25 sem juros"));  
39:        assertTrue(selenium.isTextPresent("3x R$ 148,17 sem juros"));  
40:        assertTrue(selenium.isTextPresent("4x R$ 111,13 sem juros"));  
41:        assertTrue(selenium.isTextPresent("5x R$ 88,90 sem juros"));  
42:        
43:      } catch (AssertionError e) {  
44:        reportError(e);  
45:          
46:      } catch (Exception e) {  
47:        reportError(e);  
48:          
49:      } finally {  
50:        if (erro) {  
51:          MantisReport.reporIssue("Erro no Caso de Teste de Pesquisa de Livros", "Erro em alguma validacao ou validacao", "General", msgErro, evidenciaErro, "CasoTesteMantisNOK");  
52:          CasoTesteMantisNOK.fail(msgErro);  
53:        }  
54:      }  
55:    }  
56:      
57:    private void reportError(Throwable e) {      
58:      erro = true;  
59:      msgErro = e.getMessage();  
60:      e.printStackTrace();  
61:      evidenciaErro = selenium.captureEntirePageScreenshotToString("background=#FFFFFF");  
62:    }  
63:      
64:    public void tearDown() throws Exception {  
65:      selenium.stop();  
66:      server.stop();  
67:    }  
68:  }  


Basicamente há a criação dos atributos do Selenium e SeleniumServer para a execução, os métodos setup() e tearDown() do JUnit e o caso de teste automatizado.
Note que o bloco do caso de teste (método testPesquisaLivro) está com um try-catch. Isso é necessário para que possamos reportar um bug quando o script falhar.

Nas linhas 11, 12 e 13 foram criados atributos que controlarão os dados para o erro, e serão descritos logo mais.

A linha 32 deste script irá falhar, propositalmente, para que o bug seja reportado. O trecho de código correto (que não causará um bug) é o abaixo e também esta no arquivo CasoTesteMantisOK:

assertEquals("[PRODUTO DE EXEMPLO] - Use a Cabeça! Java", selenium.getText("link=[PRODUTO DE EXEMPLO] - Use a Cabeça! Java"));

Existem 2 blocos com catch (um iniciando na linha 43 e outro na linha 46). Foram colocados dois somente para distinguir o tipo do erro, se um erro na validação (AssertionError) ou qualquer erro (Exception). Dentro destes blocos há um método chamado reportError passando como parâmetro a exception.
Este método (linhas 57 a 62) informa através da variável erro que um erro ocorreu (passando true). Pega a mensagem de erro e coloca no atributo msgErro, faz com que a exception seja apresentada o console (e.printStackTrace()) e captura a imagem da página utilizando a função captureEntirePageScreenshotToString() para passar a imagem em formato de String Base64.

PS: esse comando só funcionará rodando com a angine do Mozilla (Firefox) e no Google Chrome. Caso queira que o mesmo comando funcione no IE leia este post (em inglês).

Na linha 50 e feito uma condição para ver se ocorreu algum erro (se o atributo erro está como true), caso positivo a função de report do bug é chamada.

A linha 51 apresenta a função utilizada para reportar o bug, que será explicado logo mais. Neste momento você só precisa saber que é necessário informar os seguintes dados (nesta ordem) para o método:
  • Sumário do bug
  • Descrição do bug
  • Categoria do bug
  • Informação adicional do bug
  • Evidencia (como String Base64)
  • Nome do arquivo (que será anexado)
A linha 52 força uma falha no script mostrando a mensagem de erro ocorrida.

Classe MatisReport
Dentro do projeto no pacote com.blogspot.sembugs há a classe MantisReport, que é a responsável por reportar o bug no Mantis. Eu gerei essa classe como chamada de uma função da API mantisconnect.

PS: package, imports e comentários em javadoc foram excluídos para exemplificar o funcionamento da classe.

1:  public class MantisReport implements IConstantes {  
2:    
3:      public static void reporIssue(String sumario, String descricao, String categoria, String informacaoAdicional, String evidencia, String nomeArquivo) {  
4:          IMCSession sessao = null;  
5:          String arquivo = nomeArquivo + ".png";  
6:            
7:          try {  
8:              sessao = ConnectMantis.getSessao();  
9:              IProject projeto = sessao.getProject(PROJETO);  
10:                
11:        Issue issue = new Issue();  
12:          
13:        issue.setProject(new MCAttribute(projeto.getId(), projeto.getName()));  
14:        issue.setAdditionalInformation(null);  
15:        issue.setOs(System.getProperty("os.name"));  
16:        issue.setOsBuild(System.getProperty("os.version"));  
17:        issue.setPlatform(System.getProperty("os.arch"));  
18:        issue.setSeverity(new MCAttribute(70, "crash"));  
19:        issue.setReproducibility(new MCAttribute(10, "always"));  
20:        issue.setSummary(sumario + new Date());  
21:        issue.setDescription(descricao);  
22:        issue.setCategory(categoria);  
23:        issue.setPriority(new MCAttribute(40, "high"));  
24:        issue.setAdditionalInformation(informacaoAdicional);  
25:          
26:        long id = sessao.addIssue(issue);     
27:        sessao.addIssueAttachment(id, arquivo, "image/png", Base64.decodeBase64(evidencia));  
28:                
29:          } catch (MalformedURLException e) {  
30:              System.err.println("Erro na URL de acesso ao Mantis");  
31:              e.printStackTrace();  
32:          } catch (MCException e) {  
33:              System.err.println("Erro na comunicacao com o Mantis");  
34:              e.printStackTrace();  
35:          }  
36:      }  
37:  }  

A linha 5 cria um atributo que pega o nome do arquivo, que foi passado como parâmetro, e concatena a extensão ".png", que será necessário para anexar o arquivo no Mantis.

Na linha 8 é feita uma chamada para o Singleton (que será explicado depois) para fazer a conexão com o Mantis e retornar um objeto de sessão do Mantis (IMSession)

A linha 9 traz o projeto, que está na interface IConstantes, como objeto de projeto do Mantis (IProject). Isso é necessário para sabermos em qual projeto reportar o bug.

Na linha 11 é criado uma issue (bug). A classe Issue representa um relato de bug.
A linha 13 seta o projeto que reportaremos o bug e das linhas 14 a 24 passamos diversas informações do bug para o Mantis. Existe uma série de informações que podemos passar, eu coloquei apenas as mais relevantes aqui.

Atenção: na linha 20 eu concatenei o sumário com a data atual (issue.setSummary(sumario + new Date());). Fiz isso para que seja possível executar diversas vezes o script sem duplicar o nome do bug no Mantis.

Note que nas linhas 18, 19 e 23 é preciso criar um objeto MCAttibute para que seja possível passar informações de qualquer atributo no report do bug como Severidade, Prioridade e Frequencia.
Serão sempre duas informações: o código e o nome do atributo.
Você pode consultar o código e nome dos atributos no arquivo config_defaults_inc.php e consultar cada atributo.
Se você quiser visualizar ou alterar estes atributos que estão no código-fonte, dê uma olhada nos atributos abaixo que estão contidos no arquivo citado acima:

1:  $g_severity_enum_string    = '10:feature,20:trivial,30:text,40:tweak,50:minor,60:major,70:crash,80:block';  
2:    
3:  $g_priority_enum_string    = '10:none,20:low,30:normal,40:high,50:urgent,60:immediate';  
4:    
5:  $g_reproducibility_enum_string = '10:always,30:sometimes,50:random,70:have not tried,90:unable to duplicate,100:N/A';  

Na linha 26 o bug é submetido para o cadastro, retornando o código do bug.

A linha 27 adiciona um anexo no bug, que é a tela capturada pelo Selenium no momento do erro. Para isso é necessário passar para a função addIssueAttachment da sessão (e não da issue) os parâmetros: id do bug, nome do arquivo, tipo do arquivo e o array de bytes do arquivo (por isso a transformação em Base64, que é como o Selenium retorna a imagem).

Essa classe foi criada para facilitar o report do bug, nada impede de criarmos outras funções com mais informações ou simplesmente colocar esse código todo no script. Isso foi feito pensando em uma maior reutilização de código... ;)

Classe ConnectMantis (Singleton)
Para que seja possível reportar o bug é necessário efetuar a conexão com o Mantis. A classe criada utiliza o Design Pattern Singleton, para que não exista várias instâncias de conexão com o Mantis, mantendo apenas uma ativa em toda a execução da aplicação.
Não é o foco explicar como funciona o Design Pattern Singleton, mas você pode clicar nos links abaixo para aprender um pouco. Vou me ater apenas a um trecho do código desta classe.

Design Pattern: http://en.wikipedia.org/wiki/Design_pattern
Singleton: http://en.wikipedia.org/wiki/Singleton_pattern

1:  public ConnectMantis() throws MalformedURLException, MCException {  
2:    URL url = new URL(MANTIS_URL);  
3:    sessao = new MCSession(url, MANTIS_USER, MANTIS_PWD);  
4:  }  

Na função acima, que está contida na classe é criado um objeto URL com a URL do Mantis que está na Interface IConstantes e na linha 3 é criado um novo objeto do tipo sessão (IMSession) passando a URL, usuário e senha do Mantis.

Interface IConstantes
A interface contém apenas constantes utilizadas em comum pela aplicação, e também para ter um ponto único de alterações de usuário e senha quando existir.

1:  public interface IConstantes {  
2:    static final String MANTIS_URL = "http://localhost/mantisbt-1.2.3/api/soap/mantisconnect.php";  
3:    static final String MANTIS_USER = "administrator";  
4:    static final String MANTIS_PWD = "root";  
5:    static final String PROJETO = "Integracao";  
6:  }  

Na linha 2 a contante é a URL de acesso aos serviços SOAP do Mantis. Tome cuidado quando você for alterar essa constante, pois você terá que colocar o seu servidor (onde aqui está como localhost) e o nome de acesso a aplicação (aqui está como mantisbt-1.2.3)
As outras contantes são o usuário na linha 3, a senha na linha 4 e o nome do projeto na linha 5.

4. Execução e modificação deste tutorial
Chegamos ao fim do tutorial. Se você deseja executar este tutorial baixe os fontes de exemplo criados no Eclipse e altere os dados de URL para o Mantis.

Se você alterar os dados da interface IConstantes o exemplo não funcionará, mas você pode usar o tutorial como base para a sua integração.
Por favor mandem sugestões e feedback's se este tutorial tem ajudado ou mesmo se estiver difícil de entender (claro que dentro de seus conhecimentos de programação Java).

4.1 Para entender mais
Você pode fazer a integração com outras linguagens de programação com o Mantis , não especificamente com o Selenium. Na verdade essa é uma implementação em Java para qualquer aplicação desenvolvida em Java, não específica para o Selenium. O que fiz foi inserir dentro do código do Selenium a integração!

Para saber mais da API Java utilizada para a comunicação com o Mantis acesse:

4.2 Fontes deste tutorial
Arquivo zipado contendo o projeto desenvolvido no Eclipse:
http://www.eliasnogueira.com/arquivos_blog/selenium/integracao/mantis/mantis-selenium-example.zip

Em breve sai o projeto com a integração em conjunto do Mantis e do Testlink!
Abraço a todos!

2 comentários:

  1. parabens, o seu tutorial é muto bem explicado. Elias sera que você poderia me dar um exemplo de como capturar prints de testes, direto do browser, usando selenium e java.

    ResponderExcluir
  2. Olá Rafael!
    Basta usar a função capturePageScreenShot() ou captureEntirePageScreenShot().
    A diferença é que a primeira tira uma screenshot da tela toda e a segunda somente da página que está sendo testada.

    Abraços!

    ResponderExcluir