Melhore seus testes e2e com Playwright usando o modelo Page Object

Melhore seus testes e2e com Playwright usando o modelo Page Object
RODRIGO SILVA HARDER
RODRIGO SILVA HARDER

Compartilhe

Suponha que você recebeu a tarefa de testar as interações da pessoa usuária com uma página de login.

Para isso, você decidiu aplicar testes de ponta a ponta (E2E) usando o Playwright e propôs dois cenários de teste: um para garantir que ao preencher corretamente os campos de nome e senha, a página seja redirecionada para uma tela de boas-vindas; e outro que verifica se ao fazer login com as credenciais erradas apareça uma mensagem de erro.

Veja abaixo:

import { test, expect } from '@playwright/test';

test('Deve redirecionar para a página de boas-vindas quando fizer login com credenciais corretas', async ({ page }) => {

  //Acessa a página de login preenche os campos corretamente e clica no botão
  await page.goto('http://localhost:4200/login');
  await page.fill('input[name="username"]', 'alura');
  await page.fill('input[name="password"]', 'alura123');
  await page.click('button[type="submit"]');

  //Redireciona para a página principal
  await expect(page).toHaveURL('http://localhost:4200/principal');

  //Verifica se na página principal tem a frase "Boas vindas!"
  await expect(page.locator('h2')).toHaveText('Boas vindas!');
});

test('Deve exibir um alerta quando fizer login com credenciais incorretas', async ({ page }) => {

  //Acessa a página de login, preenche os campos com a senha errada e clica no botão
  await page.goto('http://localhost:4200/login');
  await page.fill('input[name="username"]', 'alura');
  await page.fill('input[name="password"]', 'senhaErrada');
  await page.click('button[type="submit"]');

  //Exibe uma caixa de diálogo com uma mensagem de erro
  page.on('dialog', async dialog => {
    expect(dialog.message()).toBe('Login ou senha incorretos!');
    await dialog.dismiss();
  });
});

O resultado dos testes seria bem-sucedido, como mostro na imagem abaixo.

Imagem. Relatório com todos os testes bem-sucedidos realizados com playwright em três motores de navegação diferentes para verificar uma página de login

Porém, no código, agrupamos todas as ações e asserções dentro do código de cada teste.

Em projetos menores, pode ser uma boa ideia, mas em projetos maiores e até mesmo no exemplo acima, pode causar problemas de repetição de código, dificuldade na manutenção dos testes e erros de execução em testes, causados por mudanças na estrutura da página.

Para lidar com esses problemas e manter a qualidade dos testes, podemos adotar o modelo de Page Object.

Com essa abordagem, podemos reescrever o código de forma organizada, intuitiva e reutilizável, facilitando a legibilidade e manutenção dos testes.

Então, é exatamente isso que vamos abordar neste artigo: explorar no universo dos testes o modelo de Page Object e descobrir:

  • O que é o modelo de Page Object (PO);
  • Qual é a estrutura de um projeto que utiliza PO;
  • As diferenças entre um código escrito com e sem a técnica de Page Object;
  • Por que usar o Page Object ao realizar testes E2E.

O que é Page Object Model?

O Page Object é um modelo utilizado em testes E2E para escrever testes mais organizados e reutilizáveis.

Basicamente, o que essa abordagem faz é transformar cada página da aplicação em um objeto, representado por uma classe, que vai guardar todos os elementos e ações que podem ser realizadas.

Voltemos ao exemplo dos testes na página de login. A princípio as interações com os elementos da página aconteciam dentro de cada teste.

Ou seja, tudo era feito dentro dos testes: acessar a página, identificar e preencher os campos de formulário, encontrar e clicar no botão, identificar a URL da página após o redirecionamento e localizar textos na tela ou mesmo exibir mensagens de erro.

Imagine que toda vez que você precisasse ir ao supermercado, você tivesse que decidir na hora cada item que precisa comprar e procurar em cada corredor… Você conseguiria fazer a compra, mas provavelmente para lembrar de tudo você precisaria passar no mesmo corredor mais de uma vez.

Agora se você levasse uma lista de compras pronta, onde cada item já está catalogado junto da seção onde ele pode ser encontrado, você levaria menos tempo pensando em tudo e conseguiria fazer a compra de forma mais organizada.

É dessa forma que o Page Object ajuda as pessoas desenvolvedoras: ele já tem ali todos os elementos e as interações com a página, você só precisa usá-las nos seus testes, da mesmo forma que no mercado, você apenas precisa pegar os itens que já estão na lista de compras.

