Blog > Ruby in Tests (Parte 2): Testes Funcionais Web com WebDriver

19/fev

Este post apresenta na prática como se dá a construção de testes funcionais em aplicações web usando RSpec e Selenium WebDriver, ambos frameworks open-source Ruby. Ao fim da leitura você será capaz de escrever o seu primeiro teste para avaliar uma aplicação web.

Pré-requisitos

O RSpec, framework discutido no post anterior, aqui servirá como base para os scripts que construiremos. Assim, antes de prosseguir na leiture deste post recomendo a leitura e execução dos exemplos contidos em Ruby in Tests (Parte 1): Testes de Unidade com RSpec.

Introdução

Automatizar testes já é rotina a maioria dos testadores e quando se fala em automação web um nome que logo vem a mente é Selenium WebDriver. Ele é um framework open-source bastante conhecido e disceminado na maioria das linguagens de programação, inclusive Ruby.

Em seu post, Introdução ao Selenium, Elias Nogueira descreve o Selenium WebDriver como: "[...] um conjunto de bibliotecas (bindings) para as linguagens de programação suportadas. Hoje em dia ela é amplamente utilizada por nos dar liberdade no momento da criação e alteração do script, pois podemos utilizar a linguagem de programação preferida e utilizamos de todo o poder dela (como criação de classes, loops, manipulação de erros, etc...).".

Este post apresenta as principais características e gira sobre um exemplo prático da construção de um teste usando esta abordagem.

Preparando o projeto

  1. Crie um diretório chamado "Web" dentro do diretório "Ruby in Tests"
  2. Abra o arquivo Gemfile contido no diretório "Ruby in Tests"
  3. Adicione o comando a seguir abaixo a linha que contém gem 'rspec', '~> 3.3.0': gem 'selenium-webdriver', '2.48.1'
  4. Execute o comando a seguir no prompt de comando: bundler update

Construindo o primeiro teste

Usaremos como base para este artigo, uma aplicação construída pela Qualister para uso dos alunos em treinamentos de teste, chamada QuickLoja. Ela basicamente é uma ferramenta utilizada para fazer pedidos de produtos. usaremos o seguinte roteiro:

  1. Acessar a página do QuickLoja;
  2. Fazer login;
  3. Acessar a tela de movimentações;
  4. Adicionar uma movimentação de saída;
  5. Fazer logoff.

Então vamos por a mão na massa!

Iniciaremos criando um arquivo Ruby no diretório "Web", chamado "gerenciar_movimentacoes_spec.rb" contendo a descrição de um teste usando RSpec, veja:

describe "Gerenciando movimentações" do
  context "Adicionando"
    it "movimentações de saída" do
    end
end

Veja que agora temos um método que ainda não havíamos utilizado no post anterior, chamado "context". Ele é um recurso do RSpec que ajuda a organizar melhor seus testes. Basicamente, restrigimos todos os "it"s contidos dentro do context a serem relacionados a adição de movimentações. Você pode ter quantos "context"s forem necessário, e dentro deles poderá colocar quantos "it"s forem necessários.

Para executar este teste, basta executar o comando abaixo:

rspec Web\gerenciar_movimentacoes_spec.rb --color --format documentation

O resultado será o a seguir:

.
Finished in 0.002 seconds (files took 0.18 seconds to load)
1 example, 0 failures

Perceba que este resultado não é bem descritivo, pois não apresenta exatamente quais testes passaram, e o que eles estavam executando. Para melhor estes resultados, podemos adicionar o parâmetro --format documentation, veja:

rspec Web\gerenciar_movimentacoes_spec.rb --color --format documentation

Vejamos agora o resultado obtido:

Gerenciando movimentaçoes
  movimentaçoes de saída
Finished in 0.003 seconds (files took 0.187 seconds to load)
1 example, 0 failures

Vamos criar um arquivo para adicionar as bibliotecas que utilizaremos em nossos testes, como, por exemplo a do Selenium WebDriver. Adicione o código abaixo e salve-o no diretório Ruby in Tests como "env.rb":

