Por que eu devo ler este artigo:Este artigo é útil a todo desenvolvedor de testes automatizados que deseja conhecer mais a fundo os recursos do Selenium Web Driver, os quais, dentre outras coisas, permitem comunicação direta com as APIs de drivers dos diferentes navegadores, bem como fácil integração com frameworks de testes unitários como o jUnit e o TestNG. Veremos desde a configuração do ambiente, criação dos primeiros testes, até o gerenciamento de suítes completas em conformidade com o modelo de execução paralela, que possibilita acesso direto aos recursos do DOM HTML, além de converter todo o conteúdo para diversas linguagens de programação.

O Selenium é uma ferramenta utilizada para automatização de testes de sistemas que permite ao usuário reproduzi-los rapidamente no ambiente real da aplicação, em função da sua integração direta com o navegador. Através de uma extensão podemos criar os scripts de testes de forma simples e utilizar nas mais diversas linguagens de programação. No geral, o script é gerado em HTML, porém pode ser exportado e editado para testes em Java, C#, etc.


Guia do artigo:

Neste artigo trataremos de entender como funciona o Selenium Web Driver, um recurso adicionado recentemente ao framework que se acopla à API do Selenium, permitindo a execução direta de testes via código fonte no servidor. Ele faz uso de uma API JavaScript extensiva que, aliada ao motor JavaScript do próprio navegador, permite acesso ao documento DOM completo da página HTML, assim como a manipulação de suas propriedades e funções. Em outras palavras, ele permite traduzir o código de servidor para o código de cliente e executar nossos testes automatizados no universo front-end dos diversos browsers suportados.

Além disso, uma outra grande vantagem é a possiblidade de execução das suítes de testes de forma paralelizada, criando várias threads e executando-as concorrentemente, algo que não temos na versão gráfica do Selenium. Dessa forma, os desenvolvedores podem criar testes unitários e atrelá-los aos automatizados, dentro de loops que abrem várias instâncias dos drivers dos navegadores e permitem testar diferentes recursos das aplicações de forma muito mais rápida e performática.

Criando um círculo de feedback rápido

Muitas pessoas reclamam do quanto tempo levam para executar todos os seus testes, que vão de algumas horas a alguns dias. Vamos ver como podemos acelerar as coisas e colocar aqueles que estamos escrevendo em execução de forma regular e rápida.

O segundo problema que podemos nos deparar em trazer outras pessoas para executar seus testes é a dificuldade para configurar o projeto para trabalhar em sua máquina e o grande esforço para que eles façam o mesmo.

Isso cria um círculo rápido de feedback, isto é, se os seus desenvolvedores estão rodando todos os testes antes de cada check-in eles vão saber as alterações feitas no código antes que o mesmo deixe a sua máquina.

Facilitando os testes para desenvolvedores

O ideal é que nossos testes sejam executados sempre que alguém inserir novos códigos ao repositório de código central. Partindo desse princípio, alguém pode simplesmente ir à base de código, executar um comando e ter todos funcionando.

Nós vamos fazer isso facilmente utilizando o Apache Maven, um poderoso framework para integração e geração de builds de projetos, além de gerenciador de dependências (o usaremos para mapear as dependências do Selenium Web Driver no tutorial).

Todavia, o Maven não é a única solução para este problema (por exemplo, o Gradle está rapidamente ganhando popularidade e aceitação da comunidade), mas possui uma probabilidade maior de utilização na área, já que a maioria dos desenvolvedores o terão utilizado em algum momento de sua carreira.

Um dos principais pontos positivos é que ele incentiva os desenvolvedores a usar um padrão na estrutura do projeto que torna mais fácil navegar ao redor do código fonte; ele também torna mais fácil a conexão em um sistema de CI (Continuous Integration, ou Integração Contínua), como o Jenkins ou o TeamCity, por exemplo.

Ao fazer uso do Maven somos capazes de verificar o nosso código no teste, simplesmente executando o comando mvn clean install em uma janela do terminal. Este comando irá baixar automaticamente todas as dependências mapeadas para o projeto, configurar o caminho de classes e executar todos os testes.

Construindo projeto de teste com o Maven

Para fazer o devido uso do Maven precisamos primeiramente baixá-lo no site oficial. Portanto, acesse o link disponível da seção Links e efetue o download do arquivo em formato zip, dentro da categoria Link do mesmo site (veja na Figura 1 o arquivo correto a baixar).

Selecionando arquivo de
download do Maven
Figura 1. Selecionando arquivo de download do Maven.

Se estiver utilizando o sistema operacional Linux, pode fazer a mesma instalação usando a ferramenta apt-get, através do seguinte comando:

sudo apt-get install maven

Ou, se estiver utilizando um Mac, pode fazer o mesmo procedimento com o utilitário homebrew, via comando:

brew install maven

O próximo passo é adicionar o diretório /bin à variável de ambiente PATH do SO. Para isso acesse o menu Windows e digite “variáveis de ambiente”, selecione a opção “Editar Variáveis de Ambiente do Sistema” e, na janela que abrir, clique no botão “Variáveis de Ambiente...”. Em seguida, na nova janela, dentro das opções da categoria “Variáveis do sistema” procure pela variável Path, clique em “Editar” e no final do conteúdo insira um ponto-e-vírgula (;). Adicione também o caminho da pasta /bin completo de onde descompactamos o zip do Maven, tal como temos na Figura 2. Clique em OK três vezes e pronto, seu ambiente estará pronto para trabalhar com a ferramenta.

Configurando variável de
ambiente Path para o Maven
Figura 2. Configurando variável de ambiente Path para o Maven.