Com o Page Object, ao invés de escrever essas interações diretamente nos testes, criamos uma classe que vai representar a página de login, e transformamos essas ações em métodos, por exemplo, método visitar que acessa a página de login.

Com isso, removemos a responsabilidade de interação dos testes e apenas invocamos os métodos da página, deixando o código mais limpo e legível.

Para fazer essa separação entre as interações da página e os testes, podemos criar dentro da pasta de testes, uma pasta chamada "page-objects", que vai conter os arquivos com as classes para cada página. A estrutura ficaria assim:

|_testes
    |_page-objects
        |_login.ts
    |_login.spec.ts
Banner promocional da Alura, com um design futurista em tons de azul, apresentando o texto

Reescrevendo testes com Page Object

Agora vamos refatorar o código dos dois testes para a página de login utilizando Page Object.

No arquivo "login.ts", precisamos definir uma classe chamada PaginaLogin que representa a página de login:

export default class PaginaLogin {
//Adicionar o código do construtor e métodos da classe
}

Agora precisamos criar variáveis que vão armazenar um localizador para cada elemento da página. Neste caso, um para o campo de nome, um para a senha e outro para o botão.

private readonly inputUsername: Locator;
private readonly inputPassword: Locator;
private readonly botaoSubmit: Locator;

Em seguida, definimos um construtor que recebe o parâmetro page do tipo Page para representar uma página do navegador.

E dentro desse construtor localizamos os elementos por um seletor e inicializamos as instâncias que serão manipuladas durante os testes.

constructor(private page: Page) {
   this.inputUsername = page.locator('input[name="username"]');
   this.inputPassword = page.locator('input[name="password"]');
   this.botaoSubmit = page.locator('button[type="submit"]');
 }

Agora é o momento de criar os métodos responsáveis pelas interações com a página.

async visitar() {
  await this.page.goto('http://localhost:4200/login');
}

async fazerLogin(username: string, password: string) {
  await this.inputUsername.fill(username);
  await this.inputPassword.fill(password);
}

async clicarNoBotaoSubmit() {
  await this.botaoSubmit.click();
}

async capturarMensagemDeErro(): Promise<string> {
  const dialog = await this.page.waitForEvent('dialog');
  const message = dialog.message();
  await dialog.dismiss();
  return message;
 }

async obterURLAtual() {
  return this.page.url();
}

async obterTextoCabecalho() {
  return this.page.locator('h2').innerText();
}

Lembre-se de realizar as importações necessárias do Playwright na primeira linha do arquivo:

import { Locator, Page } from "@playwright/test";

Até aqui não realizamos nenhum teste, apenas identificamos elementos e criamos ações que podem ser feitas na página, pois o objetivo aqui é apenas criar essa estrutura dos elementos e interações para depois facilitar a construção dos testes, sem pensar em lógicas mais complexas.

Agora iremos para o arquivo de teste, o "login.spec.ts".

Antes de usar qualquer método que criamos, precisamos criar uma variável global para armazenar uma instância da PaginaLogin e conseguir utilizar os métodos em diferentes partes do código.

let paginaLogin: PaginaLogin;

Lá no começo do artigo, planejamos dois testes, um para verificar se após fazer o login com as credenciais corretas a página era redirecionada, e outro para verificar se ao fazer login com as credenciais incorretas apareceria uma mensagem de erro.

Dentro de cada um desses testes, poderíamos usar o método visitar que criamos para acessar a página de login, mas no lugar de repetir a mesma linha de código nesses dois cenários, configuramos um terceiro teste que cria uma instância da página de login e navega até ela. Este teste é executado antes de cada um dos testes através do beforeEach.

   test.beforeEach(async ({ page }) => {
    paginaLogin = new PaginaLogin(page);
    await paginaLogin.visitar();
  });

Agora, podemos reescrever os testes seguindo a seguinte lógica para cada um.

Teste que verifica o comportamento da página de login com as credenciais corretas:

  • Preencher os campos de nome e senha;
  • Clicar no botão de login;
  • Obter a URL da página atual após o redirecionamento da página;
  • Obter o texto no cabeçalho da página.
test("Deve redirecionar para a página de boas-vindas quando fizer login com credenciais corretas", async () => {
    await paginaLogin.fazerLogin('alura', 'alura123');
    await paginaLogin.clicarNoBotaoSubmit();)
    expect(await paginaLogin.obterURLAtual()).toBe('http://localhost:4200/principal');
    expect(await paginaLogin.obterTextoCabecalho()).toBe('Boas vindas!');
});

Teste que verifica o comportamento da página de login com as credenciais corretas:

  • Preencher os campos de nome e senha;
  • Obter a mensagem de erro e clicar no botão simultaneamente;
  • Verificar a presença da mensagem de erro.
