QA Learning / Playwright

Módulo 3: Cenários do site de treino

Login compartilhado entre testes, loading assíncrono, paginação, estado vazio, modal e upload de arquivo.

🎥 Prefere ver antes de ler?

Esse vídeo (4min08) explica o regex do toHaveURL, a query string que força a lista vazia, a diferença entre toContainText e toHaveText, como o upload de arquivo funciona sem abrir a janela do sistema, e termina com tudo isso acontecendo de verdade no site de treino, incluindo o arrastar e soltar da galeria. Não substitui o texto abaixo, mas ajuda a visualizar antes de praticar.

1. Reaproveitando a LoginPage do Módulo 1

/index.html, /produto.html e /carrinho.html exigem sessão: sem login, redirecionam pra /login.html. O Módulo 2 te ensinou Page Object Model; aqui está o primeiro pagamento desse investimento: em vez de reescrever o preenchimento do login, criamos pages/LoginPage.js (mesma ideia da CadastroPage) e reaproveitamos ela, o mesmo fluxo que o Módulo 1 já testou:

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

async function login(page) {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login("maria@exemplo.com", "SenhaForte123");
  await expect(page).toHaveURL(/index\.html/);
}

test.describe("lista de produtos", () => {
  test.beforeEach(async ({ page }) => {
    await login(page);
  });
  // ...
});

O await expect(page).toHaveURL(...) no fim da função importa: o clique dispara um fetch com atraso proposital antes de redirecionar. Sem esperar a URL mudar, o próximo passo do teste pode rodar contra a tela de login ainda meio carregada.

2. Loading assíncrono, paginação e estado vazio

A lista de produtos também ganhou uma Page Object (pages/ProdutosPage.js). Ela busca os dados com um atraso proposital: dá pra testar o carregamento de verdade, não um "pisca" rápido demais pra reparar:

const produtos = new ProdutosPage(page);
await expect(produtos.produto(1)).toBeVisible();
await expect(produtos.loadingSpinner).toBeHidden();

A mesma página tem um parâmetro pra forçar lista vazia, sem precisar mexer no servidor, útil pra testar o estado vazio isoladamente. É por isso que ProdutosPage.goto() aceita uma opção:

await produtos.goto({ vazio: true }); // navega pra /index.html?vazio=1
await expect(produtos.emptyState).toBeVisible();

E a paginação é só clicar e conferir o número atual:

await produtos.paginationNext.click();
await expect(produtos.paginationCurrent).toHaveText("2");

3. Modal de confirmação

Ao adicionar um produto ao carrinho, um modal confirma. A ProdutoPage (pages/ProdutoPage.js) já espera o detalhe do produto carregar dentro do próprio goto(id). O botão "adicionar" não faz nada se o produto ainda não carregou (não há indicador disso na tela, vale anotar como melhoria de UX pra reportar), então esperar faz parte de "abrir a página" nessa classe, não algo que cada teste precisa lembrar de repetir:

const produto = new ProdutoPage(page);
await produto.goto(1); // ja espera o titulo carregar

await produto.addToCartButton.click();
await expect(produto.modalMessage).toContainText("foi adicionado");

4. Upload de arquivo

setInputFiles simula a escolha de um arquivo sem precisar abrir o seletor nativo do sistema operacional:

await produto.fileInput.setInputFiles(
  path.join(__dirname, "..", "fixtures", "exemplo.png")
);
await expect(produto.filePreview).toContainText("exemplo.png");

Funciona mesmo com o accept="image/*" do campo: esse atributo só filtra o que aparece no seletor manual, não bloqueia setInputFiles.

5. Além da trilha: fixtures (test.extend)

O padrão que você acabou de praticar — criar new LoginPage(page) dentro de um beforeEach — funciona e é assim que esta trilha ensina do início ao fim. Mas em projetos Playwright de médio/grande porte, o jeito recomendado pela documentação oficial é empacotar isso numa fixture customizada, com test.extend. A ideia: em vez de repetir "crie o Page Object e faça login" em cada arquivo de teste, você declara isso uma vez e o Playwright injeta pronto, exatamente como já injeta { page }:

// fixtures.js
const base = require("@playwright/test");
const { LoginPage } = require("./pages/LoginPage");

exports.test = base.test.extend({
  paginaLogada: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login("maria@exemplo.com", "SenhaForte123");
    await base.expect(page).toHaveURL(/index\.html/);
    await use(page); // o teste roda aqui, ja logado
  },
});
exports.expect = base.expect;

No teste, troca-se o require("@playwright/test") pelo arquivo de fixtures, e o beforeEach de login some: quem pede paginaLogada no teste já recebe a página logada, sem escrever a lógica de novo:

const { test, expect } = require("../fixtures");

test("produtos aparecem depois do carregamento", async ({ paginaLogada }) => {
  await expect(paginaLogada.getByTestId("product-list")).toBeVisible();
});

Por que isso importa: o beforeEach manual que esta trilha usa não escala bem quando você tem várias combinações de setup (logado como admin, logado sem produtos no carrinho, logado com sessão expirada...) — vira um emaranhado de funções auxiliares. Fixtures resolvem isso com composição: cada teste pede só as fixtures que precisa, e o Playwright cuida da ordem e da limpeza. Esta trilha fica no beforeEach de propósito, por ser mais fácil de enxergar o que acontece quando você está aprendendo — mas se o próximo projeto usar test.extend, agora você já reconhece o padrão.

6. Os testes deste módulo

Arquivo já existe em tests/03_cenarios.spec.js. Não precisa digitar, só rodar:

npx playwright test tests/03_cenarios.spec.js

Exercício

A galeria do produto tem drag and drop de verdade (eventos HTML5 nativos, não um plugin). Em tests/03_cenarios.spec.js, dentro do describe da página de produto, escreva um teste chamado "arrastar o primeiro item da galeria pro final reordena a lista" usando produto.galleryItem(n) e .dragTo():

await produto.galleryItem(1).dragTo(produto.galleryItem(3));

Depois confira a nova ordem com produto.ordemDaGaleria() (já existe na ProdutoPage) e confirme que o item 1 não é mais o primeiro. Rode python verificar.py 3 até dar 7 [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
│   ├── CadastroPage.js
│   ├── ProdutosPage.js
│   ├── ProdutoPage.js   ← Page Object usado neste exercício
│   └── RedePage.js
└── tests/
    ├── 01_primeiro_teste.spec.js
    ├── 02_selecionadores.spec.js
    ├── 03_cenarios.spec.js   ← este exercício
    └── 04_rede_ci.spec.js

Olhar de QA

Repare que cada teste deste módulo espera por algo específico antes de agir (o título do produto não estar vazio, o spinner sumir). Nenhum usa sleep(). Isso não é estilo, é necessidade: o atraso artificial das APIs varia, e esperar "um tempo fixo" cedo demais produz teste instável (passa na sua máquina, falha na do colega ou no CI, que costuma ser mais lento). Esperar a condição certa em vez do tempo certo é o que separa teste de UI confiável de teste de UI que ninguém confia mais depois de algumas semanas.

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.