O fixture request do Playwright faz chamadas HTTP sem abrir um navegador, completando em 50 a 200 milissegundos versus 3 a 5 segundos para um teste de UI. Ele está disponível ao lado de page em qualquer teste Playwright, o que permite um padrão híbrido. Criar dados de teste via API, verificar pela UI, e limpar via API, tudo em um único arquivo de teste sem nenhuma ferramenta extra.
Por que testes de API pertencem à sua suite Playwright
O argumento para testes de API não é sobre substituir testes de UI. É sobre testar as coisas certas no nível certo.
Um teste de UI que cria um item de viagem passa pelo navegador, renderiza o formulário, preenche os inputs, clica em enviar, aguarda a página atualizar, depois verifica o resultado. Leva 3 a 5 segundos e pode falhar por uma dúzia de razões não relacionadas à feature real: uma animação lenta, um localizador alterado, uma requisição de rede frágil.
Um teste de API que cria um item de viagem envia uma requisição HTTP e verifica a resposta. Leva 50 a 200 milissegundos e falha apenas quando a própria API está quebrada.
Use testes de API para: validação de dados, regras de autenticação, respostas de erro, lógica de negócio no backend. Use testes de UI para: fluxos do usuário, renderização visual, interações com o frontend. Ambos têm seu lugar. O erro é usar apenas testes de UI quando o bug está na API.
O fixture request
O Playwright expõe um APIRequestContext através do fixture request. Ele está disponível em qualquer teste da mesma forma que page:
import { test, expect } from '@playwright/test';
test('GET /api/items retorna uma lista', async ({ request }) => {
const response = await request.get('https://lab.becomeqa.com/api/items');
expect(response.status()).toBe(200);
const items = await response.json();
expect(items.length).toBeGreaterThan(0);
});Nenhum navegador abre. Nenhuma página carrega. Apenas uma requisição HTTP e uma resposta. O fixture request trata cookies, headers e configuração de base URL automaticamente.
Requisições GET e validação de resposta
O padrão de teste de API mais comum: chamar um endpoint, verificar o status, verificar a estrutura dos dados.
test('GET /api/items retorna estrutura correta', async ({ request }) => {
const response = await request.get('https://lab.becomeqa.com/api/items');
// Verificar status HTTP
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy(); // true para status codes 2xx
// Parsear o corpo da resposta
const items = await response.json();
// Verificar o array
expect(Array.isArray(items)).toBe(true);
expect(items.length).toBeGreaterThan(0);
// Verificar a estrutura do primeiro item
const firstItem = items[0];
expect(firstItem).toHaveProperty('id');
expect(firstItem).toHaveProperty('destination');
expect(firstItem).toHaveProperty('status');
});response.ok() é um atalho para status >= 200 && status < 300. Use quando você só quer saber se a requisição foi bem-sucedida.
Para verificar headers da resposta:
const contentType = response.headers()['content-type'];
expect(contentType).toContain('application/json');Requisições POST
Requisições POST enviam dados para criar um recurso. Passe o corpo como JSON:
test('POST /api/items cria um novo item', async ({ request }) => {
const response = await request.post('https://lab.becomeqa.com/api/items', {
data: {
destination: 'Tokyo',
status: 'planned',
notes: 'Temporada de cerejeiras'
}
});
expect(response.status()).toBe(201);
const created = await response.json();
expect(created).toHaveProperty('id');
expect(created.destination).toBe('Tokyo');
expect(created.status).toBe('planned');
});A opção data serializa automaticamente para JSON e define o header Content-Type: application/json.
Para requisições form-encoded use form em vez de data:
const response = await request.post('/api/login', {
form: {
username: 'admin@becomeqa.com',
password: 'testpass123'
}
});Autenticação
A maioria das APIs reais exige autenticação. Dois padrões são comuns.
Bearer token no header:test('GET autenticado retorna dados do usuário', async ({ request }) => {
// Primeiro, obter um token
const loginResponse = await request.post('https://lab.becomeqa.com/api/auth/login', {
data: {
username: 'admin@becomeqa.com',
password: 'testpass123'
}
});
const { token } = await loginResponse.json();
// Usar o token nas requisições subsequentes
const response = await request.get('https://lab.becomeqa.com/api/items', {
headers: {
'Authorization': `Bearer ${token}`
}
});
expect(response.status()).toBe(200);
});Se seu app usa cookies para auth, você pode fazer login pelo navegador e reutilizar a sessão para chamadas de API. O objeto page.request compartilha cookies com o contexto do navegador:
test('chamada de API com sessão do navegador', async ({ page, request }) => {
// Login pela UI
await page.goto('https://lab.becomeqa.com');
await page.getByRole('button', { name: 'Login' }).click();
await page.getByLabel('Username').fill('admin@becomeqa.com');
await page.getByLabel('Password').fill('testpass123');
await page.getByRole('button', { name: 'Submit' }).click();
// Usar page.request: ele carrega o cookie de auth
const response = await page.request.get('https://lab.becomeqa.com/api/items');
expect(response.status()).toBe(200);
});Observe a diferença: request (o fixture) é um contexto independente sem cookies do navegador. page.request compartilha cookies com a sessão atual do navegador.
Testando respostas de erro
APIs devem retornar erros significativos. Teste-os explicitamente.
test('GET /api/items/:id retorna 404 para id desconhecido', async ({ request }) => {
const response = await request.get('https://lab.becomeqa.com/api/items/id-inexistente-99999');
expect(response.status()).toBe(404);
const body = await response.json();
expect(body).toHaveProperty('error');
});
test('POST /api/items retorna 400 para campos obrigatórios faltando', async ({ request }) => {
const response = await request.post('https://lab.becomeqa.com/api/items', {
data: {
// campo 'destination' obrigatório faltando
status: 'planned'
}
});
expect(response.status()).toBe(400);
});
test('endpoint protegido retorna 401 sem auth', async ({ request }) => {
const response = await request.get('https://lab.becomeqa.com/api/items');
// sem header Authorization
expect(response.status()).toBe(401);
});Esses testes verificam que a API lida corretamente com entradas ruins, não apenas o caminho principal.
Combinando testes de API e UI
Um dos padrões mais poderosos: usar a API para configurar dados de teste, depois verificar pela UI. Ou o inverso: interagir pela UI, verificar pela API.
Setup via API, verificação pela UI:test('item criado aparece na tabela da UI', async ({ page, request }) => {
// Criar item via API: rápido e confiável
const createResponse = await request.post('https://lab.becomeqa.com/api/items', {
data: { destination: 'Lisboa', status: 'planned' }
});
const { id } = await createResponse.json();
// Verificar se aparece na UI
await page.goto('https://lab.becomeqa.com');
// ... passos de login ...
await expect(page.getByText('Lisboa')).toBeVisible();
// Limpeza via API após o teste
await request.delete(`https://lab.becomeqa.com/api/items/${id}`);
});Esse teste é mais rápido e mais confiável. O setup pela UI é a fonte mais comum de flakiness em testes que não estão realmente testando a UI.
Ação pela UI, verificação via API:test('deletar item pela UI remove da API', async ({ page, request }) => {
// Setup via API
const createResponse = await request.post('https://lab.becomeqa.com/api/items', {
data: { destination: 'Oslo', status: 'planned' }
});
const { id } = await createResponse.json();
// Ação pela UI
await page.goto('https://lab.becomeqa.com');
// ... login, encontrar item, clicar em deletar ...
// Verificar via API
const checkResponse = await request.get(`https://lab.becomeqa.com/api/items/${id}`);
expect(checkResponse.status()).toBe(404);
});Configure uma base URL para testes de API
Repetir a URL completa em cada teste é ruído. Defina baseURL no playwright.config.ts:
export default defineConfig({
use: {
baseURL: 'https://lab.becomeqa.com',
},
});Agora você pode usar caminhos relativos:
const response = await request.get('/api/items');
const response = await request.post('/api/items', { data: { ... } });Para projetos com base URLs separadas para UI e API, crie um fixture customizado que configura o contexto de API separadamente.
extraHTTPHeaders à configuração se sua API exige um header comum em cada requisição (como uma API key ou um header customizado X-App-Version). Defina uma vez na configuração em vez de em cada teste.export default defineConfig({
use: {
baseURL: 'https://lab.becomeqa.com',
extraHTTPHeaders: {
'X-Api-Key': process.env.API_KEY || '',
},
},
});Organize os testes de API separadamente
Mantenha os testes de API em sua própria pasta para poder executá-los independentemente dos testes de UI:
tests/
ui/
login.spec.ts
items.spec.ts
api/
items-api.spec.ts
auth-api.spec.ts# Rodar apenas testes de API: rápido, sem navegador
npx playwright test tests/api/
# Rodar apenas testes de UI
npx playwright test tests/ui/Os testes de API rodam significativamente mais rápido que os testes de UI. Executá-los separadamente no CI permite feedback rápido sobre bugs de backend antes que a suite de UI mais lenta termine.
FAQ
Ainda preciso do Postman se uso o Playwright para testes de API?O Postman é útil para exploração manual de API: descobrir quais endpoints existem, quais parâmetros aceitam, como são as respostas. Quando você já sabe o que testar, escreva a versão automatizada no Playwright. Ambos têm seu lugar.
Todo endpoint de API precisa ter um teste?Foque nos endpoints que importam: autenticação, operações de dados principais, e qualquer endpoint com lógica de validação complexa. Uma API CRUD para itens de viagem precisa de testes para criar, ler, atualizar, deletar, e pelo menos os casos de erro principais (404, 400, 401). Não toda combinação de inputs.
Como testo uploads de arquivo via API?Use a opção multipart:
const response = await request.post('/api/upload', {
multipart: {
file: {
name: 'test.pdf',
mimeType: 'application/pdf',
buffer: Buffer.from('conteúdo fake do pdf'),
}
}
});Sim. Testes que usam apenas request e não page rodam sem abrir um navegador. São testes HTTP puros e rodam tão rápido quanto qualquer outro cliente HTTP.