test("Deve exibir um alerta quando fizer login com credenciais incorretas", async () => {
    await paginaLogin.fazerLogin('alura', 'senhaErrada');
    const [mensagemErro] = await Promise.all([
      paginaLogin.clicarNoBotaoSubmit(), 
      paginaLogin.capturarMensagemDeErro()
    ]);
    expect(mensagemErro).toBe('Login ou senha incorretos!');
});

Em cada uma das partes do teste conseguimos substituir as interações diretas com a página pelos métodos que criamos na classe PaginaLogin.

É importante lembrar que no início do arquivo é importante que sejam feitas as importações necessárias, tanto do arquivo que contém a classe, quanto das funções do Playwright.

import { test, expect } from '@playwright/test'; 
import PaginaLogin from './page-object/login'; 

Além disso, é possível agrupar todos os testes relacionados à página de login para melhorar a organização e clareza, tal como facilitar a manutenção. A estrutura ficaria assim:

test.describe("Página de Login", () => {
//Adicionar os testes dentro dessa função
})

Mas fica a pergunta: será que essas alterações vão afetar o resultado do teste? E a resposta é, não.

Os testes funcionam da mesma forma como podemos ver na imagem abaixo, todos foram bem-sucedidos.

Imagem. Relatório com todos os testes bem-sucedidos realizados com playwright em três motores de navegação diferentes para verificar uma página de login

Por que usar o Page Object Model?

Após finalizar as alterações e modificar os cenários de teste com Page Object, você pode ter se perguntado:

Para realizar os testes lá no começo do artigo, o código era bem menor do que ele ficou agora, então por que usar essa abordagem? Realmente é uma boa pergunta e é exatamente isso que vou te responder agora.

Manter os testes E2E em aplicações maiores é algo desafiador, principalmente quando a estrutura da página muda.

No modelo convencional, seria necessário atualizar todos os testes manualmente para alterar os elementos que mudam na página, o que poderia causar erros, além de ser bem demorado quando temos muitos testes.

Com o Page Object separamos o código que faz a busca e interação com os elementos da página do código que realiza os testes, dessa forma, as alterações ficam concentradas em uma única classe que representa a página.

Ou seja, ao alterar um seletor ou um comportamento, basta alterar um único lugar e não múltiplos testes, o que facilita a manutenção e redução de erros.

Você se lembra que no exemplo lá no começo do artigo, foi preciso repetir o código que acessa a página de login, nos dois testes.

Além da dificuldade na manutenção, não conseguimos aproveitar essa interação com a página de login para outras situações, precisamos toda vez repetir a mesma linha de código.

O Page Object ajuda a não duplicar o código. Como criamos métodos para representar as interações com a página, podemos utilizar o mesmo método em diversos testes.

Assim, garantimos um código mais limpo e reutilizável e nos concentramos no que realmente importa que é o teste em si.

Esse comportamento de permitir a reutilização do código também ajuda a expansão de novos comportamentos ou cenários de testes à medida que a aplicação muda ou se torna maior, facilitando a escalabilidade.

Além disso, os nomes dos métodos criados com o Page Object tornam o código mais descritivo e legível.

Por exemplo, o método fazerLogin agrupa as interações necessárias para o login. Isso facilita a compreensão da funcionalidade e melhora a clareza para todas as outras pessoas desenvolvedoras.

Bacana, né? Após todas essas vantagens entendemos por que o modelo Page Object é bastante utilizado pelas pessoas desenvolvedores ao realizar testes E2E.

Gif. Homem com uma expressão de confiança, apontando para sua cabeça com um sorriso astuto, como se estivesse sugerindo uma ideia ou estratégia inteligente.

Conclusão

Exploramos a criação de testes para uma página de login e aplicamos o modelo de Page Object. Aprendemos o que é essa abordagem e como aplicá-la em testes E2E feitos com Playwright.

Com isso, percebemos algumas diferenças e vantagens em usar o Page Objects no desenvolvimento.

Agora, fica o meu convite para que você aprofunde seus conhecimentos em testes e pratique cenários ainda mais incríveis usando o Page Object.

Caso queira mais conteúdos sobre testes, acesse os links:

Até a próxima!

RODRIGO SILVA HARDER
RODRIGO SILVA HARDER

Graduado em Design Gráfico, Técnico em Química e Licenciatura em química, Rodrigo é apaixonado por ensinar e aprender e busca se aventurar em diferentes linguagens de programação. Faço parte da escola semente e atuo como monitor no time de Fórum Ops.

Veja outros artigos sobre Front-end