QA Learning / Playwright

Módulo 5: Testes de API

O fixture request do Playwright, chamar API sem abrir navegador, autenticação com token, e test.describe.serial pro ciclo de vida de um recurso.

🎥 Prefere ver antes de ler?

Esse vídeo (6min10) explica o que é o fixture request, o que é um header e o formato Bearer, de onde vem esse token (e de onde vem num projeto de verdade), por que data vira JSON sozinho, por que test.describe.serial existe (e por que os outros módulos nunca precisaram dele), como res.json() lê o corpo da resposta, e o cuidado de limpar o que você criou já que o site não tem banco de dados. Fecha mostrando o comando rodando de verdade num terminal (repara: nenhuma janela de navegador abre) e criando/apagando um produto direto pela API, refletido ao vivo na lista do site. Não substitui o texto abaixo, mas ajuda a visualizar antes de praticar.

1. Por que testar API com o Playwright, se ele é de navegador

Os Módulos 1 a 4 abriram um navegador pra cada teste. Mas boa parte do que vale testar numa API não precisa de tela nenhuma: criar um dado de setup antes de um teste de UI, conferir que um endpoint devolve o status e o corpo certos, ou testar um cenário que seria lento (ou frágil) de montar clicando na página. O @playwright/test já vem com um fixture pra isso: request. Ele faz chamada HTTP pura, sem Chromium, sem page.goto, e aponta pro mesmo baseURL configurado em playwright.config.js:

test("GET /api/produtos retorna 200", async ({ request }) => {
  const res = await request.get("/api/produtos?id=1");
  expect(res.status()).toBe(200);
});

Repare que o teste recebe request em vez de page: são dois fixtures diferentes que o Playwright oferece, e você usa o que o teste precisa (ou os dois juntos, como no desafio no fim deste módulo).

2. Autenticação com token

No QA Treino, ler produto é público, mas criar, editar e excluir exigem um token fixo no header Authorization. É o cenário mais comum de API em produção: leitura aberta, escrita protegida. Sem o header certo, a API responde 401:

De onde vem esse token-treino-123? Aqui, de lugar nenhum dinâmico: ele está escrito no README do QA Treino, só pra simplificar o aprendizado. Repare que o /api/login deste site (o mesmo do Módulo 1) não devolve token nenhum, só o nome da pessoa pra controlar a sessão da tela — são dois mecanismos completamente separados. Numa API de verdade, o token quase nunca é um valor fixo decorado: ele normalmente é gerado na hora, por um endpoint de login que devolve um token novo a cada vez (ou por um painel de "API keys" do serviço), e costuma expirar depois de um tempo. Se um dia você testar uma API assim, o primeiro passo do teste costuma ser literalmente fazer esse login e guardar o token que veio na resposta, pra usar nas chamadas seguintes.

Sobre deixar o token escrito no código: aqui é só pra simplificar o aprendizado, mas num projeto de verdade isso é considerado vazamento de segredo — o mesmo cuidado que o BASE_URL já recebe no Módulo 1 (process.env.BASE_URL || "..." no playwright.config.js). Token, senha e chave de API seguem a mesma lógica: ficam numa variável de ambiente (ou num .env fora do controle de versão) e o teste só lê process.env.TOKEN, nunca o valor escrito direto no arquivo.

const TOKEN = "token-treino-123";
const AUTORIZADO = { Authorization: `Bearer ${TOKEN}` };

test("POST sem token retorna 401", async ({ request }) => {
  const res = await request.post("/api/produtos", {
    data: { nome: "Produto sem token", preco: 10 },
  });
  expect(res.status()).toBe(401);
});

test("POST com token cria produto", async ({ request }) => {
  const res = await request.post("/api/produtos", {
    headers: AUTORIZADO,
    data: { nome: "Produto de teste", preco: 50, categoria: "Acessórios", emoji: "🧪" },
  });
  expect(res.status()).toBe(201);
});

