Ciclo de Vida do Browser e Contexto Isolado por Teste (Spring Framework)
Depois de ler esta aula, você entenderá como podemos configurar nossos testes End-to-End quando estamos escrevendo testes para uma aplicação web que utiliza o Spring Framework.
Durante esta aula, utilizaremos o navegador Google Chrome (e equivalentes modernos como o Chromium via Playwright/Selenium), mas técnicas exatamente idênticas também funcionam com outros navegadores suportados.
Vamos começar descobrindo como podemos configurar nossa classe de teste.
1. Configurando Nossa Classe de Teste
Se não quisermos gerenciar o ciclo de vida dos nossos objetos WebDriver (ou Page / BrowserContext no Playwright) manualmente dentro das classes de teste, precisamos encontrar uma maneira de:
- Criar um novo objeto
WebDriverantes que um método de configuração (@BeforeEach) seja invocado. Isso garante que possamos utilizar o objetoWebDrivercriado dentro dos nossos métodos de setup. - Injetar o objeto
WebDrivercriado em nossa classe de teste para que os métodos@BeforeEache@Testtenham acesso imediato a ele. - Fechar o navegador (
quit()) depois que um método de teardown (@AfterEach) tiver sido executado. Isso garante que ainda possamos utilizar o objetoWebDriverdentro dos nossos métodos de limpeza caso necessário (por exemplo, para capturar screenshots ou logs em caso de falha).
Podemos implementar essas funcionalidades criando duas anotações customizadas e uma extensão/runner do JUnit que gerencia o ciclo de vida do nosso objeto WebDriver (no JUnit 4 original usávamos um BlockJUnit4ClassRunner customizado chamado SeleniumTestRunner; no JUnit 6 (Jupiter), implementamos esse mesmo mecanismo de 11 passos de forma limpa através das interfaces BeforeEachCallback, AfterEachCallback e ParameterResolver, além de injeção direta no campo anotado).
Vamos começar criando as duas anotações customizadas que são utilizadas para configurar a classe WebDriver instanciada e identificar o campo onde o objeto WebDriver criado será injetado:
Passo 1: Criando a Anotação @SeleniumTest
Primeiro, criamos uma anotação chamada @SeleniumTest. Utilizamos essa anotação para configurar o tipo da classe WebDriver que será instanciada antes da execução de um método de teste (e para registrar automaticamente nossa extensão SeleniumTestExtension via @ExtendWith).
Ao inspecionarmos o código-fonte da anotação @SeleniumTest, vemos que:
- Ela possui um atributo
driverque retorna o tipo doWebDriver(KClass<out WebDriver>) que será instanciado. - O valor padrão do atributo
driveréChromeDriver::class, o que significa que, por padrão, nossos testes End-to-End serão executados utilizando o navegador Google Chrome. - Ela já é meta-anotada com
@ExtendWith(SeleniumTestExtension::class), eliminando a necessidade de declarar@ExtendWithrepetidamente nas classes de teste.
O código-fonte da anotação SeleniumTest (em src/endToEndTest/kotlin/com/testwithspring/intermediate/SeleniumTest.kt) fica assim:
|
|
Passo 2: Criando a Anotação @SeleniumWebDriver
Segundo, criamos uma anotação chamada @SeleniumWebDriver. Utilizamos essa anotação para identificar o campo (propriedade) da classe de teste no qual a extensão deverá injetar o objeto WebDriver recém-criado.
O código-fonte da anotação SeleniumWebDriver (em src/endToEndTest/kotlin/com/testwithspring/intermediate/SeleniumWebDriver.kt) fica assim:
|
|
2. Implementando o Gerenciador de Ciclo de Vida do Browser
Depois de criarmos essas duas anotações, precisamos escrever o componente do JUnit que gerencia o ciclo de vida do nosso objeto WebDriver. No JUnit 4 clássico, estendíamos a classe BlockJUnit4ClassRunner (sobrescrevendo withBefores e withAfters); no JUnit 6, implementamos BeforeEachCallback (que executa antes de qualquer método @BeforeEach) e AfterEachCallback (que executa depois de qualquer método @AfterEach).
Podemos criar nossa extensão (e equivalente estrutural do SeleniumTestRunner) seguindo estes 11 passos:
- Criar a classe
SeleniumTestExtensionimplementandoBeforeEachCallbackeAfterEachCallback(ou estendendoBlockJUnit4ClassRunnerem suítes legadas comjunit-vintage-engine). - Validar a configuração da classe de teste antes de preparar a instância.
- Encontrar a anotação
@SeleniumTestna classe de teste. Se essa anotação não for encontrada na classe de teste, lançamos umaIllegalStateException. - Encontrar os campos da classe de teste que estão anotados com a anotação
@SeleniumWebDriver. - Garantir que a classe de teste possua exatamente um campo anotado com
@SeleniumWebDriver. Se nenhum campo for encontrado, ou se mais de um campo for encontrado na classe de teste, lançamos umaIllegalStateException. - Garantir que o campo anotado com
@SeleniumWebDrivernão sejaprivate(ou torná-lo acessível via reflexão no Kotlin comisAccessible = true/ validar visibilidade). - Garantir que o tipo do campo anotado seja compatível com a interface
WebDriver. Caso contrário, lançamos umaIllegalStateException. - Obter o campo anotado da classe de teste para injeção.
- Criar um novo objeto
WebDriverutilizando reflexão (getDeclaredConstructor().newInstance()) a partir da classe configurada em@SeleniumTest. - Garantir que o objeto
WebDriverseja criado e injetado no campo anotado da instância de teste antes que os métodos anotados com@BeforeEachsejam executados. - Garantir que o navegador seja fechado (
browser.quit()) depois que os métodos anotados com@AfterEachterminarem de executar.
Vamos analisar o código completo que implementa todos esses 11 passos em Kotlin:
|
|
Alternativa Moderna com Playwright: Se você estiver utilizando o Microsoft Playwright em vez do Selenium WebDriver na sua suíte E2E, a lógica da extensão acima permanece exatamente a mesma: basta injetar um
BrowserContexte umaPagerecém-criados embeforeEach()(a partir de uma instância compartilhada deBrowser) e fechar oBrowserContextemafterEach(). Criar umBrowserContextisolado por teste no Playwright leva apenas alguns milissegundos e oferece o mesmo isolamento completo de cookies elocalStoragede abrir e fechar um navegador inteiro!
3. Escrevendo Nossa Classe de Teste End-to-End
Agora podemos configurar nossas classes de teste End-to-End seguindo estes passos:
- Anotar a classe de teste com
@SeleniumTest. Como o valor padrão do atributodriveréChromeDriver::class, não precisamos configurar o tipo do driver manualmente se quisermos usar o Chrome. - Anotar a classe de teste com
@Category(EndToEndTest::class)(no JUnit 4) ou@Tag("endToEndTest")(no JUnit 6) quando estivermos filtrando por tags, ou simplesmente colocá-la no source setsrc/endToEndTest/kotlindo Gradle JVM Test Suite. - Adicionar um campo
WebDriverna classe de teste e anotá-lo com@SeleniumWebDriver.
O código-fonte da nossa classe de teste End-to-End fica assim:
|
|
Resumo
Nesta aula, aprendemos que:
- Se não quisermos gerenciar o ciclo de vida dos objetos
WebDriver(ouPagedo Playwright) manualmente em cada classe de teste, precisamos implementar uma extensão do JUnit que crie e feche automaticamente o navegador para cada método de teste. - Nossa extensão garante que a instância do navegador já esteja disponível dentro dos métodos anotados com
@BeforeEache só seja encerrada após a conclusão dos métodos anotados com@AfterEach. - Na próxima aula, veremos como configurar testes End-to-End para uma aplicação Spring Boot, aproveitando o
@SpringBootTeste umTestExecutionListenercustomizado para iniciar a própria aplicação embarcada antes dos testes.