Depois de ter o Maven instalado e funcionando, vamos começar nosso projeto Selenium preenchendo o nosso arquivo básico de configuração do POM (pom.xml). O leitor pode ficar à vontade para usar qualquer IDE de sua preferência ou configurar os arquivos de forma manual no Windows Explorer. Para este artigo faremos uso da IDE Eclipse (vide seção Links para download), por sua simplicidade em se integrar com o Maven e manusear seus recursos.

Dentro da IDE selecione a opção File > New > Java Project, dê um nome ao seu projeto (selenium-web-driver) e clique em Finish. Isso será o suficiente para criar uma estrutura básica para executar as bibliotecas do Selenium Web Driver, assim como as suas dependências. Entretanto, antes temos que converter nosso projeto para ser reconhecido como um projeto Maven no ambiente. Clique com o botão direito sobre o projeto e selecione a opção Configure > Convert to Maven Project. Uma janela com as propriedades do POM aparecerá para informar os ids de artefato e grupo, além do tipo final de empacotamento que usaremos. Preencha todas as informações tal como mostramos na Figura 3 e clique em Finish.

Configurando parâmetros do POM
no Maven
Figura 3. Configurando parâmetros do POM no Maven.

Para efetuar os testes automatizados precisamos de um framework auxiliar de testes unitários para efetuar as operações de lógica de negócio na camada do servidor. Existem dois principais frameworks que podemos usar que se comunicam muito bem com o Selenium Web Driver: o jUnit (o mais famoso) e o TestNG (mais simples e didático). Optaremos por usar o TestNG, principalmente por ser mais fácil de obter o famoso up-and-running out-of-the-box (ambiente configurado e funcional de forma rápida), sendo o jUnit mais extensível para essa opção. Quando o assunto é Selenium, o TestNG é sempre tido como uma melhor opção nas listas de discussão da comunidade, com várias threads de perguntas sobre o assunto.

Portanto, para começar, vamos modificar o conteúdo do nosso arquivo gerado do pom.xml para o demonstrado na Listagem 1.

Listagem 1. Conteúdo inicial do arquivo pom.xml

  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <groupId>br.devmedia.selenium-testes</groupId>
            <artifactId>selenium-web-driver</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <build>
                        <sourceDirectory>src</sourceDirectory>
                        <plugins>
                                   <plugin>
                                               <artifactId>maven-compiler-plugin</artifactId>
                                               <version>3.3</version>
                                               <configuration>
                                                           <source>1.7</source>
                                                           <target>1.7</target>
                                               </configuration>
                                   </plugin>
                        </plugins>
            </build>
            <properties>
                        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
                        <!-- Versão das dependências -->
                        <selenium.version>2.48.2</selenium.version>
            </properties>
            <dependencies>
                        <dependency>
                                   <groupId>org.seleniumhq.selenium</groupId>
                                   <artifactId>selenium-java</artifactId>
                                   <version>${selenium.version}</version>
                                   <scope>test</scope>
                        </dependency>
                        <dependency>
                                   <groupId>org.seleniumhq.selenium</groupId>
                                   <artifactId>selenium-remote-driver</artifactId>
                                   <version>${selenium.version}</version>
                        </dependency>
                        <dependency>
                                   <groupId>org.testng</groupId>
                                   <artifactId>testng</artifactId>
                                   <version>6.9.9</version>
                                   <scope>test</scope>
                        </dependency>
            </dependencies>
  </project>

Temos aqui é um código XML puro do Maven. O groupId, artifactId, version e propriedades estão sujeitos às convenções de nomenclatura padrão:

  • groupId: Este deve ser um domínio\controle a ser introduzido ao contrário (como um nome de pacote no Java ou a URL de um site ao contrário);
  • artifactId: Este é o nome que será atribuído ao seu arquivo executável no final, por isso lembre-se de torná-lo no que você quer que seja chamado.
  • version: Esta deve ser sempre um número com -SNAPSHOT (uma imagem temporária) anexado ao fim. Isso mostra que ele atualmente é um trabalho em processo.

A tag <build> foi gerada automaticamente com as informações referentes à versão do compilador do Maven, bem como o nível do JDK que será usado pelo projeto. É importante que o leitor não modifique nenhuma dessas propriedades sob o risco de tornar o projeto não-funcional. A tag <properties> configura uma lista de propriedades em forma de constantes que podem ser usadas nas demais configurações. Nela incluímos a propriedade que define o encoding (codificação de caracteres) dos nossos arquivos de código (UTF-8), bem como a versão das bibliotecas do Selenium Web Browser (conhecido comumente como SeleniumHQ) que incluem as bibliotecas de suporte à API do Java, bem como as libs do driver remoto do Selenium em si.

Para que o nosso projeto funcione em conformidade com o encoding declarado no pom.xml precisamos converter sua formatação para o mesmo. Para isso, clique com o botão direito no projeto e selecione a opção Properties. Em Resource mude a opção Text file encoding para Other e a opção da combo para UTF-8.

É muito importante que o leitor se atente às versões das bibliotecas que está usando, e isso pode ser identificado através do groupId daquele lib em questão. Por exemplo, perceba que as dependências declaradas em seguida ao selenium-java e do selenium-remote-driver tem em seu atributo <groupId> o mesmo valor. Isso acontece porque ambas pertencem ao mesmo projeto Java publicado na central online de dependências do Maven, logo, sempre que uma versão nova for lançada do projeto (groupId), ambas as bibliotecas também terão suas versões atualizadas. É por isso que criamos uma propriedade em nível global e a inserimos no atributo <version> de cada uma.

