Testes E2E para Aplicações Web Spring: Submetendo um Formulário
Depois de concluirmos esta aula, nós:
- Saberemos como criar Page Objects para uma página que exibe um formulário (
CreateTaskPageeTaskForm). - Entenderemos os desafios que enfrentamos ao verificar a URL de redirecionamento quando o ID da entidade recém-criada é gerado dinamicamente pelo banco de dados.
- Saberemos escrever testes End-to-End completos para funcionalidades que submetem um formulário HTML.
1. Introdução ao Sistema Sob Teste (SUT): A Página de Criação de Tarefa
Durante esta aula, escreveremos testes End-to-End para a funcionalidade que permite aos usuários criar novas tarefas. A página de criação de tarefa (create task page) é renderizada quando um usuário autenticado envia uma requisição GET para o caminho /task/create, e contém um formulário simples (#create-task-form) que permite informar o título (#task-title) e a descrição (#task-description) da nova tarefa.
O código-fonte do nosso template HTML/JSP (create.jsp) possui a seguinte estrutura:
|
|
Vamos avançar e criar os Page Objects necessários para interagir com essa página e seu formulário.
2. Criando os Page Objects Necessários (CreateTaskPage e TaskForm)
Antes de escrevermos nossos testes End-to-End para a funcionalidade de criação de tarefas, precisamos criar dois Page Objects e fazer uma pequena adaptação na classe TaskPage:
1. O Page Object CreateTaskPage
A classe CreateTaskPage representa a página de criação de tarefa (/task/create). Ela possui três métodos:
getForm(): Retorna um objetoTaskForm, que é o Page Object que representa o formulário de criação ou atualização de tarefa (#create-task-form).isOpen(): Retornatruese a página de criação de tarefa estiver aberta efalsecaso contrário.open(): Abre a página de criação de tarefa e retorna uma nova instância deCreateTaskPage.
|
|
2. O Page Object de Formulário TaskForm
A classe TaskForm é um Page Object que representa tanto o formulário de criação quanto o formulário de edição de tarefa (bastando passar o formId correspondente no construtor!). Criar um Page Object separado para um formulário é uma excelente prática porque separa as responsabilidades da página das responsabilidades de manipulação de inputs.
A API da classe TaskForm possui cinco métodos:
typeDescription(description: String): Limpa (clear()) qualquer descrição existente na<textarea id="task-description">e digita o novo valor.typeTitle(title: String): Limpa (clear()) qualquer título existente no<input id="task-title">e digita o novo valor.submitTaskForm(): Submete o formulário (form.submit()) e retorna um novo objetoTaskPage. Se o formulário em questão for o formulário de criação de nova tarefa (que ainda não possui um input oculto#task-id), o método auxiliargetTaskId()retornaránulle passarátaskId = nullpara o construtor deTaskPage.getTaskDescription(): Retorna o valor atual preenchido no campo de descrição.getTaskTitle(): Retorna o valor atual preenchido no campo de título.
Por que chamamos
clear()antes desendKeys()emtypeTitle()etypeDescription()? Porque o métodoWebElement.sendKeys()apenas concatena os caracteres ao final do texto que já estiver dentro do input! ComoTaskFormtambém é reutilizado no formulário de atualização de tarefas (onde os inputs já vêm preenchidos com o título e a descrição antigos), chamarclear()antes desendKeys()garante que o valor antigo seja substituído pelo novo!
O código-fonte da classe TaskForm fica assim:
|
|
3. Adaptando TaskPage para Lidar com taskId Desconhecido (isOpenWithUnknownTaskId)
Quando submetemos o formulário de criação de tarefa com dados válidos, nossa aplicação web redireciona o navegador para a página de visualização da tarefa recém-criada (/task/{id}). É por isso que o método submitTaskForm() de TaskForm retorna um novo objeto TaskPage.
Entretanto, se estivermos inicializando nosso banco de dados apenas uma vez no startup da aplicação (e vários testes criarem tarefas), não podemos saber de antemão qual será o id exato gerado pela sequence do banco para a tarefa criada. Isso significa que precisamos passar taskId = null para o construtor de TaskPage quando a tarefa acaba de ser criada!
Para suportar esse cenário com segurança, fazemos duas melhorias na classe TaskPage:
- Garantimos que o método
open()lance uma exceção (RuntimeException) caso o valor do campotaskIdsejanull(já que não é possível abrir/task/nulldiretamente). - Adicionamos o método
isOpenWithUnknownTaskId()à classeTaskPage. Esse método remove o prefixo basehttp://localhost:8080/task/da URL atual do navegador e verifica se o sufixo restante da URL é um número inteiro válido (o ID gerado pelo banco)!
Veja como fica a classe TaskPage atualizada:
|
|
Nota sobre Spring Boot +
@Sql: Se estivermos escrevendo testes End-to-End para uma aplicação Spring Boot usando@Sqlantes de cada método de teste (ondetasks.sqlexecutaALTER SEQUENCE tasks_id_seq RESTART WITH 4;), sabemos com certeza que o ID da tarefa criada será4L. Mesmo assim, o métodoisOpenWithUnknownTaskId()continua sendo extremamente útil porque desacopla o teste E2E do valor interno da sequence do banco!
Vamos ver agora como escrever os testes End-to-End para a funcionalidade de criação de tarefa utilizando esses Page Objects.
3. Escrevendo Testes End-to-End para a Criação de Tarefa
Podemos construir a classe de teste SubmitCreateTaskFormWithValidInformationTest seguindo estes 7 passos:
Passo 1: Criar a Classe de Teste e Configurar o WebDriver
Primeiro, criamos a classe de teste SubmitCreateTaskFormWithValidInformationTest e configuramos a injeção do navegador:
|
|
Passo 2: Declarar as Constantes DESCRIPTION e TITLE
Segundo, adicionamos duas constantes à nossa classe de teste (DESCRIPTION e TITLE) contendo o título e a descrição da nova tarefa que será criada pelo formulário. Declaramos essas constantes diretamente dentro da classe de teste porque elas são exclusivas deste cenário e não são compartilhadas com outras classes:
|
|
Passo 3: Adicionar o Campo createTaskPage
Terceiro, adicionamos o campo private lateinit var createTaskPage: CreateTaskPage à nossa classe de teste. Nossos métodos de teste utilizarão esse campo quando submeterem o formulário de criação de tarefa.
Passo 4: Implementar o Método @BeforeEach (Login e Abertura da Página de Criação)
Quarto, implementamos o método @BeforeEach seguindo duas etapas:
- Autenticar o usuário
JohnDoeantes da execução do método de teste (logUserIn()), pois nossa aplicação web não permite que usuários anônimos insiram novos registros no banco de dados. - Instanciar
CreateTaskPage(browser), abrir a página de criação invocando.open()e armazenar o objeto retornado no campocreateTaskPage.
Passo 5: Implementar o Método @AfterEach (Logout)
Quinto, garantimos que o usuário logado seja desconectado ao final de cada teste invocando NavigationBar(browser).logUserOut() no método @AfterEach.
Passo 6: Escrever o Método Auxiliar createTask()
Sexto, escrevemos um método privado createTask(): TaskPage que obtém o TaskForm a partir de createTaskPage, digita o TITLE e a DESCRIPTION e submete o formulário. Extrair esse método auxiliar evita repetir o preenchimento do formulário em cada um dos quatro métodos @Test:
|
|
Passo 7: Escrever os Quatro Testes End-to-End
Sétimo, escrevemos os quatro testes End-to-End para validar todos os efeitos visíveis da criação da tarefa:
- Verificar se a submissão do formulário redireciona o usuário para a página de visualização da tarefa criada (
viewTaskPage.isOpenWithUnknownTaskId()). - Garantir que a página de visualização exibe a descrição da tarefa recém-criada (
DESCRIPTION). - Verificar se a página de visualização exibe o título da tarefa recém-criada (
TITLE). - Garantir que a seção de ciclo de vida exibe o nome do usuário logado (
JohnDoe.NAME) como criador da tarefa!
Veja o código completo da nossa classe SubmitCreateTaskFormWithValidInformationTest:
|
|
Resumo
Esta aula nos ensinou duas lições fundamentais:
- É uma excelente prática criar um Page Object dedicado para representar um formulário (
TaskForm), permitindo reutilizá-lo entre as páginas de criação (CreateTaskPage) e de atualização (UpdateTaskPage) e mantendo as responsabilidades bem separadas. - Quando o ID da nova entidade é gerado dinamicamente pelo banco de dados durante o teste End-to-End, podemos validar o redirecionamento para
/task/{id}com o métodoisOpenWithUnknownTaskId()e verificar os dados renderizados na página de destino (getTaskTitle(),getTaskDescription(),getCreatorName()). - No próximo tópico, avançaremos para um dos cenários mais desafiadores do mundo real: como escrever testes End-to-End para Single Page Applications (SPAs) em JavaScript, lidando com requisições AJAX/Fetch assíncronas, renderização dinâmica e diálogos modais!