Testes de aceitação automatizados com Selenium

Selenium Webdriver

 

O que é o Selenium? Por que usá-lo?

Fazer os testes de aceitação de uma aplicação web costuma ser bastante trabalhoso. A cada nova versão lançada da aplicação existe um conjunto de funcionalidades que sempre precisam ser testadas repetidas vezes. Inúmeras vezes. Além, é claro, das novas funcionalidades que foram adicionadas e que também precisam ser testadas. Portanto, estes testes consomem muito tempo e geram muito (re)trabalho para serem realizados. É aí que o Selenium pode ajudar.

O Selenium é uma ferramenta livre e gratuita que permite realizar os testes de uma aplicação de forma automatizada. Assim o trabalho repetitivo e cansativo de sempre testar as mesmas funcionalidades passa a ser realizado não mais por humanos, mas sim de forma automatizada pelo Selenium. Com isso, os testadores passam a se concentrar naquilo que realmente dá trabalho, deixando as tarefas repetitivas para serem feitas pelo computador.

Há claras vantagens em se utilizar uma ferramenta que automatize os testes:

  1. Tarefas repetidas são muito sujeitas a erro. É comum o testador ignorar ou esquecer parte do que precisa ser testado; isso não ocorre quando automatizamos, pois, os passos descritos são seguidos à risca pelo computador. Ele é obediente e nunca cansa!
  2. A capacidade de teste do ser humano é limitada. Se hoje um testador testa 5 funcionalidades por hora, é muito provável que amanhã ele continue testando a mesma quantidade nesse intervalo de tempo. Talvez ele progrida um pouco, mas nada tão absurdo. Já a bateria de testes realizada por um computador (ou vários, pois, podemos montar um grid e paralelizar a execução) tem uma curva de crescimento muito mais acentuada. Ele é capaz de executar muito mais tarefas em menos tempo.
  3. A regressão diminui bastante com o uso de ferramentas automatizadas. A regressão ocorre quando novas funcionalidades de um sistema fazem com que funcionalidades pré-existentes deixem de funcionar. Nos testes manuais, principalmente em grandes sistemas, a regressão ocorre com alguma frequência, pois, os testadores nem sempre conseguem testar todos os cenários possíveis. Já com os testes automatizados isso diminui consideravelmente já que ele executa SEMPRE todos os testes: tanto os das novas funcionalidades quanto os antigos. Daí, ele é capaz de pegar erros em testes antigos que podem ter sido gerados pela inclusão de novas funcionalidades.

Claro que nem sempre é possível automatizar tudo. Mas o que for, devemos fazê-lo.

Como o Selenium Funciona?

Os testes de aplicações web, em sua maioria, envolvem o uso de um navegador web. O Selenium permite simular o comportamento do usuário utilizando um navegador web.

Quando utilizamos o Selenium para fazer testes automatizados, basicamente temos duas ferramentas para utilizar:

  • Selenium IDE: é uma ferramenta que permite a rápida prototipagem de scripts de testes. É um plugin do Firefox que possibilita gravar o comportamento do usuário: página que ele acessou, textos escritos em formulários, cliques em links e botões etc. Depois de gravadas, estas ações podem ser exportadas para um script de teste em diversas linguagens de programação: Java, Python, Perl, JavaScript etc.
  • Selenium WebDriver: é uma ferramenta que oferece uma API que permite a escrita de forma mais produtiva e organizada de scripts de testes. Essa é a escolha natural quando desejamos escrever testes automatizados para aplicações web utilizando o Selenium. Os exemplos desse post utilizam o WebDriver.

O Selenium WebDriver faz chamadas diretamente ao navegador utilizando o suporte à automação nativo de cada navegador. Assim os testes escritos com o WebDriver são bastante realistas, pois, em vez de usar um engine JavaScript próprio (como era feito no Selenium 1), ele chama diretamente o navegador.

O Selenium suporta praticamente todos os navegadores web existentes: Google Chrome, Firefox, Internet Explorer, Safari, Opera etc.

Exemplos de uso do Selenium WebDriver

Vamos mostrar exemplos do uso do Selenium WebDriver com a linguagem Java. Os exemplos aqui descritos todos estão no meu GitHub. Clique aqui e acesse o código completo deste post.