require 'selenium-webdriver'

Adicionaremos agora o código do Selenium WebDriver para executar a ação de adicionar a movimentação de saída. O primeiro passo é adicionar no topo do arquivo "gerenciar_movimentacoes.rb" a requisicao ao arquivo "env.rb":

require_relative "../env.rb"

Ótimo, agora já podemos começar a escrever código dentro do escopo do "it" usando o framework Selenium WebDriver, e o primeiro passo é configurar o navegador que utilizaremos. Essa configuração nos permitirá escolher o navegador, maximizá-lo e configurar algumas regras, como, por exemplo, qual será o tempo limite de espera para que os elementos sejam carregados na tela, veja:

# Instanciando e configurando o navegador web
navegador = Selenium::WebDriver.for :firefox
navegador.manage.window.maximize()
navegador.manage.timeouts.implicit_wait = 5

O comando "navegador = Selenium::WebDriver.for :firefox" fez com que a variável navegador instanciasse o navegador web que será aberto, no caso o Firefox. E agora, qualquer ação que desejemos efetuar no navegador deverá ser dada utilizando métodos contidos no objeto "navegador"

O código que criamos irá abrir e maximizar o Firefox, ele determina que caso algum elemento do qual eu queira interagir, por exemplo, um botão, não apareça na tela em até 5 segundos, o Seleniium WebDriver deverá lançar uma excessão de "Elemento não encontrado". Executar esse código agora faria com que o browser fosse aberto e maximizado.

Fique a vontade para executar o teste a qualquer momento, para ver os resultados do código que está gerando. O comando será o mesmo:

rspec Web\gerenciar_movimentacoes_spec.rb --color --format documentation

Vamos agora adicionar o método "get" logo abaixo das configurações do navegador para navegar até uma página, veja:

# Acessando a página inicial do QuickLoja
navegador.get('http://quickloja.qualister.info/')

Execute o teste e agora verá que após o navegador ser aberto ele abrirá o QuickLoja. Os próximos passos terão o objetivo de digitar informações nos campos e submeter o formulário, mas para isso teremos que identificar propriedades únicas dos elementos que desejamos interagir, então vamos analisar o HTML da página inicial do QuickLoja:

Vemos que os campos que queremos interagir, que são login e senha, possuem a propriedade ID e são únicos, ou seja, não existem dois campos com o mesmo ID. Então, podemos usar um método contido no objeto "navegador" para buscar os elementos pelo ID e interagir com eles, veja:

# Preenchendo o formulário
navegador.find_element(:id, 'usuariologin').send_keys("teste")
navegador.find_element(:id, 'usuariosenha').send_keys("123")

Vimos que para interagir com objetos contidos na página foi necessário utilizar o método "find_element", disponível no objeto "navegador". Este método exige dois parâmetros, o primeiro é a estratégia de pesquisa do elemento e o segundo é o valor pesquisado. Uma vez encontrado, podemos executar ações nele. Neste caso, usamos o método "send_keys" para digitar um texto no campo. Além de preencher os dados no campo, ainda precisamos clicar no botão Entrar, mas não temos identificadores únicos neste botão, então teremos que pensar em outra forma, como por exemplo, o fato dele ser o único botão da tela. Neste caso, podemos identifica-lo através da estratégia ":tag_name", veja:

# Preenchendo o formulário
navegador.find_element(:id, 'usuariologin').send_keys("teste")
navegador.find_element(:id, 'usuariosenha').send_keys("123")
navegador.find_element(:tag_name, 'button').click()

Veja que agora identificamos o botão e em seguida usamos o método "click" para clicar nele. Ao executar o teste, veremos que a ação que codificamos foi executada mas ainda resta avaliar se o o teste teve sucesso, para isso iremos avaliar se o texto contido na barra de título da página é "Administração QuickLoja", veja como é possível fazer isso:

  # Validando login com sucesso
  expect(navegador.title()).to eq("Administração QuickLoja")

