Entendendo os tipos de esperas no Selenium WebDriver

Ter um mecanismo de espera confiável é um dos maiores desafios quando estamos trabalhando com automação de testes pela UI. Neste post, vou explicar sobre os tipos de esperas no Selenium WebDriver (em Java) e como usá-las. No final do post, serão descritos dois casos um pouco mais complexos e como foram solucionados.

No Selenium WebDriver, de acordo com a própria documentação, existem dois tipos de esperas: implícita e explícita.

Implícita

A espera implícita serve, basicamente, para dizer ao WebDriver um tempo máximo pelo qual ele deve aguardar quando estiver tentando encontrar um elemento. Exemplo:

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);  

Lembretes:

  • Tempo default é 0 (zero) segundos.
  • Basta setar o valor uma vez!
  • 10 segundos costuma ser um tempo razoável.
  • É um recurso útil, mas não use somente ele. A espera explícita é mais flexível e confiável para seus testes, principalmente se a aplicação a ser testada usa muito JavaScript ou AJAX.

Explícita

A espera explícita diz ao WebDriver uma condição (ou tempo) para que ele aguarde antes de prosseguir com o teste. Um exemplo de espera explícita é o polêmico Thread.sleep(), que deve ser evitado ao máximo por ser uma má prática e por ser muito dependente do browser, da máquina onde os testes estão sendo rodados, ou até de alguma integração que sua aplicação possa ter com outros sistemas.

Com isso, use o WebDriverWait! Exemplo:

WebDriverWait wait = new WebDriverWait(driver, 10);  
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("botaoOk")));  

Lembrete:

  • Tempo de polling default é de 0,5 segundos (ou 500 milissegundos).

A classe ExpectedConditions

Na classe ExpectedConditions, existem várias condições predefinidas para usar nos seus testes. Abaixo, vou listar as condições que mais uso e dar algumas dicas.

Visibilidade de elementos
  • visibilityOfElement / visibilityOfElementLocated

Dica: opte por essa ExpectedCondition em vez da "presenceOfElement". A "presence" verifica apenas que o elemento está no DOM, enquanto a "visibility" verifica que o elemento está presente E enabled (ou seja, disponível para interação).

Texto presente em elemento
  • textToBePresentInElement / textToBePresentInElementLocated

Útil para aguardar até que uma mensagem de sucesso apareça em uma div, por exemplo. Lembrando que, para aguardar até que um texto esteja presente em um input de texto qualquer, use a ExpectedCondition "textToBePresentInElementValue".

Frames
  • frameToBeAvailableAndSwitchToIt

Como o nome diz, aguarda até que o frame esteja disponível e muda para ele, dessa forma não precisamos escrever o tão odiado "driver.switchTo().frame()".

Aguardar até que um checkbox (ou radio button) esteja selecionado
  • elementToBeSelected / elementSelectionStateToBe(WebElement element, true) / elementSelectionStateToBe(By locator, true)

Lembrando que é muito importante, principalmente com checkboxes, colocar uma espera como essa após o clique no elemento. Seu teste pode rodar "rápido demais" e acabar não dando tempo de selecionar o checkbox.

Aguardar até que um checkbox (ou radio button) esteja deselecionado
  • elementSelectionStateToBe(WebElement, false) / elementSelectionStateToBe(By locator, false)

Mesma dica que comentei acima.


Outros casos

Aplicações que usam muito AJAX

Em aplicações que usam muito AJAX, com campos muito dinâmicos, o WebDriver pode acabar "se perdendo" e lançando exceções como NoSuchElementException ou StaleElementReferenceException. O grande problema é que esses erros podem ser intermitentes, gerando falsos negativos. Testes com essa característica são chamados de flaky tests.

Passei por diversos problemas como esse ao testar aplicações que usam AJAX demais nos campos. A solução foi adicionar ignores à instância de wait, para aguardar pela condição especificada, ignorando as exceções que citamos. Além disso, acabei conhecendo e utilizando a classe FluentWait, que, segundo a API:

"é uma implementação da interface Wait que permite configurar o tempo de timeout, o intervalo de polling e adicionar ignores para exceções específicas."

Você pode declarar uma instância de FluentWait da seguinte forma:

Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)  
                       .withTimeout(10, TimeUnit.SECONDS)
                       .pollingEvery(1, TimeUnit.SECONDS)
                       .ignoring(NoSuchElementException.class)
                       .ignoring(StaleElementReferenceException.class);

Fica fácil de ler o que código acima faz: cria uma instância de FluentWait com timeout de 10 segundos, intervalo de polling de 1 segundo, e ignorando as exceções NoSuchElementException e StaleElementReferenceException. O FluentWait é extremamente flexível e pode ser usado nas mais complexas situações.

Alan Richardson (Evil Tester), autor dos livros "Selenium Simplified" e "Java For Testers", escreveu um post muito interessante sobre o uso do FluentWait com WebElements.

Aguardar até o valor de um atributo mudar

Em algum momento, pode surgir a necessidade de aguardar até que algum atributo de um elemento mude, por exemplo. Porém, essa prática não é muito recomendada (seção "Waiting for attributes"), então use-a com cuidado (ainda assim, é melhor do que usar Thread.sleep).

Vamos considerar o seguinte exemplo, que vivenciei recentemente:

Figura inputs

Na imagem acima vemos dois inputs de texto (A e B) e um botão "Calcular". Suponha que quando informo um valor no Input A e clico no botão, é feita uma requisição a um sistema externo e um valor é calculado e exibido no Input B. De acordo com as ExpectedConditions que vimos anteriormente, poderíamos, a princípio, usar a "textToBePresentInElementValue" para automatizar a espera dessa requisição, certo?

Poderíamos, mas apenas se soubéssemos exatamente qual valor seria retornado pela requisição. No caso, isso não era trivial e não era o foco do teste. Nesse teste, precisávamos apenas do Input B preenchido para prosseguir com o preenchimento de um form. Logo, bastava aguardar até que a tag "value" do Input B tivesse algum valor qualquer diferente de vazio.

A classe ExpectedCondition permite que você crie suas próprias condições customizadas. O exemplo em questão foi resolvido usando uma espera como a seguinte:

public static void aguardaAteQueValueMude(final WebElement element)  
{
    WebDriverWait wait = new WebDriverWait(driver, 10);

    wait.until(new ExpectedCondition<Boolean>() {
        public Boolean apply(WebDriver driver) {
            String value = element.getAttribute("value");

            if(!value.equals("")) {
                return true;
            }

            return false;
        }
    });
}

No trecho de código acima, observe que estou criando uma ExpectedCondition booleana que, a cada intervalo de polling, vai recuperar o conteúdo da tag "value" do elemento (no caso, o Input B) e, caso esse valor seja diferente de vazio (""), a condição retorna true. Caso contrário, após os 10 segundos de timeout, retorna false.


Para complementar, recomendo dar uma olhada nos links abaixo (alguns deles foram citados no texto). Recomendo também a palestra da Trish Khoo (Google) sobre falhas intermitentes, apresentada na Selenium Conference 2013.

Compartilhem suas dúvidas nos comentários, até o próximo post! :)


Referências

WebDriverAdvanced

http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html

http://seleniumsimplified.com/2012/08/fluentwait-with-webelement/

http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/support/ui/FluentWait.html

http://blog.mozilla.org/webqa/tag/webdriverwait/

http://vnrtech.blogspot.com.br/2013/04/selenium-explicit-wait.html


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

Contatos: stefanfk@gmail.com | Twitter | LinkedIn


comments powered by Disqus