Cucumber com Selenium e Spring

Aproveite todo o poder do Spring para facilitar abstrações

Willian Antunes
Editora Globo
6 min readOct 10, 2018

--

Quando escuto GIVEN, WHEN, AND e THEN, a primeira coisa que vem na cabeça é o famoso Cucumber! Caso não conheça, sugiro acessar o site da ferramenta. Olha só a execução do projeto que explicarei:

Como o exemplo contempla Selenium também, sugiro o artigo introdutório do Lucas Gigek sobre o assunto:

Vira e mexe precisamos realizar testes funcionais para garantir diversas regras de negócio, mas tem algo que acaba atrapalhando o desenvolvimento: um certo foco fora da implementação concreta do teste. Por exemplo, quando precisamos criar toda a navegação via design pattern Page Object de um hotsite muitas vezes é necessário realizar uma certa manobra para passar o WebDriver de uma instância para outra da página simulada em questão, e tem situações também que precisamos coletar evidências com prints de tela. OK, os dois exemplos são muito triviais, mas a coisa começa a complicar quando mais requisitos começam a surgir:

  1. Monitoria para garantir principais funcionalidades (smoke testing)
  2. Relatório do tempo de execução de cada método do Page Object
  3. Logs detalhados
  4. Evidências de tela

Uma resolução simples para o requisitos 2, 3 e 4 seria por exemplo com o uso do design pattern Proxy, só que abstrair algo que isole a mágica pode ser algo complexo e mais uma vez focaríamos fora da implementação principal do nosso teste funcional em si, então é aí que o Spring entra!

Colocando o container IoC para trabalhar

Em vez de nos preocuparmos com cada versão do Spring que devemos adicionar no projeto como as seguintes:

  • spring-core
  • spring-beans
  • spring-aop

Podemos passar a responsabilidade para o Maven BOM ou Spring Boot com o seu famoso parent a fim de evitarmos mutreta com versões e muito mais, no meu caso preferi o último. Feito isso, o próximo passo é adicionar o arquivocucumber.xml para que a dependência cucumber-spring possa achá-lo e então configurar aquilo que precisamos. A minha ideia é focar na programação programática, então deixei ele em src/test/resources com basicamente o seguinte conteúdo:

<context:component-scan base-package="br.com.globo"/>

Depois, para auxiliar em diversas situações nos testes, teremos a seguinte estrutura de pacotes a partir de br.com.globo:

  • annotations: Anotações customizadas.
  • aop: Mantém os Proxies.
  • conf: Configurações programáticas do Spring
  • pages: Representação lógica/concreta de nossas páginas
  • steps: Mantém os Step Definitions
  • support: Basicamente mantém nossos Utils da vida

Configurando WebDriver com download automático pelo WebDriverManager

Já vi alguns projetos com o executável do ChromeDriver na solução, em vez disso, já que não é boa prática, podemos usar o WebDriverManager e deixar toda essa responsabilidade pra ele. Veja como fica nossa classe de configuração:

Aproveitei e coloquei uma lógica para trocar entre um driver e outro mediante o uso de um perfil específico para executar headless. Saiba como trocar entre um perfil e outro aqui.

Obter arquivo de properties no formato YAML

Como não estou usando a autoconfiguração do Spring Boot (saiba os detalhes da penúltima seção), criei um bean do tipo PropertySourcesPlaceholderConfigurer que consome o objeto gerado pela instância da classe YamlPropertiesFactoryBean.

Configuração dos atributos anotados com FindBy via BeanPostProcessor

Se tem um recurso que ajuda muito do Selenium, é anotar um atributo de uma classe com FindBy e então, em função do By que inclui CSS, XPath, entre outros, receber um WebElement já configurado com a determinada referência. Então em vez disso:

WebElement e = browser.findElement(By.cssSelector("div p.jafar"));

Podemos deixar assim:

@FindBy(css = "div p.jafar")
private WebElement e;

Só que para isso funcionar, é necessário usar o PageFactory. No Spring existe uma interface chamada BeanPostProcessor, que permite aplicarmos algo após um bean ser criado no contexto:

Logando qualquer exceção e coletando evidências com Spring AOP

Já fiz uma postagem comentando sobre o design pattern Proxy com Spring AOP, só que focado em um parte específica, aqui faça algo um pouco diferente. Primeiro, criei um PointCut específico para os Step Definitions, análogo a um interceptador:

E então monitoro ele caso uma exceção seja lançada pelo AfterThrowing:

E para coletar evidência da tela após a execução de cada Step, uso o mesmo PointCut no After, aproveito também os próprios detalhes da implementação para salvar a evidência.

Um ponto importante é que tive que usar a anotação EnableAspectJAutoProxy em uma classe de configuração para habilitar a função, no caso deixei na WebDriverConfiguration.

E por fim nossa Feature de teste

Nosso arquivo feature de exemplo fica em src/test/resources/features. Conforme visto no GIF do começo da postagem, envolve o GitHub da InfoGlobo:

A configuração de execução do Cucumber fica na classe CucumberSettings:

E o código-fonte com a parte concreta para executar nossa feature:

O porquê de algumas coisas

Como o projeto cucumber-spring atua na execução dos testes para possibilitar o casamento de um projeto com o outro, tive que realizar algumas manobras para possibilitar a execução do projeto com sucesso, além disso, quem fuçar verá outras coisas que são explicadas abaixo.

Injetar ApplicationContext em vez do WebDriver

Veja que eu obtenho o WebDriver pelo ApplicationContext em vez de injetá-lo na classe PageObjectBeanPostProcessor. Só faço isso pois o escopo cucumber-glue inexiste quando o nosso bean do tipo BeanPostProcessor é criado, então se você tentar receberá o seguinte erro:

No Scope registered for scope name ‘cucumber-glue’

Trabalhar com escopo cucumber-blue em quase tudo

Em vez de termos instâncias singleton em tudo quanto é lugar (padrão do Spring), a ideia é manter parte dos beans vivos somente na execução de cada feature, ou seja, o WebDriver, que representa o nosso navegador, manterá seu estado do primeiro step até o último, depois será encerrado.

Configuração XML para escanear componentes em vez de programática

Conforme podemos ver aqui no código-fonte do projeto cucumber-spring, a configuração é obtida via cucumber.xml, e para deixar totalmente programática eu teria que realizar alguns macetes como estender uma classe com toda a configuração do Spring para cada execução do Cucumber (veja as referências no final do artigo), então para evitar esse acoplamento, achei melhor deixar assim.

Tem Spring Boot no projeto mas não usa a autoconfiguração dele

Eu basicamente usei ele para evitar conflitos e possíveis mutretas entre as versões do próprio Spring e suas dependências. Veja na imagem por exemplo que o spring-core é a versão 4.3.19-RELEASE.

Dependências dos starters do Spring Boot

Guava 22.0 sendo que o Selenium já o tem como dependência

Se tirarmos o Guava 22.0 do pom.xml e tentarmos executar o projeto, o seguinte erro será lançado:

java.lang.IllegalAccessError: tried to access method com.google.common.util.concurrent.SimpleTimeLimiter.<init>(Ljava/util/concurrent/ExecutorService;)V from class org.openqa.selenium.net.UrlChecker

E isso só acontece por causa de um conflito de versões entre as dependências do próprio Selenium:

Conflito para uso do Guava

A maneira que achei de resolver foi incluindo no pom.xml a seguinte dependência:

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>

Desse jeito o erro some e o projeto executa tranquilo!

Uso do Awaitility em vez do WebDriverWait nos asserts

O WebDriverWait é muito engessado e focado no Selenium, enquanto o Awaitility é flexível e focado no teste em si, assim posso usá-lo passando uma função lambda configurando de várias maneiras. Foi simplesmente uma escolha técnica diferente para ganhar mais liberdade

Uso do Spring-AOP em vez de Hooks

O motivo principal é usar algo agnóstico. Se por ventura precisarmos utilizar uma estrutura parecida em outro projeto que não use Cucumber, podemos copiar/colar pate do código sem problemas por não ser preso a ferramenta, mas é importante enfatizar que Hooks serviriam normalmente para o caso da evidência, e o mais legal é que ele pode receber beans injetados com a anotação Autowired.

É só um começo

O casamento entre Cucumber, Selenium e Spring é poderosa! Permite realizarmos várias coisas e o que foi mostrado aqui é só um começo. O que acho mais legal é que podemos focar cada vez mais na regra de negócio e deixar todo o trabalho maçante para o Spring. Abuse e use com consciência!

Referências:

Projeto completo:

Ao som de Evandro do Bandolim, Chorando Baixinho.

--

--