Excelente! Agora já temos a primeira parte de nosso teste, que é o login, funcionando corretamente! Veja que utilizamos o método "expect", que aprendemos no primeiro post desta série, em conjunto com o método "title", que retorna o texto contido na barra de título do navegador.

Outras estratégias de identificação de elementos

Vimos que para interagir com elementos é necessário primeiro identifica-los. No caso do formulário de login, utilizamos as estratégias de identificação por ":id" e por ":tag_name". Vamos praticar outras formas de identificação de elementos.

O próximo passo do nosso teste é clicar sobre o link "Movimentações". Este link não possui ID e não é a único link da tela, mas ele tem uma característica única, que é seu texto. Em casos como este, podemos usar a estratégia ":link_text", veja:

# Clicando no link "Movimentações"
navegador.find_element(:link_text, 'Movimentações').click()

O próximo passo é clicar sobre o botão "Nova movimentação", então você poderia pensar em usar a estratégia ":tag_name", com o valor "button", como fizemos no formulário de login. Mas não funcionaria, porque? Veja o código HTML deste botão:

Ele tem aparência de botão, mas por trás do código, é um link. Isso pode acontecer em algumas aplicações web, por isso sempre temos que identificar os elementos olhando para seu código HTML. Bom, veja que ele não tem identificador único, mas ele é o único link que possui o texto "Nova movimentação", logo, podemos usar a estratégia ":link_text" novamente, veja:

navegador.find_element(:link_text, 'Nova movimentação').click()

A interação com o formulário de adição de movimentações exige a utilização de outros métodos e também outras estratégias, vamos ver seu código HTML:

Vemos que pela primeira vez teremos de interagir com um combobox, que usa um método diferente para interação, e mais, não temos IDs neste formulário, o que nos faz começar a usar uma outra estratégia, a ":name". Então vamos lá:

# Identifiando o campo "Tipo"
tipo = navegador.find_element(:name, 'movimentacaotipo')
# Instanciando-o como uma combobox
combo = Selenium::WebDriver::Support::Select.new(tipo) 
# Selecionando a opção basenado-se no texto apresentado ao usuário em tela
combo.select_by(:text, "Saída")

Poderíamos selecionar a opção também baseando-se na ordem em que ele se encontra no combobox (ex. 0 seria Entrada e 1 seria Saída) usando o parâmetro :index ou através da propriedade "value" da opção usando o parâmetro :value.

Agora basta digitarmos o valor e quais itens queremos adicionar a esta movimentação, identificando os elementos pelo seu atributo "name" e enviando texto via "send_keys", veja:

navegador.find_element(:name, 'movimentacaovalor').send_keys("59,90")
navegador.find_element(:name, 'movimentacaoitens').send_keys("PlayStation")

Agora vamos clicar no botão "Gravar". Veja o HTML dele:

Perceba que ele tem regras CSS declaradas dentro do atributo "class", algumas aplicações usam muito esse atributo, inclusive para destinguir um elemento de outro, então, podemos utiliza-lo como sendo um identificador único em alguns momentos. No WebDriver, quando o elemento tem apenas uma regra declarada em seu atributo class, podemos utilizar a estratégia ":class_name", mas no caso deste botão, em que temos duas regras, faz-se necessário usar a estratégia ":css", veja:

navegador.find_element(:css, '.btn.btn-primary').click()

Para aprender mais sobre como identificar elementos usando CSS Selector, acesse o site w3schools.com.

O próximo passo é efetuar a validação da mensagem de sucesso do cadastro, para isso, validaremos que a mensagem "Sucesso ao inserir a movimentação" foi apresentada na tela. Vejamos o HTML desta mensagem:

Veja que temos a propriedade "class", e assim, poderemo usar a estratégia ":css" para identificar a caixa da mensagem. Já para capturar o texto, será necessário usar a propriedade "text", veja:

# Validação da mensagem de sucesso
mensagem = navegador.find_element(:css, '.alert.alert-success').text
expect(mensagem).to eq("Sucesso ao inserir a movimentação")