É provável que ao fazer este tutorial uma versão mais recente que a 2.48.2 já tenha sido lançada. Para checar isso, basta acessar o site do mvnrepository (seção Links) e pesquisar pelo grupoId em questão. A mesma lógica vale para a biblioteca do TestNG, que pertence ao grupo org.testng.

O atributo <scope> configura qual nível daquela API será usado, isto é, em qual ambiente do Java. No caso testes, suas classes se farão úteis, assegurando que as dependências só sejam carregadas no classpath quando os mesmos forem executados.

Salve o arquivo e aguarde até que o Maven faça o build do projeto, baixando todas as dependências (precisaremos de uma conexão estável com a internet, sem proxy, para efetuar este passo). Para verificar se todas as libs foram baixadas com sucesso, clique com o direito sobre o projeto e selecione Build Path > Configure Build Path.... Na aba Libraries > Maven Dependencies, veja a lista de bibliotecas.

O leitor talvez se questione em relação à grande quantidade de bibliotecas adicionadas ao projeto, quando só configuramos duas dependências no pom.xml. Isso acontece porque algumas libs têm outras dependências próprias, o que acaba gerando um ciclo de dependência, resultando nessa quantidade de libs (49 no total). Essa é mais uma vantagem em usar o Maven, já que ele gerencia tudo isso sozinho e de forma automática.

Precisamos ainda organizar nossos pacotes de código fonte para dividir o que será classe de fonte comum e classe de teste. Para isso, acesse novamente o menu de Build Path e, na aba Source, clique sobre a pasta src apresentada, em seguida no botão Remove. Depois, clique em Add Folder... > Create New Folder... e dê o nome src/main/java ao mesmo, clicando em OK para finalizar. Faça o mesmo para o pacote src/test/java. No final, teremos dois novos diretórios de código fonte que poderão ser usados para separar suas classes. Quando fizer essa configuração, o Maven passará a dar um erro informando que não consegue encontrar mais o diretório de código fonte do projeto. Isso se deve à seguinte linha que foi inserida automaticamente quando da criação do projeto:

 <sourceDirectory>src</sourceDirectory>

Remova-a e o Maven voltará a funcionar normalmente.

Já temos então a base do nosso projeto Selenium. O próximo passo é criar um teste básico que possamos executar usando o Maven. Comece então criando uma nova classe de nome PrimeiroTeste.java, clicando com o botão direito na pasta src/test e selecionando New > Class. Preencha também o campo Package com o valor br.edu.devmedia.selenium. Insira o conteúdo da Listagem 2 à mesma.

Listagem 2. Primeiro teste com o Selenium Web Driver.

  package br.edu.devmedia.selenium;
   
  import org.openqa.selenium.By;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.WebElement;
  import org.openqa.selenium.firefox.FirefoxDriver;
  import org.openqa.selenium.support.ui.ExpectedCondition;
  import org.openqa.selenium.support.ui.WebDriverWait;
  import org.testng.annotations.Test;
   
  public class PrimeiroTeste {
            
            private void exemploGoogleQuePesquisaPor(final String stringPesquisa) {
                        WebDriver driver = new FirefoxDriver();
                        driver.get("http://www.google.com");
                        
                        WebElement campoPesquisado = driver.findElement(By.name("q"));
                        campoPesquisado.clear();
                        campoPesquisado.sendKeys(stringPesquisa);
                        
                        System.out.println("O título da página é: " + driver.getTitle());
                        
                        campoPesquisado.submit();
                        
                        (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
                                   public Boolean apply(WebDriver objDriver) {
                                               return objDriver.getTitle().toLowerCase().startsWith(stringPesquisa.toLowerCase());
                                   }
                        });
                        
                        System.out.println("O título da página é: " + driver.getTitle());
                        
                        driver.quit();
            }
   
            @Test
            public void googleExemploQueijo() {
                        exemploGoogleQuePesquisaPor("Queijo!");
            }
   
            @Test
            public void googleExemploLeite() {
                        exemploGoogleQuePesquisaPor("Leite!");
            }
  }

Repare que o Selenium tem suas estruturas semelhantes às da linguagem HTML usada no Selenium Web Browser (observe através do plugin do Firefox ou de outras implementações). Os dois métodos de teste, efetivamente, estão no fim da classe anotados com a annotation do TestNG @Test (atente-se para importar o pacote correto desta classe – org.testng.annotations.Test – pois o jUnit, bem como outros frameworks, usam a mesma). Ambos fazem uma chamada ao método exemploGoogleQuePesquisaPor() passando uma String com valores distintos.

O objetivo do teste, basicamente, é simular uma pesquisa à página do google.com (que funciona com requisições HTTP do tipo GET) passando no parâmetro “p” (reconhecido pela engina do Google) o valor a ser pesquisado.

O método exemploGoogleQuePesquisaPor() recebe a String de pesquisa, instancia o driver do Firefox (via classe FirefoxDriver, que vem dentro das libs do Selenium importadas) e submete a página a qual o mesmo deve acessar “virtualmente”. Em seguida, criamos um objeto do tipo WebElement que busca na página aberta um elemento HTML via regra de condição feita no método findElement() (no nosso caso procuramos o elemento pelo atributo name dele; o valor “q” se refere ao nome do campo de texto da caixa de pesquisa na página do Google). Uma vez com o elemento em mãos, podemos modificar seu valor através do método sendKeys(), bem como dar um Enter no mesmo via método submit() (neste caso, ele não é aplicado somente ao input de tipo submit, mas qualquer input que esteja dentro de um formulário na página).

