Introdução ao Padrão Page Object em Kotlin
Depois de concluirmos esta aula, nós:
- Saberemos o que é um Page Object.
- Entenderemos por que devemos utilizar Page Objects em nossos testes End-to-End.
- Conheceremos as três boas práticas fundamentais para projetar Page Objects limpos e fáceis de manter.
1. O Que é um Page Object?
Martin Fowler define o termo Page Object da seguinte maneira:
“Um Page Object encapsula uma página HTML, ou fragmento de página, com uma API específica da aplicação, permitindo que você manipule elementos da página sem precisar vasculhar o HTML.”
Em outras palavras, um Page Object esconde a estrutura interna da página HTML (ou de um fragmento dela) dos nossos testes End-to-End e fornece métodos de alto nível que permitem aos nossos testes interagir com a página ou consultar informações exibidas nela.
Os Page Objects nos ajudam a escrever testes End-to-End fáceis de ler, escrever e manter por dois motivos principais:
- Eliminação de código duplicado: Nossa suíte de testes passa a ter um único lugar que conhece a estrutura HTML, os IDs e os seletores CSS de uma determinada página ou componente visual. Se o HTML mudar, atualizamos apenas o Page Object correspondente, e não dezenas de testes espalhados!
- Criação de uma DSL (Linguagem Específica de Domínio): Se dermos nomes orientados ao domínio da aplicação para os métodos dos nossos Page Objects (como
loginPage.login(email, password)outaskPage.deleteTask()), criamos uma linguagem fluente e expressiva para os nossos testes End-to-End.
A seguir, vamos analisar as três boas práticas que nos ajudam a escrever bons Page Objects.
2. As Boas Práticas dos Page Objects
Existem três boas práticas essenciais que nos ajudam a projetar Page Objects limpos e coesos:
1ª Boa Prática: Divida a página HTML em seções significativas (não crie apenas um Page Object gigante por página)
Embora esses objetos sejam chamados de Page Objects (Objetos de Página), não devemos criar apenas um único Page Object monolítico para cada página HTML inteira. Em vez disso, devemos dividir a página HTML em seções significativas e criar um Page Object para cada seção ou componente!
Por exemplo:
- Uma página que exibe uma lista de tarefas (
TaskListPage) pode conter uma lista de objetos que representam cada item individual da lista (TaskListItem). - Uma página complexa pode expor um Page Object separado para a barra de navegação (
NavigationBar), outro para um formulário (TaskForm) e outro para um modal de confirmação.
Além disso, quando dividimos uma página HTML em Page Objects, devemos ignorar as partes do HTML que servem apenas para estruturar o layout visual (como <div>s de grid do Bootstrap/Tailwind) e modelar a estrutura da página de uma forma que faça sentido para os usuários da nossa aplicação.
Ao seguirmos essa técnica, criamos uma verdadeira linguagem específica de domínio (DSL) que destaca a essência dos nossos casos de teste.
2ª Boa Prática: Os métodos de um Page Object devem retornar apenas tipos básicos ou outros Page Objects
Tipicamente, um Page Object possui três categorias de métodos:
- Métodos que buscam informações na página atual: Devem retornar tipos de dados primitivos ou objetos básicos do domínio/JDK, como
String,Int,Long,BooleanouLocalDate(e nuncaWebElementouLocator!). - Métodos que permitem acessar outras seções da página atual: Devem retornar outros Page Objects que representam aquelas subseções (por exemplo,
taskForm()retornando umTaskForm). - Métodos que navegam para outra página ou recarregam a página atual: Devem retornar um novo Page Object que representa a página de destino carregada após a ação!
Se seguirmos essa regra à risca, colhemos dois grandes benefícios:
- Na grande maioria das vezes, não precisamos expor a instância subjacente do
WebDriver(ouPage) para as nossas classes de teste End-to-End. Isso significa que nossas classes de teste podem ignorar completamente a API técnica do Selenium/Playwright e interagir exclusivamente por meio da DSL definida pelos nossos Page Objects. - Segurança em tempo de compilação: Se alterarmos o fluxo de navegação de uma funcionalidade na aplicação (por exemplo, após criar uma tarefa o usuário passa a ser redirecionado para a lista de tarefas em vez da página de detalhes), basta alterarmos o tipo de retorno do método correspondente no Page Object e o compilador do Kotlin nos mostrará imediatamente quais classes de teste foram afetadas pela mudança!
Consequência Importante desta Regra: Se uma mesma ação na UI puder produzir resultados de navegação diferentes dependendo dos dados de entrada, devemos modelar esses resultados através de métodos diferentes no Page Object! Por exemplo, se uma página diferente é renderizada após um login bem-sucedido (
TaskListPage) em comparação a uma tentativa de login com senha inválida (LoginPagecom alerta de erro), devemos adicionar dois métodos de login aoLoginPage:
login(email, password): TaskListPageloginWithInvalidCredentials(email, password): LoginPage
3ª Boa Prática: Page Objects NÃO devem fazer asserções!
Como vimos, a responsabilidade dos Page Objects é fornecer uma linguagem específica de domínio (DSL) que nos permita interagir com a página HTML subjacente e extrair informações dela.
Por outro lado, as asserções servem para especificar as condições de negócio que devem ser verdadeiras após a execução do nosso cenário de teste. Em outras palavras, as asserções especificam as regras de negócio verificadas pelo teste e, por isso, devem ficar dentro dos métodos de teste (@Test), e não escondidas dentro dos Page Objects!
Se adicionarmos asserções dentro dos nossos Page Objects:
- Misturamos a lógica de apresentação/extração do DOM com a lógica de verificação de regras de negócio.
- Criamos Page Objects “inchados” (bloated) que acumulam responsabilidades demais e violam o Princípio da Responsabilidade Única (SRP).
Algumas pessoas argumentam que colocar asserções dentro dos Page Objects ajuda a eliminar lógica de asserção duplicada e fornecer mensagens de erro melhores. Embora esse objetivo seja válido, podemos alcançar exatamente o mesmo benefício sem misturar responsabilidades: basta utilizarmos uma biblioteca de asserções fluente como o AssertJ e criarmos asserções customizadas (AbstractAssert) para os nossos Page Objects caso necessário!
Resumo
Esta aula nos ensinou cinco pontos essenciais:
- Um Page Object esconde a estrutura interna de uma página HTML (ou fragmento de página) dos nossos testes End-to-End.
- Page Objects nos permitem eliminar código de seletores duplicado e criar uma linguagem específica de domínio (DSL) legível para os nossos testes.
- Não devemos criar apenas um único Page Object por página HTML; devemos dividir a página em seções lógicas significativas e criar um Page Object para cada seção.
- Os métodos de um Page Object devem retornar apenas tipos básicos (
String,Boolean,Int, etc.) ou outros Page Objects — nunca objetosWebElementcrus. - Não devemos adicionar asserções dentro dos nossos Page Objects para não misturar lógica de interação com a UI e regras de negócio do teste.
- Na próxima aula, colocaremos todos esses princípios em prática criando o nosso primeiro Page Object completo: a
LoginPage!