Configuração básica inicial

Para executar os testes com Selenium, antes de mais nada, é necessário adicionar a biblioteca do Selenium WebDriver ao classpath de sua aplicação. Utilizando o Maven, basta adicioná-la ao pom.xml do seu projeto conforme abaixo.

<dependency>
	<groupId>org.seleniumhq.selenium</groupId>
	<artifactId>selenium-java</artifactId>
	<version>3.0.0-beta2</version>
	<scope>test</scope>
</dependency>

Adicionando esta dependência, o Selenium está pronto para uso – inclusive com diversas opções de implementações da interface WebDriver. Basta escolhermos uma dessas implementações e começar a escrever nossos testes.

Um caso clássico de uso do Selenium é o preenchimento de formulário HTML e submissão dos dados preenchidos. Vamos fazer um exemplo básico com isso.

Para uso em nosso exemplo, vamos utilizar o preenchimento do formulário do site do Correios que nos permite a busca de logradouro por bairro. O formulário é o exibido abaixo.

Formulário de pesquisa do Correios de logradouro por bairro
Formulário de pesquisa do Correios de logradouro por bairro

Basicamente, portanto, precisamos preencher os 3 campos existentes (UF, Localidade e Bairro) e clicar no botão Buscar para a pesquisa ser realizada.

Preenchendo dados de um formulário com o Selenium

Precisamos ter em mente que ao escrever códigos/testes que utilizam a API do Selenium precisamos conhecer o código HTML da página a ser testada. Isso porque vamos instruir o Selenium quais elementos da página ele vai acessar para realizar suas ações (preencher texto, selecionar valor, clicar em links etc.).

Assim, vamos ter que escolher o valor de um select(UF), preencher 2 inputs (Localidade e Bairro) e clicar no botão Buscar. Ao verificar o código HTML, descobrimos os seguintes dados sobre cada um desses elementos:

  • UF: é uma tag select com o atributo name="UF"
  • Localidade: tag input com o atributo name="Localidade
  • Bairro: tag input com o atributo name="Bairro"
  • Botão “Buscar”: tag input com o atributo type="Submit"

Vamos utilizar essas informações para escrevermos o nosso código de teste. Realizaremos os seguintes passos:

  1. Visitar a página do Correios que contém esse formulário
  2. Preencher os campos UF, Localidade e Bairro com os valores “RJ”, “Rio de Janeiro” e “Copacabana”, respectivamente
  3. Clicar no botão “Buscar”
  4. Verificar o resultado da busca.

O código completo está abaixo.

@Test
public void preencheFormularioCorreiosBuscaLogradouroPorBairro() {
  WebDriver driver = new ChromeDriver();
  // Visita a página do Correios
  driver.get("http://www.buscacep.correios.com.br/sistemas/buscacep/buscaLogBairro.cfm");

  // Escolhe o valor de UF
  Select selectUF = new Select(driver.findElement(By.name("UF")));
  selectUF.selectByVisibleText("RJ");
  // Preenche a Localidade com o valor "Rio de Janeiro"
  WebElement inputLocalidade = driver.findElement(By.name("Localidade"));
  inputLocalidade.sendKeys("Rio de Janeiro");
  // Preenche o campo Bairro com o valor "Copacabana"
  WebElement inputBairro = driver.findElement(By.name("Bairro"));
  inputBairro.sendKeys("Copacabana");

  // clica no botão Buscar
  WebElement buttonBuscar = driver.findElement(By.cssSelector("input[type='submit'"));
  buttonBuscar.click();
}

Os comentários do código anterior são auto-explicativos, mas vale alguns esclarecimentos:

  • A primeira linha cria um driver; essa é a classe do Selenium por meio da qual acessamos elementos da página e aplicamos ações em cima dos mesmos.
  • A segunda linha (driver.get("...")) permite carregar uma página HTML a partir de uma URL.
  • As linhas a seguir seguem sempre o mesmo padrão; acessamos um elemento da página e aplicamos uma ação nele; observe que usamos o método findElement(By.name("...")) para acessar o elemento por meio do atributo HTML name. A diferença ocorre no caso do botão “Buscar” que não possui o atributo name; nesse caso, estamos utilizando o método By.cssSelector() e acessamos por meio do atributo type da tag input.
  • Utilizamos os métodos a seguir para aplicar ações aos elementos:
    • selectByVisibleText: pemite selecionar um valor de um select pelo texto que é exibido ao usuário
    • sendKeys: coloca texto em um campo input text
    • click: realiza o clique em botões e links