A implementação seguinte trata de um tempo de espera de 10 segundos (via classe WebDriverWait), o suficiente para que a requisição retorne com a resposta. E quando isso acontecer, pegamos nosso objeto de driver e modificamos o título (tag <title>) para o valor da String enviada na pesquisa. Assim, podemos imprimir no Console da IDE o novo valor do mesmo. Não esqueça de sempre finalizar o driver (fechar o navegador), via método quit().

Para executar esse exemplo não basta tentar executar a classe como uma Java Application convencional porque não temos o método main(). Como se trata de testes unitários precisamos configurar o Maven para fazer o trabalho. Portanto, clique com o botão direito sobre o projeto e selecione a opção Run As > Run Configurations.... Depois, clique com o direito na opção Maven Build e selecione New. Na janela que abrir na lateral, preencha o campo Name com o valor Selenium Testes e o campo Goals com o valor clean install (comando para limpar e instalar o projeto no Maven). Em seguida, clique no botão Browse Workspace..., selecione o nosso projeto e clique em OK (veja na Figura 4 como suas configurações devem ficar).

Configurações do Maven para
executar testes
Figura 4. Configurações do Maven para executar testes.

Pronto, agora é só clicar em Run e o projeto será compilado e o build gerado. Sempre que quiser executar novamente basta clicar no botão de seta ao lado do Run no topo da IDE e selecionar a opção Selenium Testes.

Possíveis problemas no Maven

Dependendo de como seu ambiente tenha sido configurado, pode ser que seja preciso configurar um JDK (Java Development Kit) que o Maven tem dependência direta. Se a execução dos seus testes retornar um erro semelhante ao ilustrado na Listagem 3, precisamos executar os seguintes passos:

  1. Acesse o menu Window > Preferences > Java > Installed JREs;
  2. Verifique se existe algum JDK disponível;
  3. Caso só tenha JRE, clique no botão Add... > Standard VM, clique em Next;
  4. Em seguida, clique em Directory... e procure pela pasta do JDK instalado na máquina (geralmente em C:\Arquivos de Programas\Java);
  5. Clique em Finish, marque a checkbox do JDK importado e depois clique em OK.

Após isso, volte novamente à tela de Run Configurations e, na aba JRE, selecione a nova JDK na combo de Alternate JRE. Clique em Run e o código fonte, bem como o build do projeto, serão gerados com sucesso. Para saber se tudo correu bem, verifique se a mensagem exibida na Listagem 4 aparece no final do seu Console.

