Testes E2E para SPAs JavaScript: Diálogos Modais e Confirmações
Depois de concluirmos esta aula, nós:
- Entenderemos como selecionar a condição de espera correta (wait condition) quando estamos escrevendo testes End-to-End para funcionalidades que exibem diálogos modais (como modais de confirmação de exclusão).
- Saberemos como criar um Page Object dedicado para representar o diálogo modal (
DeleteTaskConfirmationDialog) e integrá-lo à classeTaskActions.
1. Escrevendo Testes End-to-End para Funcionalidades que Utilizam Diálogos Modais
A página de visualização de tarefa (view task page) contém um link que exclui uma tarefa existente do banco de dados. A parte relevante do template HTML que renderiza a página de visualização da tarefa na nossa SPA é a seguinte:
|
|
Na versão tradicional (Server-Side Rendered) da aplicação, clicar em #delete-task-link disparava imediatamente a exclusão. Já na nossa aplicação JavaScript (SPA), quando um usuário clica no link de exclusão (#delete-task-link), a aplicação abre um diálogo modal de confirmação (#delete-task-dialog) que possui dois botões:
- O botão Cancelar fecha o diálogo modal de confirmação sem excluir a tarefa.
- O botão Excluir (
#delete-task-button) fecha o diálogo modal de confirmação, envia a requisiçãoDELETE /api/task/{id}para o backend e redireciona o usuário para a página de listagem de tarefas.
O template HTML que contém a marcação do diálogo modal é o seguinte:
|
|
Antes de escrevermos testes End-to-End para a funcionalidade de exclusão de tarefa com modal, precisamos selecionar a condição de espera que deve ser verdadeira após clicarmos no link que abre o modal. Quando escrevemos testes End-to-End para funcionalidades que exibem diálogos modais, escolhemos a condição de espera seguindo estas duas regras simples:
- Se quisermos escrever asserções para o texto/conteúdo do diálogo modal, devemos aguardar até que o diálogo modal esteja visível (
waitUntilElementIsVisible). - Se quisermos clicar nos botões do diálogo modal, devemos aguardar até que esses botões estejam clicáveis (
waitUntilElementIsClickable) — o que é especialmente importante porque frameworks de UI (como Bootstrap, Material UI ou Radix) costumam aplicar animações de fade-in ao abrir um modal!
Vamos ver agora como modelar esse diálogo modal nos nossos Page Objects.
2. Criando um Novo Page Object para o Diálogo Modal (DeleteTaskConfirmationDialog)
Como vimos anteriormente, a classe TaskActions é o Page Object que permite aos nossos testes clicar nos links de excluir e atualizar a tarefa. No entanto, não devemos colocar o código que manipula o diálogo modal de confirmação diretamente dentro da classe TaskActions, pois isso daria responsabilidades demais a uma única classe.
Em vez disso, aplicando a 1ª Boa Prática dos Page Objects, criaremos um novo Page Object chamado DeleteTaskConfirmationDialog que representa exclusivamente o diálogo de confirmação! Podemos criá-lo seguindo estes quatro passos:
Passo 1: Criar a Classe DeleteTaskConfirmationDialog e seus Campos
Primeiro, criamos a classe DeleteTaskConfirmationDialog com duas propriedades privadas:
browser: Contém a referência para o objetoWebDriverutilizado.taskActions: Contém a referência para oWebElementque representa o container#task-action-links(onde está o link#delete-task-link).
Passo 2: Implementar o Construtor
Segundo, implementamos o construtor que recebe e armazena browser e taskActions:
|
|
Passo 3: Implementar o Método open()
Terceiro, adicionamos o método open() à classe DeleteTaskConfirmationDialog e o implementamos em três etapas:
- Localizar e clicar no link
#delete-task-linkencontrado dentro detaskActions. - Aguardar até que o nosso teste possa clicar no botão de confirmação
#delete-task-buttondentro do modal (SeleniumWait.waitUntilElementIsClickable(browser, By.id("delete-task-button"))). - Retornar a referência para este próprio objeto
DeleteTaskConfirmationDialog(this).
|
|
Passo 4: Implementar o Método deleteTask()
Quarto, adicionamos o método deleteTask() à classe DeleteTaskConfirmationDialog e o implementamos em duas etapas:
- Localizar e clicar no botão de confirmação
#delete-task-buttondentro do modal. - Aguardar até que a página de listagem de tarefas (
TaskListPage) esteja aberta (targetPage.waitUntilPageIsOpen()) e retornar o novo objetoTaskListPage.
Depois de implementarmos esse método, o código completo da classe DeleteTaskConfirmationDialog fica assim:
|
|
Agora que criamos o Page Object que representa o diálogo de confirmação, nosso último passo é fazer com que a classe TaskActions o utilize!
3. Modificando a Classe TaskActions
Podemos atualizar o método deleteTask() da classe TaskActions em apenas duas linhas:
- Instanciar
DeleteTaskConfirmationDialog(browser, rootElement)e abrir o modal de confirmação chamando.open(). - Confirmar a exclusão da tarefa no banco de dados chamando
dialog.deleteTask()e retornar aTaskListPageresultante!
Depois de fazermos essa alteração, o código-fonte completo da classe TaskActions fica assim:
|
|
Novamente, repare que não precisamos fazer nenhuma alteração nas nossas classes de teste End-to-End que testam a exclusão de tarefas! Como a assinatura pública de TaskActions.deleteTask(): TaskListPage permaneceu exatamente a mesma, os testes continuam limpos e desacoplados do fato de a exclusão agora passar por um diálogo modal de confirmação.
Resumo
Esta aula nos ensinou três lições essenciais:
- Devemos colocar o código que manipula um diálogo modal em um novo Page Object dedicado (
DeleteTaskConfirmationDialog), evitando sobrecarregar os Page Objects existentes com responsabilidades demais. - Se quisermos escrever asserções para o conteúdo textual de um diálogo modal, devemos aguardar até que o diálogo esteja visível (
waitUntilElementIsVisible). - Se quisermos clicar nos botões de ação de um diálogo modal, devemos aguardar até que esses botões estejam clicáveis (
waitUntilElementIsClickable). - No próximo e último tópico do Capítulo 02, mergulharemos nas Aulas Bônus: simulação de APIs REST externas com WireMock (incluindo respostas JSON, falhas e timeouts) e recursos avançados do JUnit 6 (Jupiter) (
@Nested,@ParameterizedTest, extensões customizadas e integração com Spring)!