Ao executar este teste, é aberta uma janela do Chrome; nessa janela o formulário é preenchido e o botão é clicado para realizar a pesquisa.

Nota: observe que utilizei em meu código o ChromeDriver; lembrando que, neste caso, é necessário ter o ChromeDriver no path da sua máquina para que tudo funcione perfeitamente. Você pode, também, adicionar a linha abaixo antes da criação do driver para que o ChromeDriver seja encontrado.

   System.setProperty("webdriver.chrome.driver",
           "caminho-do-chromedriver");

Transformando o código anterior em um caso de teste

O código anterior está quase completo, mas ele não testa nada. Um caso de teste típico tem 3 passos bem definidos:

  1. Preparação dos dados: no nosso caso, é o preenchimento do formulário;
  2. Execução do teste: é o clique do botão; e
  3. Verificação do resultado: precisamos verificar se a pesquisa deu certo, isto é, se trouxe resultados.

Neste caso específico, ao realizar a pesquisa, é carregada uma outra página com uma tabela listando os resultados.

Página com resultados da busca
Página com resultados da busca

O que precisamos é verificar se esta tabela existe e há resultados nela. Vamos ao código.

   String codigoPagina = driver.getPageSource();
   assertThat(codigoPagina, Matchers.containsString("DADOS ENCONTRADOS COM SUCESSO."));
   assertNotNull(driver.findElement(By.cssSelector("table.tmptabela")));

Agora sim! Temos asserções. A primeira linha em destaque, por meio de uma asserção Hamcrest, procura pela string “DADOS ENCONTRADOS COM SUCESSO” no código-fonte da página, que foi obtido utilizando o método getPageSource() do Selenium. A segunda linha em destaque verifica se existe a tabela com os resultados – isso é feito por meio do método cssSelector do Selenium (a tabela de resultados tem o atributo class="tmptabela").

A figura abaixo mostra o resultado da execução do teste no Eclipse.

Teste do Selenium executado com sucesso
Teste executado com sucesso (barra verde)

Para ver o teste falhar, basta alterar a localidade, por exemplo, para São Paulo – agora a barra fica vermelha indicando que ele não conseguiu encontrar o texto de sucesso.

Teste falha após modificar a localidade
Teste falha (barra vermelha) após modificar a localidade – veja as linhas destacadas acima

Escrevendo testes Selenium que envolvem chamadas Ajax

Um uso comum do Selenium e que merece atenção por causar confusão e ter alguma dificuldade por parte de quem usa a ferramenta ocorre em testes que envolvem chamadas Ajax. Tipicamente, neste caso, o clique em um link ou em um botão dispara uma chamada Ajax que atualiza parte da página.

Vamos usar como exemplo a pesquisa do Google. Ao colocarmos um texto na caixa de pesquisa, o resultado é carregado por meio de chamada Ajax. O código de teste abaixo verifica se uma pesquisa no Google trouxe resultados.

 @Test
 public void pesquisaNaPaginaGoogleTermoTesteAceitacao() {
   WebDriver driver = new ChromeDriver();
   driver.get("http://www.google.com.br");
   driver.findElement(By.name("q")).sendKeys("testes de aceitação");
 
   assertNotNull(driver.findElement(By.cssSelector("div.srg")));
 }

Ao executar esse teste, conforme exibido na figura abaixo, o nosso assert falha. O Selenium nos informa que não conseguiu encontrar a div com a classe srg (a div que contem os resultados da pesquisa).

Teste de pesquisa do Google com falha
Teste da pesquisa do Google falha mesmo trazendo resultados (veja a janela atrás do código com os resultados). Por quê?

E por que isso ocorre? Simples. O teste executado não espera pelo retorno da chamada Ajax; a asserção é feita antes de os resultados retornarem. Logo, a div que procuramos realmente não existe! Precisamos então fazer com que o Selenium espere pelo resultados antes de fazermos a verificação.

