QA Learning / Playwright

Módulo 2: Page Object Model, formulário e validação

Agrupar testes com test.describe, organizar o projeto com Page Object Model, e conferir mensagem de validação de campo.

🎥 Prefere ver antes de ler?

Esse vídeo (2min52) explica por que organizar com Page Object Model, mostra a classe CadastroPage por dentro, o que é um constructor, como module.exports/require conectam os arquivos, e termina com uma validação de senha curta acontecendo de verdade no site de treino. Não substitui o texto abaixo, mas ajuda a visualizar antes de praticar.

1. Agrupar testes com describe e beforeEach

Quando vários testes compartilham a mesma página ou o mesmo preparo, agrupe com test.describe e repita a parte comum num test.beforeEach, que roda antes de cada teste do grupo, com uma página nova e limpa:

test.describe("cadastro", () => {
  test("campos obrigatorios mostram erro ao submeter em branco", async ({ page }) => {
    await page.goto("/cadastro.html");
    // ...
  });

  test("cadastro valido mostra mensagem de sucesso", async ({ page }) => {
    await page.goto("/cadastro.html");
    // ...
  });
});

Repare: cada teste recebe um page novo, porque o Playwright isola testes entre si (contexto de navegador zerado). Por isso um teste nunca vê o estado que o anterior deixou. É o que torna a ordem de execução irrelevante.

2. Por que organizar com Page Object Model

O Módulo 1 escreveu page.getByTestId("login-email-input") direto no teste. Funciona bem pra um arquivo com dois testes, mas pensa em escala: a trilha inteira (e um projeto de automação de verdade) acaba com dezenas de testes tocando as mesmas páginas. Se o data-testid de um campo mudar, você teria que caçar e trocar em todo teste que usa aquela página.

Page Object Model (POM) resolve isso: cada página do site vira uma classe, que guarda os locators e as ações daquela página. O teste chama métodos com nome de negócio (cadastro.preencher(...)), não detalhes de seletor:

// pages/CadastroPage.js
class CadastroPage {
  constructor(page) {
    this.page = page;
    this.nameInput = page.getByTestId("signup-name-input");
    this.emailInput = page.getByTestId("signup-email-input");
    this.passwordInput = page.getByTestId("signup-password-input");
    this.submitButton = page.getByTestId("signup-submit-button");
    this.successMessage = page.getByTestId("signup-success-message");
    this.nameError = page.getByTestId("signup-field-error-name");
    this.emailError = page.getByTestId("signup-field-error-email");
    this.passwordError = page.getByTestId("signup-field-error-password");
  }

  async goto() {
    await this.page.goto("/cadastro.html");
  }

  async preencher({ nome = "", email = "", senha = "" }) {
    await this.nameInput.fill(nome);
    await this.emailInput.fill(email);
    await this.passwordInput.fill(senha);
  }

  async submeter() {
    await this.submitButton.click();
  }
}

module.exports = { CadastroPage };

constructor(page) é um método especial de toda classe: ele roda sozinho, uma vez, no exato momento em que alguém escreve new CadastroPage(page) (isso acontece no beforeEach do teste, mais abaixo). É o lugar certo pra preparar tudo que o objeto vai precisar. Aqui ele recebe o page do Playwright (o mesmo que o Módulo 1 usava direto no teste) e guarda, em this.algumaCoisa, um locator pronto pra cada campo e botão da tela de cadastro. Esses this.algumaCoisa ficam acessíveis pros outros métodos da classe (preencher, submeter) e pro teste, como cadastro.passwordError faz lá na frente.

Arquivo já existe em pages/CadastroPage.js. Se o data-testid de um campo mudar amanhã, você corrige em um lugar, a classe, e todo teste que usa CadastroPage continua funcionando.

3. Usando a Page Object no teste

O teste cria a página no beforeEach e usa seus métodos e locators. Repare como fica mais curto e mais fácil de ler, quase uma frase:

const { CadastroPage } = require("../pages/CadastroPage");