Listagem 3. Erro de JDK no Maven.

  [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project selenium-web-driver: Compilation failure
  [ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
  [ERROR] -> [Help 1]
  [ERROR] 
  [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
  [ERROR] Re-run Maven using the -X switch to enable full debug logging.
  [ERROR] 
  [ERROR] For more information about the errors and possible solutions, please read the following articles:
  [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
Listagem 4. Execução do build do projeto com sucesso.

  [INFO] ------------------------------------------------------------------------
  [INFO] BUILD SUCCESS
  [INFO] ------------------------------------------------------------------------
  [INFO] Total time: 3.162 s
  [INFO] Finished at: 2015-12-07T09:13:45-03:00
  [INFO] Final Memory: 18M/157M
  [INFO] ------------------------------------------------------------------------
  

Todos os passos até aqui apenas criaram a estrutura de projeto e build para os nossos testes. Todavia, ainda precisamos inserir um componente que se encarregue de executar os testes finais, que por sua vez, devem estar inseridos em uma suíte de testes. Esta é um conjunto de testes organizados em uma ou mais classes que nos possibilita organizar e dividir os mesmos, considerando que podemos ter inúmeros testes no projeto e não queremos executar todos de uma vez sempre que usarmos os comandos do Maven.

Para tal, faremos uso de um plugin do próprio Maven, criado exclusivamente para essa finalidade: o maven-surefire. Ele recebe um arquivo XML num formato pré-definido para suítes de testes, que contém a(s) classe(s) de teste que deve(m) ser executada(s) no projeto. Portanto, criemos primeiramente o arquivo de suíte. Na raiz do projeto crie um novo arquivo XML clicando com o botão direito e selecionando a opção New > Other.... Digite “xml” na caixa de pesquisa que aparecer e selecione a opção XML > XML File. Clique em Next, dê o nome testng.xml para o arquivo e clique em Finish. O arquivo deve conter a estrutura demonstrada na Listagem 5.

Listagem 5. Conteúdo do arquivo testng.xml.

  <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
  <suite name="Exemplo de Execução de testes com TestNG">
            <test name="Teste Simples - Google Page">
                        <classes>
                                   <class name="br.edu.devmedia.selenium.PrimeiroTeste" />
                        </classes>
            </test>
  </suite>

A estrutura desse tipo de arquivo deve sempre obedecer à hierarquia: suite > test > classes > class. Nela, podemos ter quantas suítes de teste quisermos no mesmo arquivo, assim como vários testes em uma suíte, várias classes em um teste, e assim por diante. Para cada suíte ou teste podemos opcionalmente dar um nome à mesma, que será exibido no Console quando o teste estiver em execução. Por fim, informe o nome da(s) classe(s) de teste do seu projeto que devem, obrigatoriamente, estar contidas dentro do diretório src/test/java.

Em seguida, abra o pom.xml e inclua o plugin definido na Listagem 6, logo após o plugin já incluso do maven-compiler-plugin.

Listagem 6. Inserindo novo plugin do Surefire.

  <!-- Plugin do Maven para executar os testes -->
  <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.19</version>
            <configuration>
                        <suiteXmlFiles>
                                   <!-- Arquivo(s) XML do TestNG suíte -->
                                   <suiteXmlFile>testng.xml</suiteXmlFile>
                        </suiteXmlFiles>
            </configuration>
  </plugin>

Veja que informamos na tag <version> a versão 2.19, via consulta à página do mvnrepository, bem como o nome do arquivo testng.xml que criamos na tag <suiteXmlFile>. Se tiver mais arquivos de suítes a serem considerados pelo plugin, insira-os em tags subsequentes abaixo umas das outras.

Para testar basta executar novamente a task Selenium Testes e aguardar o processo finalizar. Veremos o navegador do Firefox (se instalado) abrir duas vezes, acessar a URL do google.com, digitar os textos Leite! e Queijo! na caixa de pesquisa e fechar o navegador em seguida. Veja na Figura 5 um print da tela do navegador no momento em que o texto é consultado.

Print da tela do Firefox quando
texto é consultado
Figura 5. Print da tela do Firefox quando texto é consultado.

Veremos também no Console do Eclipse o log de execução dos testes, com seu resultado final, conforme mostra a Listagem 7. Veja como o valor do título da página é exibido antes da pesquisa, apenas com a palavra “Google”, e depois da pesquisa com o valor digitado acrescido do texto “ – Pesquisa Google”.

Listagem 7. Log final com resultado da execução dos testes.

  -------------------------------------------------------
   T E S T S
  -------------------------------------------------------
  Running TestSuite
  O título da página é: Google
  O título da página é: Leite! - Pesquisa Google
  O título da página é: Google
  O título da página é: Queijo! - Pesquisa Google
  Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 30.589 sec - in TestSuite
   
  Results :
   
  Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
Se tiver problemas para baixar as dependências, tente adicionar um -U no fim do comando de clean do Maven. Isso vai forçá-lo a verificar os repositórios da central para buscar as bibliotecas atualizadas.

Se os testes não funcionam, provavelmente as versões do seu Selenium e Firefox estão fora de sincronia. Verifique os pré-requisitos e refaça os passos até aqui para se certificar de que seu ambiente esteja configurado corretamente.

Agora temos um projeto básico configurado para executar um par de testes também básicos usando o Maven. Levando em consideração um cenário simples, com poucos testes leves, tudo vai funcionar rapidamente. Entretanto, quanto mais adicionamos testes ao seu projeto, mais as coisas vão começando a se debilitar em quesito performance. Para tentar suavizar este problema vamos utilizar todo o poder da sua máquina para executar os testes em paralelo.

Executando os testes em paralelo

Executar testes em paralelo significa muitas coisas diferentes em um ambiente que se comporta de maneira síncrona até o momento, a saber:

  • Executar todos os testes em vários navegadores ao mesmo tempo;
  • Executar os testes em várias instancia do mesmo navegador.

Isso são coisas bem diferente do ponto de vista programático. Devemos utilizar nossos testes em paralelo para aumentar a abrangência do nosso site, isto é, quando estamos escrevendo testes automatizados para garantir que as coisas funcionam com o site que estamos testando, certamente considerará, de início, que seu site deve funcionar em todos os navegadores. A realidade é que isto não somente é uma máxima, como também uma difícil realidade de encarar: já que muitos navegadores lá fora suportarão todo tipo de recurso como, por exemplo, sites que utilizam AJAX de forma intensa, ou o Flash no navegador web Lynx, baseado em texto, que pode ser usado em uma janela no terminal Linux.

Criar testes para todos os navegadores suportados pelo Selenium é bom, mas temos alguns problemas aqui. Algo que a maioria das pessoas não sabem é que o suporte ao navegador oficial do núcleo do Selenium é a atual versão do navegador e a versão anterior ao momento do lançamento de uma nova versão do Selenium. Na prática, ele pode muito bem trabalhar em navegadores mais antigos, já que a equipe do framework faz vários trabalhos para tentar garantir que eles não quebrem tal suporte. No entanto, se desejar executar uma série de testes no Internet Explorer 6, 7 ou 8, saiba que está executando testes em navegadores que não são oficialmente suportados pelo Selenium.

Em seguida, vem o nosso próximo conjunto de problemas. O Internet Explorer é suportado apenas em máquinas Windows, e podemos ter apenas uma única versão dele instalada na máquina por vez. Existem alguns hacks que podemos usar para instalar várias versões do Internet Explorer em uma mesma máquina, mas se fizer isso não irá conseguir testes precisos. O Safari é suportado apenas em máquinas OS X e, como no IE, só poderemos ter uma versão instalada de cada vez.

Logo, fica evidente que, mesmo que desejemos executar os testes em todos os navegadores suportados pelo Selenium, não seremos capazes de fazê-los em uma única máquina.

Neste ponto os desenvolvedores tendem a modificar a estrutura dos testes para que aceitem apenas uma lista de navegadores. Eles escrevem algum código que detecta, ou especifica os navegadores disponíveis em uma máquina. Depois de fazerem isso, começam a executar os testes em mais algumas máquinas em paralelo, mais ou menos como mostra a Figura 6.

Matriz de execução de testes por browsers
Figura 6. Matriz de execução de testes por browsers.

Outra preocupação recorrente é em relação à engine de JavaScript do navegador escolhido. O Internet Explorer é notoriamente conhecido pelas suas dificuldades em conciliar a execução de scripts (principalmente paralelos) com performance em aplicações web. Além disso, diferentes navegadores executam testes em velocidades diferentes porque os seus motores de JavaScript não são iguais.

Quando um teste falhar, devemos descobrir em qual navegador ele está sendo realizado, bem como o porquê da falha. Isso pode levar apenas um minuto do seu tempo, mas todos os minutos se somam. Então, por que não executamos os testes em apenas um tipo de navegador no momento? Vamos proceder da seguinte forma: faremos os testes em um navegador bom e rápido, e depois nos preocupamos com a compatibilidade cross-browser.

É uma boa ideia escolher somente um navegador para rodar os testes nas máquinas de desenvolvimento. Podemos usar um servidor de CI se preocupar com a cobertura do navegador como parte da nossa construção de pipeline. Procure focar sempre em navegadores mais usados no mercado, que tenham comprovadamente bons motores JavaScript.

Testes paralelos com TestNG

O TestNG suporta paralelamente threads out-of-the-box, só precisamos dizer onde usar. No nosso caso, estamos interessados nas definições de configuração em paralelo com a propriedade threadCount. Criaremos conjuntos paralelos de execução de métodos. Isto irá pesquisar em nosso projeto os métodos que possuem a anotação @Test e irá agrupá-los em um grande pool de testes. O plugin do Maven que adicionamos, então, leva os testes para fora deste pool e executa-os. O número de testes que serão executados simultaneamente dependerá de quantas threads estarão disponíveis. Usaremos a propriedade threadCount para controlar isso.

É importante notar que isso não garante a ordem que os testes serão executados. Estamos usando as definições da configuração da threadCount para controlar como muitos testes serão executados em paralelo, mas, como já deve ter notado, não especificamos um número. Em vez disso, usamos a variável ${threads} no Maven; isso vai levar o valor à propriedade threads do plugin Maven.

Então, comecemos executando o seguinte comando:

mvn clean install 

Esse comando irá utilizar o valor padrão 1 no arquivo do POM. No entanto, se desejar uma quantidade maior de threads, é só executar:

mvn clean install -Dthreads=2

Ele agora irá substituir o valor de 1 armazenado no arquivo POM para o valor 2. Isso nos dá a capacidade de ajustar o número de threads que usamos para executar nossos testes sem fazer quaisquer alterações de código físico.

Agora é preciso modificar o nosso código para tirar vantagem disso. Anteriormente, instanciamos uma instância do FirefoxDriver em cada um dos nossos testes. Vamos levar esta classe para fora do teste e colocar a instanciação do navegador em sua própria classe chamada WebDriverThread. Mas antes disso, vamos criar uma classe chamada DriverFactory que tratará da triagem das threads de uma forma geral. Siga as instruções da Listagem 8 para criar a referida classe.

Listagem 8. Classe DriverFactory – fábrica de drivers.

  package br.edu.devmedia.selenium;
   
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
   
  import org.openqa.selenium.WebDriver;
  import org.testng.annotations.AfterMethod;
  import org.testng.annotations.AfterSuite;
  import org.testng.annotations.BeforeSuite;
   
  public class DriverFactory {
            private static List<WebDriverThread> webDriverThreadPool = Collections.synchronizedList(new ArrayList<WebDriverThread>());
            private static ThreadLocal<WebDriverThread> driverThread;
   
            @BeforeSuite
            public static void instantiateDriverObject() {
                        driverThread = new ThreadLocal<WebDriverThread>() {
                                   @Override
                                   protected WebDriverThread initialValue() {
                                               WebDriverThread webDriverThread = new WebDriverThread();
                                               webDriverThreadPool.add(webDriverThread);
                                               return webDriverThread;
                                   }
                        };
            }
   
            public static WebDriver getDriver() throws Exception {
                        return driverThread.get().getDriver();
            }
   
            @AfterMethod
            public static void clearCookies() throws Exception {
                        getDriver().manage().deleteAllCookies();
            }
   
            @AfterSuite
            public static void closeDriverObjects() {
                        for (WebDriverThread webDriverThread : webDriverThreadPool) {
                                   webDriverThread.quitDriver();
                        }
            }
  }

A classe mantém dois atributos globais:

  • webDriverThreadPool, referente ao pool de threads com os drivers que usaremos para executar em paralelo os testes. O tipo genérico dessa lista é WebDriverThread (que ainda criaremos a seguir) e ela é instanciada a partir do método synchronizedList() de Collections, o qual retorna uma lista síncrona de dados;
  • o segundo atributo se refere à lista de threads que usaremos dentro do método de inicialização dos dados. Este método está declarado com a anotação @BeforeSuite, que funciona como um construtor de testes, executando antes de qualquer outra estrutura da suíte.

Estamos fazendo isso para isolar cada instância de WebDriver se certificando de que nenhum cruzamento de dados aconteça entre os testes. Quando eles começarem em paralelo, não queremos que diferentes testes disparem comandos para a mesma janela do navegador. Cada instância do WebDriver agora está bloqueada com segurança em sua própria thread.

No método estático getDriver() acessamos o método get() da lista de threads que retorna a próxima disponível, retornando o driver em si via método getDriver(). Também usamos as anotações @AfterMethod para executar após o método estático, cuja função será limpar os cookies do navegador para a próxima suíte de testes; e @AfterSuite, que se encarrega de fechar os drivers (os navegadores) fisicamente. Veja que neste último método estamos fazendo uso do método quit() do WebDriver que, por sua vez, trata de matar a instância do navegador que está aberta. Existe um outro método chamado close() que pode ser usado para essa finalidade, mas ele não fecha o navegador, mas apenas a aba atual que estiver aberta. Esse método é mais usado para quando abrimos várias abas no navegador e desejamos fechar apenas a atual.

Criemos agora uma nova classe de nome WebDriverThread, tal como mostra a Listagem 9.

Listagem 9. Classe WebDriverThread – thread individual.

  package br.edu.devmedia.selenium;
   
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.firefox.FirefoxDriver;
  import org.openqa.selenium.remote.DesiredCapabilities;
   
  public class WebDriverThread {
            private WebDriver webdriver;
            private final String operatingSystem = System.getProperty("os.name").toUpperCase();
            private final String systemArchitecture = System.getProperty("os.arch");
   
            public WebDriver getDriver() throws Exception {
                        if (null == webdriver) {
                                   System.out.println(" ");
                                   System.out.println("Sistema Operacional Atual: " + operatingSystem);
                                   System.out.println("Arquitetura Atual: " + systemArchitecture);
                                   System.out.println("Browser selecionado: Firefox");
                                   System.out.println(" ");
                                   webdriver = new FirefoxDriver(DesiredCapabilities.firefox());
                        }
                        return webdriver;
            }
   
            public void quitDriver() {
                        if (null != webdriver) {
                                   webdriver.quit();
                                   webdriver = null;
                        }
            }
  }

Essa classe, por sua vez, se encarrega de manter uma referência única ao objeto WebDriver do Selenium, além das propriedades de tipo do Sistema Operacional e arquitetura do mesmo (isso para deixar nossos testes mais completos e sabermos exatamente em que tipo de ambiente os estamos executando). Por isso, o método getDriver() imprime todas estas informações antes de criar o driver do Firefox efetivamente. No final, criamos o método quitDriver() para finalizar o mesmo.

Tudo que resta agora é criar uma nova classe de testes, muito semelhante a primeira, de nome TesteBasicoWD, que ao invés de criar a própria instância de driver, buscará do pool uma disponível. Para isso, siga os passos mostrados na Listagem 10.

Listagem 10. Classe de testes TesteBasicoWD.

  package br.edu.devmedia.selenium;
   
  import org.openqa.selenium.By;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.WebElement;
  import org.openqa.selenium.support.ui.ExpectedCondition;
  import org.openqa.selenium.support.ui.WebDriverWait;
  import org.testng.annotations.Test;
   
  public class TesteBasicoWD extends DriverFactory {
            private void exemploGoogleQuePesquisaPor(final String stringPesquisa) throws Exception {
                        WebDriver driver = DriverFactory.getDriver();
   
                        driver.get("http://www.google.com");
   
                        WebElement campoPesquisado = driver.findElement(By.name("q"));
   
                        campoPesquisado.clear();
                        campoPesquisado.sendKeys(stringPesquisa);
   
                        System.out.println("O título da página é: " + driver.getTitle());
   
                        campoPesquisado.submit();
   
                        (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
                                   public Boolean apply(WebDriver objDriver) {
                                               return objDriver.getTitle().toLowerCase().startsWith(stringPesquisa.toLowerCase());
                                   }
                        });
                        System.out.println("O título da página é: " + driver.getTitle());
            }
   
            @Test
            public void googleExemploQueijo() throws Exception {
                        exemploGoogleQuePesquisaPor("Queijo!");
            }
   
            @Test
            public void googleExemploLeite() throws Exception {
                        exemploGoogleQuePesquisaPor("Leite!");
            }
   
  }

Modificamos nossa classe de teste básico para que estenda de DriverFactory. Ao invés de instanciar uma nova instância de FirefoxDriver no teste, estamos chamando o método DriverFactory.getDriver() para obter uma instância de WebDriver válida. Finalmente, teremos removido o driver.quit() de cada teste, já que isso tudo deve ser feito por nossa classe DriverFactory agora.

Vamos girar o nosso teste novamente usando o seguinte comando:

mvn clean install

O teste executará da mesma forma que antes. Porém, agora podemos especificar algumas threads de desempenho via comando:

mvn clean install -Dthreads=5

Repare que dessa vez abrirão dois navegadores Firefox, com ambos os testes executados em paralelo e, em seguida, ambos os navegadores fecharão novamente. A maior consequência direta desse tipo de implementação é a diminuição do tempo necessário para executar uma suíte completa de testes. Isso ocorre porque a maior parte do tempo é gasto para compilar o código e carregar os navegadores. No entanto, à medida que adicionamos mais testes, essa diminuição no tempo torna-se cada vez mais e mais evidente.

Este é, provavelmente, um bom momento para ajustar o seu arquivo TesteBasicoWD.java e começar a adicionar mais alguns testes que buscam termos de pesquisas diferentes, ou até acessam sites diferentes. Mexa um pouco com o número de threads e veja quantos navegadores concorrentes podemos obter funcionando ao mesmo tempo. Certifique-se de imprimir no Console os tempos de execução para ver os ganhos de velocidade que está realmente tendo. Chegará um ponto onde alcançaremos os limites de hardware do seu computador e adicionar mais threads vai realmente retardar o processo todo ao invés de fazê-lo mais rapidamente. Ajustar os testes de acordo com o ambiente de hardware é uma parte importante da gestão de testes em vários segmentos.

Como de praxe, manter as janelas do navegador abertas enquanto se executa todos os testes não irá funcionar em todos os casos.

Às vezes, podemos ter um site que define os cookies do lado do servidor, os quais o Selenium desconhece. Neste caso, limpar os cookies pode não ter efeito nenhum e podemos achar que fechar o navegador é a única maneira de garantir um ambiente limpo para cada teste.

Se usar o InternetExplorerDriver, por exemplo, provavelmente perceberemos ao usar versões um pouco mais antigas do navegador (por exemplo, IE8 e IE9) que os testes ficarão cada vez mais lentos até que cheguem a um impasse. Infelizmente, as versões mais antigas do IE não são perfeitas e têm vários problemas de vazamento de memória.

Suporte a múltiplos navegadores

Até agora nossos testes em paralelo podem executar várias instâncias do navegador ao mesmo tempo. No entanto, ainda estamos usando apenas um tipo de condutor, o FirefoxDriver. Mas nosso código ainda não está adaptado para trabalhar com outros tipos de browser, isso porque o Firefox já tem uma comunicação muito próxima com o universo Selenium, inclusive disponibilizando um add-on para testes com o framework.

Para corrigir isso precisamos, inicialmente, criar uma nova interface que irá conter as assinaturas dos métodos que usaremos para criar os drivers e suas capacidades de forma dinâmica no código. Portanto, crie-a no pacote do projeto e dê o nome de DriverSetup. Inclua o código contido na Listagem 11 à mesma.

Listagem 11. Interface de configuração dos drivers - DriverSetup.

  package br.edu.devmedia.selenium;
   
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.remote.DesiredCapabilities;
   
  public interface DriverSetup {
            WebDriver getWebDriverObject(DesiredCapabilities desiredCapabilities);
            
            DesiredCapabilities getDesiredCapabilities();
  }

Veja que ela dispõe de dois métodos: o primeiro retorna um novo objeto do tipo WebDriver com base nas capabilities (capacidades) enviadas por parâmetro; e o segundo instancia essas capacidades de acordo com as especificidades de cada navegador.

Agora precisamos implementar a estrutura de código concreta que irá implementar esta interface e garantir que os métodos sejam sobrescritos em cada browser. Para isso, faremos uso das estruturas de Enum (enumerations) que se encaixam muito bem nesse propósito de construir código estático e em formata de listas de valores constantes. Dê o nome da estrutura de DriverType e adicione o conteúdo da Listagem 12 ao mesmo.

Listagem 12. Código do enum de DriverType.

  package br.edu.devmedia.selenium;
   
  import java.util.Arrays;
  import java.util.HashMap;
   
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.chrome.ChromeDriver;
  import org.openqa.selenium.firefox.FirefoxDriver;
  import org.openqa.selenium.ie.InternetExplorerDriver;
  import org.openqa.selenium.opera.OperaDriver;
  import org.openqa.selenium.remote.CapabilityType;
  import org.openqa.selenium.remote.DesiredCapabilities;
  import org.openqa.selenium.safari.SafariDriver;
   
  public enum DriverType implements DriverSetup {
   
            FIREFOX {
                        public DesiredCapabilities getDesiredCapabilities() {
                                   DesiredCapabilities capabilities = DesiredCapabilities.firefox();
                                   return capabilities;
                        }
   
                        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
                                   return new FirefoxDriver(capabilities);
                        }
            },
            CHROME {
                        public DesiredCapabilities getDesiredCapabilities() {
                                   DesiredCapabilities capabilities = DesiredCapabilities.chrome();
                                   capabilities.setCapability("chrome.switches", Arrays.asList("--no-default-browser-check"));
                                   HashMap<String, String> chromePreferences = new HashMap<String, String>();
                                   chromePreferences.put("profile.password_manager_enabled", "false");
                                   capabilities.setCapability("chrome.prefs", chromePreferences);
                                   return capabilities;
                        }
   
                        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
                                   return new ChromeDriver(capabilities);
                        }
            },
            IE {
                        public DesiredCapabilities getDesiredCapabilities() {
                                   DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer();
                                  capabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true);
                                   capabilities.setCapability(InternetExplorerDriver.ENABLE_PERSISTENT_HOVERING, true);
                                   capabilities.setCapability("requireWindowFocus", true);
                                   return capabilities;
                        }
   
                        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
                                   return new InternetExplorerDriver(capabilities);
                        }
            },
            SAFARI {
                        public DesiredCapabilities getDesiredCapabilities() {
                                   DesiredCapabilities capabilities = DesiredCapabilities.safari();
                                   capabilities.setCapability("safari.cleanSession", true);
                                   return capabilities;
                        }
   
                        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
                                   return new SafariDriver(capabilities);
                        }
            },
            OPERA {
                        public DesiredCapabilities getDesiredCapabilities() {
                                   DesiredCapabilities capabilities = DesiredCapabilities.operaBlink();
                                   return capabilities;
                        }
   
                        public WebDriver getWebDriverObject(DesiredCapabilities capabilities) {
                                   return new OperaDriver(capabilities);
                        }
            };
  }

