Um teste E2E pode ficar verde sem fazer uma única chamada ao serviço que você
acha que está verificando. Isso acontece quando page.route() intercepta a
requisição e devolve um JSON fixo. O navegador provou que a tela reage àquele
JSON. Ainda não provou que a API aceita o método, os headers, o corpo ou o
formato da resposta.
A solução não é abandonar mocks. É dar a cada teste uma pergunta menor. Use
route.fulfill para verificar estados da interface com respostas determinísticas.
Use route.fetch quando precisar partir da resposta real e alterar apenas um
campo. Use HAR para reproduzir uma sequência de rede conhecida. Depois, rode
um teste de integração separado contra staging, sandbox ou um contrato
verificado pelo provedor.
Este artigo parte do guia de testes end-to-end com Playwright e trata de uma dúvida mais estreita: como isolar a rede sem transformar o isolamento em falsa confiança.

Regra prática
route.fulfilltesta a reação da aplicação a um caso conhecido.route.fetchmantém uma chamada real e altera a resposta antes de entregá-la ao navegador.routeFromHARreproduz tráfego gravado, mas depende de correspondência de URL, método e payload.- Um teste separado precisa verificar o contrato real. O mock não faz esse trabalho sozinho.
O que um mock de API realmente prova?
Um mock prova o comportamento do cliente diante da resposta que você escolheu.
A documentação de Mock APIs do Playwright
mostra que route.fulfill pode impedir a chamada e devolver dados definidos no
teste. Essa é uma propriedade útil: o fluxo não depende da disponibilidade,
latência ou estado de um terceiro durante cada execução do navegador.
O que fica fora da prova é igualmente importante. O teste não descobre que o
endpoint mudou de POST para PUT, que o servidor passou a exigir um header,
que o schema removeu um campo ou que a autenticação expirou. O mock responde
antes que essas condições tenham chance de aparecer.
Isso não é um defeito do Playwright. É o limite do nível de teste. Um teste de interface pergunta se a pessoa consegue concluir uma ação quando recebe um estado. Um teste de contrato ou integração pergunta se dois sistemas ainda concordam sobre a mensagem. O guia de testes de software ajuda a separar essas perguntas antes de escolher a ferramenta.
O falso positivo aparece quando uma equipe chama o primeiro teste de "teste da
API". O nome sugere uma cobertura que o código não tem. Prefira nomes que
declarem a fronteira, como checkout mostra erro quando o pagamento responde 503 e sandbox aceita o contrato de criação de pagamento.
Esse problema também aparece em testes Playwright gerados por IA: uma sequência plausível e uma asserção verde não provam que a dependência certa foi exercitada.
Quando usar route.fulfill, route.fetch ou HAR?
Escolha a forma de interceptação pelo que precisa permanecer sob controle. A documentação de Network do Playwright separa interceptar uma requisição, modificá-la, abortá-la e alterar sua resposta. A diferença é pequena no código, mas muda o que o teste consegue provar.
| Técnica | A chamada chega à API? | Melhor pergunta | Risco principal |
|---|---|---|---|
route.fulfill |
Não | A UI trata este estado conhecido? | O fixture pode divergir do contrato real. |
route.fetch + route.fulfill |
Sim, durante o teste | A UI trata uma resposta real com uma variação controlada? | O teste herda instabilidade e dados externos. |
routeFromHAR |
Não, usa o arquivo gravado | O fluxo reproduzido funciona com uma conversa de rede conhecida? | O HAR fica velho ou não casa com o payload atual. |
request ou APIRequestContext |
Sim, no teste de API | O serviço aceita a requisição e entrega o schema esperado? | Requer ambiente controlado e credenciais de teste. |
O Playwright Mock APIs documenta os três primeiros caminhos. Para o quarto, o APIRequestContext permite chamar endpoints, preparar o ambiente e verificar o serviço sem passar pela interface. Essa divisão costuma produzir uma suíte menor e com falhas mais fáceis de localizar.
Não escolha HAR apenas porque gravar uma sessão parece rápido. Ele é bom quando o fluxo tem várias requisições estáveis e o arquivo pode ser revisado junto com o código. Para um caso de erro simples, um fixture explícito mostra melhor qual estado a pessoa está vendo e por que o teste passa.
Como criar um mock determinístico no Playwright?
O teste abaixo verifica uma tela que carrega recomendações de
/api/recommendations. Ele é ilustrativo: adapte a URL, o HTML e o contrato ao
seu projeto. A parte importante é registrar a rota antes de page.goto(), usar
um fixture pequeno e provar que a requisição esperada realmente passou pelo
interceptor.
// tests/recommendations.mock.spec.ts
import { test, expect } from "@playwright/test";
test("mostra recomendações quando a API responde com dados", async ({ page }) => {
let intercepted = false;
await page.route(/\/api\/recommendations\?userId=test-user$/, async (route) => {
intercepted = true;
await route.fulfill({
status: 200,
contentType: "application/json",
json: {
items: [{ id: "book-1", title: "Testing at the Boundary" }],
},
});
});
await page.goto("/recommendations?userId=test-user");
await expect(page.getByRole("heading", { name: "Testing at the Boundary" }))
.toBeVisible();
expect(intercepted).toBe(true);
});
O status e o contentType não estão ali para enfeitar o fixture. Eles deixam
explícito o que a aplicação recebe. Adicione casos separados para lista vazia,
resposta inválida e erro temporário. Não coloque todos os estados em um único
teste com condicionais: cada falha deve dizer qual comportamento deixou de
funcionar.
Se a tela depende de autenticação, mantenha o login no fixture de contexto e deixe o mock representar somente a API que o caso controla. Um JSON gigante copiado de produção mistura dados de setup, contrato e cenário. Comece com o menor objeto que prova a decisão visual e aumente-o somente quando a aplicação realmente lê outro campo.
Como usar route.fetch sem perder o sinal da API real?
route.fetch faz a requisição original e devolve uma resposta que pode ser
alterada antes de route.fulfill. O API de Route do Playwright
documenta esse caminho para cenários em que a chamada real é necessária, mas
um detalhe da resposta precisa ser controlado.
test("mostra o estado vazio mesmo com uma resposta real", async ({ page }) => {
await page.route("**/api/recommendations", async (route) => {
const response = await route.fetch();
const body = await response.json();
await route.fulfill({
response,
json: { ...body, items: [] },
});
});
await page.goto("/recommendations?userId=test-user");
await expect(page.getByText("No recommendations yet")).toBeVisible();
});
Esse teste tem uma pergunta diferente do primeiro. Ele deixa a API participar, mas força uma variação que pode ser difícil de produzir de forma segura no ambiente. Use-o para explorar a integração e o tratamento da resposta, não como substituto de um teste determinístico que precisa rodar offline.
Há um custo: o teste agora pode falhar por autenticação, rede, limite de uso ou
mudança no serviço. Por isso, deixe route.fetch em uma camada menor ou em uma
execução de integração. O caminho rápido da suíte deve continuar usando
route.fulfill para estados conhecidos.
Como reproduzir uma conversa com routeFromHAR?
HAR é uma boa escolha quando o fluxo depende de várias chamadas e o arquivo
gravado pode ser tratado como um artefato revisável. O Playwright recomenda
gravar o HAR, versioná-lo junto com o teste e desligar a atualização durante a
execução normal. A correspondência considera URL e método; em POST, o payload
também importa.
test("reproduz o fluxo gravado de recomendações", async ({ page }) => {
await page.routeFromHAR("./hars/recommendations.har", {
url: "**/api/recommendations",
update: false,
});
await page.goto("/recommendations?userId=test-user");
await expect(page.getByRole("heading", { name: "Testing at the Boundary" }))
.toBeVisible();
});
Revise o HAR como código. Remova cookies, tokens, dados pessoais e respostas que não pertencem ao cenário. Se o teste passa apenas porque um HAR antigo responde a uma URL ampla demais, você ganhou velocidade e perdeu diagnóstico. Uma verificação útil é fazer o caso falhar quando nenhuma entrada do arquivo corresponder, em vez de deixar a aplicação seguir com uma resposta inesperada.
Como provar que o mock não escondeu o contrato?
O caminho mais simples é separar as camadas. A suíte do navegador testa o
comportamento visível com route.fulfill. Uma suíte de API usa request ou um
APIRequestContext independente contra staging, sandbox ou um serviço local.
Se dois times evoluem o endpoint separadamente, um teste de contrato verifica
as expectativas do consumidor contra o provedor, como explica o PactFlow sobre
contract testing orientado pelo consumidor.
// tests/recommendations.contract.spec.ts
import { test, expect } from "@playwright/test";
test("o serviço de staging mantém o contrato de recomendações", async ({ request }) => {
const response = await request.get("/api/recommendations?userId=test-user");
expect(response.ok()).toBe(true);
const body = await response.json();
expect(body).toEqual(
expect.objectContaining({
items: expect.any(Array),
}),
);
});
Esse teste precisa de um baseURL de staging e de dados que possam ser
descartados. Não aponte uma suíte automática para produção só porque o endpoint
é público. Se não existe sandbox, coloque a chamada real atrás de uma etapa
manual ou crie um contrato explícito que o provedor possa verificar.
O resultado desejado é uma matriz pequena:
route.fulfillcobre estados da interface e roda em cada alteração.route.fetchou HAR cobre uma variação que precisa de uma conversa de rede mais parecida com a realidade.- O teste de API ou contrato verifica método, autenticação, schema e estado do serviço em uma execução separada.
Não conte os três como a mesma cobertura. Eles podem usar o mesmo nome de endpoint, mas respondem a perguntas diferentes.
Quando a dependência tem uma superfície própria, use também testes de contrato para um servidor MCP em TypeScript como referência de fronteira: o transporte e o schema precisam de uma prova separada do comportamento da tela.
Quais falhas tornam esse padrão enganoso?
O primeiro erro é registrar uma rota ampla demais. **/api/** pode capturar a
requisição que o teste deveria observar e devolver um sucesso para um endpoint
incorreto. Use uma expressão ou glob estreito e verifique método, query e corpo
quando esses dados fazem parte do contrato.
O segundo é esquecer que SSR e chamadas fora do navegador acontecem em outro
processo. page.route() intercepta o tráfego do contexto do browser. Ele não
corrige automaticamente uma chamada feita pelo servidor durante o render. Uma
discussão recente no r/Playwright sobre APIs de Server Components
chama atenção para essa fronteira. Para SSR, controle o ambiente do servidor,
use um proxy de teste ou teste a camada com uma ferramenta adequada.
O terceiro é deixar um service worker esconder a requisição. A documentação de
Network do Playwright explica que pode
ser necessário configurar serviceWorkers: "block" quando os eventos de rede
não aparecem como esperado. Primeiro confirme a topologia; depois escolha o
mock.
O quarto é transformar retry em aprovação. O guia de retries do Playwright
classifica como flaky o teste que falha na primeira execução e só passa depois
de uma nova tentativa. Aumentar retries pode preservar o pipeline, mas não
prova que o mock, o fixture ou o ambiente ficaram corretos.
Checklist para revisar um mock de API
Antes de aceitar um teste, responda às perguntas abaixo:
- O teste diz se está verificando a UI, a integração ou o contrato?
- A rota é registrada antes da navegação?
- O padrão da rota inclui o método, a URL e os parâmetros relevantes?
- O fixture contém apenas os campos que o cenário precisa?
- Há um caso de erro com status e corpo coerentes?
- O teste prova que a rota esperada foi interceptada?
- Existe outro teste que chama staging, sandbox ou o provedor do contrato?
- Cookies, tokens e dados pessoais ficaram fora do HAR e dos relatórios?
- Uma falha de rede aparece como falha de integração, e não como uma tela verde?
- O primeiro retry gera um sinal de flakiness em vez de ser escondido?
Se a resposta final for "não", o teste pode continuar útil. Apenas dê a ele um nome e uma posição na suíte que correspondam ao que ele realmente prova.
Perguntas frequentes
Mockar a API torna o teste E2E inútil?
Não. O mock mantém o navegador diante de estados difíceis de criar e torna a execução repetível. Ele não substitui um teste de integração ou contrato. A separação correta é usar o mock para o comportamento da UI e uma chamada independente para saber se o serviço real ainda aceita o acordo.
route.fetch é sempre melhor que route.fulfill?
Não. route.fetch preserva uma chamada real e pode herdar instabilidade, dados
externos e autenticação. route.fulfill é melhor para um caso determinístico,
como um erro 503 ou uma lista vazia. Escolha pelo sinal que o teste precisa, não
por parecer mais próximo de produção.
Um HAR substitui um ambiente de staging?
Não. HAR reproduz o tráfego que foi gravado e ajuda a testar o cliente sem rede. Ele não demonstra que o provedor atual aceita a requisição. Versione o HAR como fixture e mantenha uma verificação separada no staging, sandbox ou contrato do serviço.
Como mockar uma API usada durante SSR?
page.route() não alcança automaticamente chamadas feitas no servidor. Controle
a dependência no processo que faz o render, aponte-o para um serviço de teste ou
teste a API no nível do servidor. Depois use Playwright para verificar o HTML e
o comportamento que o navegador realmente recebe.
Fontes consultadas
- Playwright, "Mock APIs", consultado em 2026-08-11
- Playwright, "Best Practices", consultado em 2026-08-11
- Playwright, "Network", consultado em 2026-08-11
- Playwright, "Route", consultado em 2026-08-11
- Playwright, "APIRequestContext", consultado em 2026-08-11
- Playwright, "Retries", consultado em 2026-08-11
- PactFlow, "What is consumer-driven contract testing?", consultado em 2026-08-11