test.describe("cadastro", () => {
  let cadastro;

  test.beforeEach(async ({ page }) => {
    cadastro = new CadastroPage(page);
    await cadastro.goto();
  });

  test("cadastro valido mostra mensagem de sucesso", async () => {
    await cadastro.preencher({ nome: "Joana", email: "joana@exemplo.com", senha: "SenhaForte123" });
    await cadastro.submeter();

    await expect(cadastro.successMessage).toBeVisible();
  });
});

Note que o teste não chama page.getByTestId nem uma vez. Isso é o que fica "escondido" dentro da Page Object: o teste descreve comportamento (preencher, submeter, ver sucesso); a classe sabe como fazer isso nesta página específica.

4. toBeVisible e toBeHidden

Validação de formulário é o lugar clássico pra testar os dois lados de uma asserção: o erro certo aparece, e os errados não aparecem. toBeHidden() é o oposto de toBeVisible(): também espera sozinho, só que até o elemento sumir (ou não existir, ou estar com display:none):

await expect(cadastro.passwordError).toBeVisible();
await expect(cadastro.nameError).toBeHidden();

Testar só o "caminho feliz" (tudo certo) ou só o erro mais óbvio deixa passar bug de validação cruzada: por exemplo, um campo que acende o erro errado, ou todos os erros acendendo juntos quando só um campo estava errado.

5. Os testes deste módulo

Arquivo já existe em tests/02_selecionadores.spec.js. Não precisa digitar, só rodar (com o site de treino ligado):

npx playwright test tests/02_selecionadores.spec.js

Bug real encontrado nesta trilha: o formulário de cadastro tinha required nos campos, e a validação nativa do navegador bloqueava o envio antes do JavaScript customizado rodar, então as mensagens de erro por campo nunca apareciam de verdade. A correção foi adicionar novalidate no <form>, pra deixar a validação customizada (a que tem data-testid e o teste consegue verificar) ser a única a rodar. Se você testou um formulário e a asserção nunca falha nem passa do jeito esperado, vale suspeitar disso: o navegador pode estar resolvendo antes do seu código.

Exercício

Em tests/02_selecionadores.spec.js, dentro do mesmo describe("cadastro", ...), escreva um teste chamado "senha curta mostra so o erro de senha" usando a cadastro (a CadastroPage já criada no beforeEach): preenche nome e e-mail válidos, senha com menos de 6 caracteres, submete, e confere que só o erro de senha aparece (os de nome e e-mail continuam escondidos). Rode npx playwright test tests/02_selecionadores.spec.js e depois python verificar.py 2 até dar 4 [ok]. Gabarito em trilha-7/GABARITO.md.

📁 Ver estrutura de pastas da trilha-7/
trilha-7/
├── playwright.config.js
├── package.json
├── verificar.py
├── GABARITO.md
├── README.md
├── fixtures/
│   └── exemplo.png
├── pages/
│   ├── LoginPage.js   ← Page Object, já existia
│   ├── CadastroPage.js   ← Page Object, usado neste exercício
│   ├── ProdutosPage.js
│   ├── ProdutoPage.js
│   └── RedePage.js
└── tests/
    ├── 01_primeiro_teste.spec.js
    ├── 02_selecionadores.spec.js   ← este exercício
    ├── 03_cenarios.spec.js
    └── 04_rede_ci.spec.js

Olhar de QA

Formulário de validação é um dos lugares onde teste automatizado paga mais rápido: são várias combinações de campo válido/inválido, fáceis de esquecer num teste manual repetitivo, e cada uma é uma linha de teste aqui. O cuidado é não parar no "apareceu uma mensagem de erro": confira qual mensagem, e que as outras não apareceram. Teste que só confere "tem erro na tela" aceita um sistema que mostra a mensagem errada pro campo certo.

E sobre organização: Page Object Model não é burocracia, é o que separa um projeto de automação que qualquer um do time consegue manter de um que só quem escreveu entende. Nos próximos módulos, repare como o Módulo 3 reaproveita o login que o Módulo 1 já testou, em vez de reescrever o fluxo de novo. É o POM pagando o investimento.

Terminou este módulo? Marque: o ✓ aparece no índice da trilha e a barra de progresso anda no começo do site. Fica salvo só no seu navegador.