Veja que ela apenas implementa as configurações individuais dos navegadores (Firefox, Chrome, IE, Safari e Opera) mais usados do mercado. Para chamar qualquer configuração basta sobrescrever a instanciação do driver na classe WebDriverThread para o seguinte código:

webdriver = DriverType.CHROME.getWebDriverObject(DriverType.CHROME.getDesiredCapabilities());

Salve todo o conteúdo e execute novamente o comando do Maven para dar build na aplicação. O resultado pode ser conferido na Figura 7.

esultado da execução dos testes no Google Chrome
Figura 7. Resultado da execução dos testes no Google Chrome.

Dependendo das configurações do seu sistema operacional, pode se fazer necessário o download do driver do navegador específico para funcionar em conjunto com o Selenium Web Driver. Veja na seção Links a URL com a relação destes itens.

O Selenium é uma ferramenta extremamente madura para lidar com o desenvolvimento de testes automatizados em vários âmbitos e te auxiliar no processo de teste das suas aplicações web.

Apesar de ser amplamente aceito, trata-se de um projeto aberto, mantido por desenvolvedores que dedicam seu tempo a contribuir e facilitar as nossas vidas. Por causa disso, o framework está em constante aprimoramento, inclusive de correções sobre seus bugs. Na seção Links encontra-se a página que lista todas essas melhorias para que você possa, eventualmente, se guiar quando erros acontecerem nos seus projetos.

Referências

Confira também