O que muitos desenvolvedores fazem, como uma solução natural, é adicionar um Thread.sleep() antes da chamada assertNotNull. Se vocês fizerem isso, verão que o teste passa – a div é encontrada. No entanto essa solução não é ideal, pois, o resultado do teste passa a ser não determinístico. Ora ele passa, ora ele não passa – pois o tempo do sleep pode não ser suficiente (o servidor pode demorar a responder, por exemplo). Ter testes desse tipo é muito ruim, já que não são confiáveis e nunca sabemos se o teste realmente falhou porque o elemento não existe ou porque o timeout não foi suficiente. Definitivamente essa não é a solução.

O Selenium nos fornece duas soluções melhores para esse caso: explicit waitsimplicit wait.

Explicit Wait

Utilizamos Explicit Wait para esperar que uma determinada condição ocorra antes que a execução do código continue. Por exemplo: que a execução do código só continue se um elemento estiver presente na tela. Veja abaixo como fica o nosso código de teste de pesquisa do Google com Explicit Wait.

 @Test
 public void pesquisaNaPaginaGoogleTermoTesteAceitacaoComExplicitWait() {
   WebDriver driver = new ChromeDriver();
 
   driver.get("http://www.google.com.br");
   driver.findElement(By.name("q")).sendKeys("testes de aceitação"); 

   WebDriverWait wait = new WebDriverWait(driver, 30);
   WebElement divResultados = 
      wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("div.srg")));
 
   assertNotNull(divResultados); 
 }

As linhas em destaque mostram o Explicit Wait. Utilizamos a classe WebDriverWait que nos permite esperar até que (until()) uma condição ocorra – no nosso caso, a div esteja presente. Se o timeout de 30 segundos expirar (segundo argumento do construtor WebDriverWait), é lançada uma exceção TimeoutException e o teste vai falhar.

Implicit Wait

Com o Implicit Wait informamos ao driver que desejamos que ele faça um pooling por um tempo determinado ao tentar encontrar elementos na página. A configuração padrão do driver é esperar 0 segundos. O código abaixo mostra o Implicit Wait.

 @Test
 public void pesquisaNaPaginaGoogleTermoTesteAceitacaoComImplicitWait() {
   WebDriver driver = new ChromeDriver();
   driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);

   driver.get("http://www.google.com.br");
   driver.findElement(By.name("q")).sendKeys("testes de aceitação");

   assertNotNull(driver.findElement(By.cssSelector("div.srg")));
 }

A linha em destaque configura um Implicit Wait de 30 segundos. Com isso, daí pra frente, toda tentativa de encontrar um elemento na página, como o findElement do exemplo, vai ser feita por até 30 segundos antes de que seja lançada uma NoSuchElementException.

Conclusão

Como pode ser visto o Selenium WebDriver é uma ferramenta muito útil para a automatização de testes de aceitação. Ele fornece suporte para diversos navegadores Web e possui API para as mais diversas linguagens.

Para conhecer mais sobre o Selenium WebDriver. clique aqui e acesse a página de documentação dele.

Se quiser conhecer o Selenium IDE (que não foi o foco deste post) sugiro uma leitura no excelente post do Elias Nogueira.

Lembrando que todo o código desse post encontra-se disponível aqui em meu GitHub.

SAIBA MAIS: agora que você já sabe automatizar testes de aceitação, aprenda a organizá-los melhor com o Page Object clicando aqui!
  • Gleidson

    Este post ficou muito Top. Além de teoria e conceitos, ainda foi dado muitas dicas práticas.

    • André Thiago

      Valeu, Gleidson!

  • Marcelo

    Muito bom André! Notei muitas semelhanças quando criamos testes do Selenium com Javascript, utilizando o Protractor, que sincroniza com o AngularJs.

    • Sim, Marcelo. As ferramentas de automação costumam seguir a mesma filosofia. E no caso do Protractor, se não estou enganado, ele usa o Selenium por trás dos panos pra executar os testes.

  • Matheus

    Muito bom!

    • Valeu, @disqus_gz2dnPRLmk:disqus ! Muito bom saber que você gostou do conteúdo! Abraços!