O data vira o corpo da requisição em JSON automaticamente (não precisa de JSON.stringify nem de Content-Type manual). headers é onde entra qualquer header extra, como o Authorization aqui.

3. test.describe.serial: testar o ciclo de vida de um recurso

Nos módulos anteriores, cada teste era independente de propósito, o Playwright roda testes em paralelo e não garante ordem. Mas pra testar criar → ler → atualizar → excluir o mesmo produto, os passos dependem um do outro: o segundo precisa do id que o primeiro criou. test.describe.serial é a exceção deliberada: roda os testes do grupo em ordem, na mesma worker, e permite compartilhar uma variável entre eles:

test.describe.serial("ciclo de vida de um produto via API", () => {
  let produtoId;

  test("POST com token cria produto", async ({ request }) => {
    const res = await request.post("/api/produtos", {
      headers: AUTORIZADO,
      data: { nome: "Produto de teste", preco: 50, categoria: "Acessórios", emoji: "🧪" },
    });
    const corpo = await res.json();
    produtoId = corpo.item.id;
  });

  test("PUT atualiza o preco", async ({ request }) => {
    const res = await request.put(`/api/produtos?id=${produtoId}`, {
      headers: AUTORIZADO,
      data: { preco: 120 },
    });
    expect(res.status()).toBe(200);
  });

  test("DELETE remove o produto", async ({ request }) => {
    const res = await request.delete(`/api/produtos?id=${produtoId}`, {
      headers: AUTORIZADO,
    });
    expect(res.status()).toBe(200);
  });
});

await res.json() lê o corpo da resposta já convertido de JSON pra objeto JavaScript, é assim que o teste pega o id gerado pra usar no próximo passo. Use test.describe.serial com moderação: fora desse tipo de cenário (dependência real entre passos), testes independentes continuam sendo a regra, porque rodam em paralelo e uma falha não arrasta as outras.

4. Sem banco, sem reset: o cuidado que isso exige

O QA Treino guarda os produtos em memória, sem banco de verdade. Isso significa duas coisas importantes pros seus testes:

  • os produtos criados em teste ficam lá até o servidor reiniciar, não existe endpoint de reset;
  • por isso, todo teste que cria um produto neste módulo também o apaga no fim do próprio teste (ou do grupo, no caso do describe.serial acima) — é o padrão de limpar o que você sujou, e vale pra testes de API tanto quanto pra testes de UI;
  • os 12 produtos originais (ids 1 a 12) nunca devem ser editados ou apagados nos seus testes, porque os Módulos 1 a 4 (e a prova deles) dependem desses dados continuarem do jeito que estavam.

5. Os testes deste módulo

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

npx playwright test tests/05_api.spec.js

Exercício

Em tests/05_api.spec.js, escreva um teste chamado "PUT sem token retorna 401 e nao muda o preco": faça um request.put("/api/produtos?id=1", { data: { preco: 999 } }) sem o header Authorization, confira que a resposta é 401, e depois confira com um GET que o preço do produto 1 continua 249.9 (não mudou). Rode npx playwright test tests/05_api.spec.js e depois python verificar.py 5 até dar 8 [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
│   └── RedePage.js
└── tests/
    ├── 01_primeiro_teste.spec.js
    ├── 02_selecionadores.spec.js
    ├── 03_cenarios.spec.js
    ├── 04_rede_ci.spec.js
    ├── 05_api.spec.js   ← este exercício
    └── 06_desafio_final.spec.js

Olhar de QA

Testar API é geralmente mais barato e mais estável do que testar a mesma coisa pela UI: sem navegador, sem seletor, sem esperar animação. Por isso, quando um cenário pode ser verificado nas duas camadas, a camada de API costuma ser onde cobrir mais casos de borda (status, validação, autorização) compensa mais, e a UI fica pra confirmar que tudo isso aparece certo na tela pro usuário final. O desafio final deste trilha combina as duas camadas no mesmo teste, que é exatamente esse raciocínio na prática: API pra preparar e mudar o dado, UI pra confirmar o que a pessoa realmente vê.

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.