Quando um time de backend renomeia um campo da resposta, os testes E2E no staging pegam isso depois do deploy. O contract testing pega antes. O frontend gera um contrato formal a partir dos seus próprios testes. O backend verifica que consegue satisfazer essas expectativas de forma independente, sem rodar a suite de integração completa.

O que é contract testing

Contract testing verifica que dois serviços concordam com o formato da sua comunicação, não apenas que cada serviço funciona corretamente de forma isolada.

Um contrato é uma descrição formal do que um consumidor (seu frontend ou outro serviço) espera de um provedor (sua API). O consumidor gera o contrato a partir dos seus próprios testes. O provedor verifica que consegue satisfazer essas expectativas sem rodar a suite completa de testes do consumidor.

O resultado: você pega mudanças que quebram a API antes de chegarem ao staging.

Pact: a ferramenta padrão

Pact é a biblioteca de contract testing mais adotada. Funciona gravando interações reais de API durante os testes do consumidor, salvando-as como "pacts" (arquivos JSON), e reproduzindo-as contra o provedor real.

Instalação no lado do consumidor (frontend)

npm install -D @pact-foundation/pact

Escrevendo um teste de consumidor

Esse teste define o que seu frontend espera do endpoint /users/1:

import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import path from 'path';

const { like, string, integer } = MatchersV3;

const provider = new PactV3({
  consumer: 'frontend',
  provider: 'user-api',
  dir: path.resolve(process.cwd(), 'pacts'),
});

describe('User API contract', () => {
  it('returns user data', async () => {
    await provider
      .given('user 1 exists')
      .uponReceiving('a request for user 1')
      .withRequest({
        method: 'GET',
        path: '/users/1',
        headers: { Authorization: like('Bearer token123') },
      })
      .willRespondWith({
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: integer(1),
          name: string('Alice'),
          email: string('alice@example.com'),
        },
      })
      .executeTest(async (mockServer) => {
        const response = await fetch(`${mockServer.url}/users/1`, {
          headers: { Authorization: 'Bearer token123' },
        });
        const data = await response.json();

        expect(data.name).toBeDefined();
        expect(data.email).toBeDefined();
      });
  });
});

Quando esse teste roda, o Pact:

1. Inicia um servidor mock

2. Grava a interação (requisição + resposta esperada) como um arquivo pact em ./pacts/

3. Executa seu teste contra o servidor mock

O arquivo pact gerado é o contrato. Ele tem esta aparência:

{
  "consumer": { "name": "frontend" },
  "provider": { "name": "user-api" },
  "interactions": [{
    "description": "a request for user 1",
    "request": { "method": "GET", "path": "/users/1" },
    "response": {
      "status": 200,
      "body": { "id": 1, "name": "Alice", "email": "alice@example.com" }
    }
  }]
}

Verificação no lado do provedor (backend)

O time de backend pega esse arquivo pact e verifica que a API deles o satisfaz:

import { Verifier } from '@pact-foundation/pact';

describe('Provider verification', () => {
  it('validates the expectations of Frontend', () => {
    return new Verifier({
      provider: 'user-api',
      providerBaseUrl: 'http://localhost:3001',
      pactUrls: [path.resolve(process.cwd(), 'pacts/frontend-user-api.json')],
      stateHandlers: {
        'user 1 exists': async () => {
          // popular usuário de teste no banco
          await db.users.create({ id: 1, name: 'Alice', email: 'alice@example.com' });
        },
      },
    }).verifyProvider();
  });
});

O verificador envia a requisição gravada para o backend real e verifica se a resposta corresponde ao contrato. Se o time de backend renomear name para full_name, essa verificação falha antes de qualquer código chegar ao staging.

Onde o Playwright se encaixa

O Playwright lida com testes E2E de integração. O contract testing lida com verificação na fronteira entre serviços. Eles se complementam.

Na prática:

  • Testes de contrato pegam: renomes de campos, campos removidos, status codes alterados, incompatibilidades de tipo
  • Testes E2E com Playwright pegam: problemas de renderização de UI, quebras de fluxo do usuário, falhas de integração full-stack

Você não substitui testes Playwright por testes de contrato. Testes de contrato tornam seus testes Playwright mais rápidos e estáveis: menos falhas E2E surgem de mudanças de formato de API que deveriam ter sido pegas mais cedo.

Pact Broker: compartilhando contratos entre times

Quando consumidor e provedor estão em repositórios separados (comum em microsserviços), você precisa de um lugar central para armazenar e compartilhar arquivos pact. O Pact Broker é a ferramenta padrão: open source, auto-hospedável, ou disponível como PactFlow (o serviço em nuvem deles).

Os times publicam pacts depois que os testes do consumidor rodam. Os pipelines do provedor puxam o pact mais recente e verificam antes de fazer deploy. Isso cria uma verificação de dependência: o provedor não consegue fazer deploy se quebrar o contrato de um consumidor.

Quando o contract testing vale a pena

O contract testing adiciona overhead: escrever testes de consumidor no DSL do Pact, configurar state handlers do provedor, manter um broker. Vale a pena quando:

  • Você tem múltiplos apps frontend ou serviços chamando a mesma API
  • Mudanças de API frequentemente quebram consumidores downstream
  • Você está em uma arquitetura de microsserviços onde os times fazem deploy de forma independente
  • Sua suite E2E é lenta e você quer pegar falhas de integração mais cedo

Provavelmente não vale para:

  • Um monolito onde frontend e backend estão no mesmo repositório e fazem deploy juntos
  • Um time de duas pessoas onde os dois lados são a mesma pessoa
  • Um projeto em estágio inicial onde a API muda com muita frequência para manter contratos estáveis

Comece com Playwright para E2E. Adicione contract testing quando tiver dois serviços implantados de forma independente que quebram as suposições um do outro.

→ Veja também: Testes de API com o APIRequestContext do Playwright (Sem Postman) | Testes de API 101: Tudo que Todo Engenheiro QA Precisa Saber em 2026 | A Pirâmide de Testes Explicada para Engenheiros QA