Por último, iremos fazer logoff clicando no link "Sair" e então fechar o navegador, veja:

# Fazer logoff
navegador.find_element(:link_text, 'Sair').click()
# Fechar o navegador
navegador.close()

Hooks

Como vimos no primeiro post desta série, Hooks podem ajudar a estruturar o código, no caso deste nosso script, em tese, se tivermos muitos testes, todos terão a ação de abrir o browser e navegar para a página do QuickLoja, certo? Então, poderíamos adicionar estas duas ações em Hooks, desta forma todos os "it"s que criamos não necessitarão possuir os comandos referentes a abrir e fechar o navegador, veja:

before do
  # Instanciando e configurando o navegador web
  @navegador = Selenium::WebDriver.for :firefox
  @navegador.manage.window.maximize()
  @navegador.manage.timeouts.implicit_wait = 5
end
it "movimentações de saída" do    
  # Acessando a página inicial do QuickLoja
  @navegador.get('http://localhost:4567/quickloja/')
  # Preenchendo o formulário
  @navegador.find_element(:id, 'usuariologin').send_keys("teste")
  @navegador.find_element(:id, 'usuariosenha').send_keys("123")
  @navegador.find_element(:tag_name, 'button').click()
  # Validando que entrou
  expect(@navegador.title()).to eq("Administração QuickLoja")
  # Capturando um screenshot como evidência
  @navegador.save_screenshot("evidencia.png")
  # Clicando no link "Movimentações"
  @navegador.find_element(:link_text, 'Movimentações').click()
  # Clicando em "Nova movimentação"
  @navegador.find_element(:link_text, 'Nova movimentação').click()
  # Preenchendo o formulário
  tipo = @navegador.find_element(:name, 'movimentacaotipo')
  combo = Selenium::WebDriver::Support::Select.new(tipo) 
  combo.select_by(:text, "Saída")
  @navegador.find_element(:name, 'movimentacaovalor').send_keys("59,90")
  @navegador.find_element(:name, 'movimentacaoitens').send_keys("PlayStation")
  @navegador.find_element(:css, '.btn.btn-primary').click()
  # Validação da mensagem de sucesso
  mensagem = @navegador.find_element(:css, '.alert.alert-success').text
  expect(mensagem).to eq("Sucesso ao inserir a movimentação")
  # Fazer logoff
  @navegador.find_element(:link_text, 'Sair').click()   
end
after do
  # Fechar o browser
  @navegador.close()
end

Uma variável só tem a sua validade dentro de um método, ou seja, variáveis criadas no método "before" só valem no escopo "before", as criadas em "it"s, somente em escopo "it", e assim por diante. Mas nesse caso, precisamos usar a variável "navegador" que foi criada no método "before" dentro do método "it" e depois, também, no método "after". Para que isso funcione, em Ruby, usamos o "@" antes da variável, assim, a variável "navegador" criada no "before" vale pra todos os demais métodos.

Relatório de execução em HTML

Para gerar um relatório de execução dos testes no formato HTML basta adicionar os parâmetros --format html e --out arquivo_html.html, veja um exemplo:

rspec Web\gerenciar_movimentacoes_spec.rb --format html --out relatorio_web.html

A execução deste comando não traz retorno no prompt de comando, ele apenas gera o arquivo de relatório, semelhante a imagem abaixo:

Conclusão

Vimos que automatizar testes para aplicações web é bem simples usando RSpec e WebDriver, basta conhecer os comandos e entender como identificar um elemento através da leitura de seu HTML, escolher a melhor estratégia e interagir com ele utilizando métodos como click() ou send_keys(), por exemplo. Outra coisa que aprendemos foi que a utilização de expectativas é algo primordial em testes automatizados.

Links interessantes

  1. http://www.rubydoc.info/gems/selenium-webdriver/0.0.28/Selenium
  2. hhttp://www.w3schools.com/cssref/css_selectors.asp
  3. hhttp://www.w3schools.com/cssref/css_selectors.asp

POSTS RELACIONADOS

AGENDA

CURSOS RELACIONADOS