Playwright Network Mocking: Intercept and Mock API Requests

Playwright network mocking lets you intercept API calls and return controlled responses for faster, more reliable tests. Eliminate flaky failures caused by slow or unstable backends.

Attachment Details Playwright-Network-Mocking_-Intercept-and-Mock-API-Requests

Playwright network mocking lets you intercept HTTP requests during test execution and return controlled responses instead of hitting real APIs. If your end-to-end tests break because a backend was slow, a third-party service returned unexpected data, or a staging environment went down, the test didn't fail because of your code. It failed because of something outside your control.

This guide covers how playwright network mocking works, when to use it, how to set it up with practical examples, and how to debug failures when mocked responses don't behave the way you expect.

If you want a quick reference for Playwright commands alongside this guide, keep that handy.

Why Is Playwright Network Mocking Essential for Reliable Tests?

Playwright network mocking is essential because end-to-end tests that depend on live APIs are inherently fragile. A single backend deployment, a rate limit, or a slow database query can turn a passing test suite into a wall of red failures that have nothing to do with your frontend code.

Here's what actually happens without network mocking:

  • Your test clicks a button that triggers an API call
  • The API returns a 500 error because staging is having a bad day
  • The test fails, your CI pipeline blocks
  • A developer spends 15 minutes investigating, only to find the test itself was fine

Multiply that across a team running hundreds of tests per day, and you're looking at hours of wasted time every week. Network mocking removes this dependency entirely. Instead of calling a live API, Playwright intercepts the request and returns exactly the data your test expects.

Teams that mock their API layer in Playwright tests typically see three immediate benefits:

Benefit What Changes
Faster execution No waiting on real HTTP round-trip
Fewer false failures Infrastructure issues don't break tests
Better edge case coverage Easy to test timeouts, 404s, malformed payloads

Tip: The trade-off is real, though. Mocked tests don't catch actual API contract changes. The best approach is layered: mock most API calls for speed and reliability, then keep a small set of smoke tests that hit real endpoints before every release.

How Does Playwright Intercept and Mock API Requests?

Definition: Playwright network mocking is the process of using page.route() to register a handler that intercepts matching HTTP requests before they leave the browser, letting you fulfill them with fake data, modify real responses, or block them entirely. No application code changes needed.

Playwright intercepts API requests using the page.route() method, which registers a handler that runs before any matching network request leaves the browser. You provide a URL pattern, and Playwright gives you a Route object to decide what happens next.

The interception happens at the browser level, not the application level. Your frontend makes a normal fetch or XHR call, Playwright catches it mid-flight, and returns whatever response you define. From the application's perspective, it looks like a real API response.

There are three core operations you can perform on an intercepted route:

Operation What It Does When to Use
route.fulfill() Replaces response with mock data. Real API never called. Testing against controlled data
route.continue() Lets request through, optionally modifying headers/URL Adding auth headers, changing endpoints
route.abort() Blocks the request entirely Testing UI behavior on network failure

Fulfill is the most common. Here's a basic example:

example.spec.js
// Mock a user list endpoint with static data
await page.route('**/api/users', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({
      users: [{ id: 1, name: 'Alice' }]
    })
  });
});

Continue lets the real request go through but modifies it first:

example.spec.js
// Add an auth header to every API request
await page.route('**/api/**', async (route) => {
  await route.continue({
    headers: {
      ...route.request().headers(),
      'Authorization': 'Bearer test-token-123'
    }
  });
});

Abort blocks the request completely:

example.spec.js
// Simulate a network failure on image loads
await page.route('**/*.png', route => route.abort('failed'));

Note: You can also intercept a real response and modify it before the browser processes it. This is called response patching, great when you need mostly real data with one small change.

example.spec.js
// Fetch the real response, then add a test item
await page.route('**/api/products', async (route) => {
  const response = await route.fetch();
  const json = await response.json();
  json.push({ id: 99, name: 'Test Product' });
  await route.fulfill({ response, json });
});

Tip: The URL matching supports glob patterns. **/api/users matches any URL ending in /api/users. For more complex matching, pass a regular expression or a predicate function instead of a string.

Setting Up Playwright Network Mocking Step by Step

Important: Always set up your route handlers before calling page.goto(). If you register routes after navigation, early requests like initial data fetches will slip through unmocked. This is the #1 setup mistake teams make.

Step 1 - Register Routes Before Navigation

Always set up your route handlers before calling page.goto(). If you register routes after navigation, early requests like initial data fetches will slip through unmocked.

example.spec.js
// Correct: route registered before navigation
await page.route('**/api/config', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ theme: 'dark' })
  });
});
await page.goto('https://myapp.com');

Step 2 - Choose Page-Level or Context-Level Mocking

This decision affects how broadly your mocks apply:

page.route() applies only to that specific page. Use for test-specific overrides.

context.route() applies to every page in the browser context, including new tabs and popups. Use for consistent auth responses or feature flags.

example.spec.js
// Context-level: mock auth for all pages
await context.route('**/api/auth/me', async (route) => {
  await route.fulfill({
    body: JSON.stringify({ id: 1, role: 'admin' })
  });
});

Tip: Use context-level mocking for things like authentication tokens and feature flags that every page needs. Use page-level mocking for test-specific data that only one test cares about.

Step 3 - Organize Mock Data in Separate Files

Don't inline large JSON payloads inside your test files. Store mock data in a fixtures or mocks directory and import it:

terminal
tests/
  mocks/
    users.json
    products.json
  specs/
    dashboard.spec.ts
    checkout.spec.ts

This keeps tests readable and makes mock data reusable across test files.

Step 4 - Use Helper Functions for Common Patterns

If multiple tests need the same mocked endpoints, create a setup function:

helpers/mock-setup.ts
// helpers/mock-setup.ts
export async function setupStandardMocks(page) {
  await page.route('**/api/users', route =>
    route.fulfill({ body: JSON.stringify(usersData) })
  );
  await page.route('**/api/config', route =>
    route.fulfill({ body: JSON.stringify(configData) })
  );
}

This reduces duplication and makes it easy to update mocks in one place.

Step 5 - Clean Up Routes Between Tests

Important: Playwright doesn't automatically remove route handlers between tests in the same file. Use page.unrouteAll() or set up fresh routes in beforeEach to avoid mock leakage between tests.

When Should You Mock vs. Hit Real APIs?

Not every test should use mocked responses. Here's a quick decision table:

Test Type Mock APIs? Why
Component behavior tests Yes Testing UI logic, not backend
Error handling and edge cases Yes Hard to trigger with real APIs
Performance-sensitive CI runs Yes Eliminates network latency
Pre-release smoke tests No Final check against real services
API contract validation No Confirms frontend/backend alignment

Tip (80/20 Rule): Mock about 80% of your API calls for speed and reliability. Keep 20% hitting real endpoints for confidence. This balance gives you fast CI feedback without losing sight of real integration issues.

When you're testing UI behavior like form rendering, error messages on 500 responses, or loading states, those are UI concerns. The API data is just an input. Control it with mocks.

Hit real APIs when you're validating integration points: smoke tests before a release, contract tests that verify your frontend and backend agree on data shapes, and any test where the goal is confirming the real API works as expected.

How Network Mocking Helps Debug Test Failures

When a mocked test fails, debugging gets simpler because you've eliminated one entire category of problems. You know the API response was exactly what you defined. So the failure is either in your test logic, your application code, or your mock data.

But when tests with real API calls fail, the first question is always: "Was it the API or the app?" That's where debugging time explodes.

Playwright network mocking helps with debugging in a few specific ways:

Simulate exact failure scenarios. Want to test what happens when the API returns a 503? Mock it. Want to see how your UI handles a 2-second delay? Add a timeout to the mock.

Log intercepted requests. Add console output to your route handlers to see exactly what URLs are being called, what headers are sent, and what response data your test provides.

Inspect traces. The Playwright Trace Viewer records all network activity, including mocked responses. The Network tab shows every intercepted request with its status code, timing, and payload.

Here's how to simulate a slow API response:

example.spec.js
// Simulate a slow API response
await page.route('**/api/dashboard', async (route) => {
  await new Promise(resolve => setTimeout(resolve, 3000));
  await route.fulfill({
    status: 200,
    body: JSON.stringify({ data: [] })
  });
});

For teams running hundreds of Playwright tests in CI, tracking which failures are caused by bad mocks versus real application bugs becomes its own challenge.

Playwright Network Mocking in CI/CD Pipelines

Mocking network requests becomes even more valuable in CI/CD environments where tests need to be fast, stable, and independent of external services.

In local development, a flaky API response is annoying. In CI, it blocks your entire deployment pipeline. Teams running Playwright tests in GitHub Actions, GitLab CI, or Jenkins often deal with a frustrating pattern: tests pass locally but fail in CI because the test environment can't reliably reach the same APIs.

Playwright network mocking fixes this by removing the dependency entirely. Your CI tests run against mock data that lives in your repository, right next to the test code.

Here are the CI-specific best practices worth following:

Keep mock data in version control. Store JSON fixtures alongside your tests so they're always in sync with the code. When an API contract changes, update the mock data in the same PR.

Use HAR files for complex scenarios. Playwright can record real API traffic into HAR (HTTP Archive) files and replay them during tests.

Monitor mock drift. Schedule periodic checks where a subset of tests runs against real endpoints to verify mocks still reflect reality.

Here's how HAR replay works:

example.spec.js
// Replay API calls from a recorded HAR file
await page.routeFromHAR('tests/fixtures/api.har', {
  url: '**/api/**',
  update: false
});

Tip: Set update: true when you want to refresh the HAR file with real responses, then switch back to update: false for normal runs. This keeps mock data current without rewriting fixtures manually..


Important (Mock Drift Warning): Over time, mock data can silently diverge from your actual API. Schedule a monthly CI job that runs your critical tests against real endpoints. If those tests fail but your mocked tests pass, your mocks are stale.

For teams that want visibility into how tests perform across CI runs, TestDino tracks results in one place and shows historical trends. It makes it easy to spot when mock drift starts causing problems or when tests behave differently in CI compared to local runs.

Common Mistakes to Avoid

After working with teams that use playwright network mocking at scale, certain mistakes come up again and again:

Mistake What Goes Wrong Fix
Over-broad URL patterns **/** intercepts CSS, images, scripts Match only specific API endpoints
Stale mock data Mock has 5 fields, real API returns 8 Monthly audits against live schema
Ignoring HTTP methods Mock returns user list for POST requests too Check route.request().method()
Skipping error states Only happy paths tested Mock 400, 403, 500, and timeout responses
Mock leakage Routes from one test bleed into the next Use beforeEach and afterEach cleanup

Here's how to handle different HTTP methods properly:

example.spec.js
await page.route('**/api/users', async (route) => {
  if (route.request().method() === 'GET') {
    await route.fulfill({ body: JSON.stringify(usersList) });
  } else if (route.request().method() === 'POST') {
    await route.fulfill({ status: 201, body: '{"id": 42}' });
  } else {
    await route.continue();
  }
});

Important: Always add an else fallback that calls route.continue(). If you don't handle a method, the request hangs forever and your test times out with no useful error message.

Conclusion

Playwright network mocking is one of the most practical techniques for building fast, reliable end-to-end tests. It removes the biggest source of flakiness in most test suites — dependency on live APIs — and gives you full control over the data your application sees during tests.

The key takeaways: register routes before navigation, keep mock data organized in separate files, choose between page-level and context-level mocking based on scope, and always maintain a small set of tests that hit real APIs for integration confidence. In CI, use HAR files for complex scenarios and schedule regular checks to prevent mock drift.

Getting mocking right is half the battle. The other half is understanding what your test results actually mean across hundreds of runs. TestDino gives Playwright teams AI-powered failure classification, flaky test detection, and historical trend analysis so debugging stays fast even as your test suite grows.

FAQ

Can you mock WebSocket connections with Playwright?
Yes. Playwright supports WebSocket mocking through page.routeWebSocket(). You can intercept WebSocket connections, send custom messages, and simulate server behavior without connecting to a real WebSocket server.
Does playwright network mocking work with all HTTP methods?
Yes. The page.route() handler intercepts all HTTP methods, including GET, POST, PUT, PATCH, and DELETE. Use route.request().method() inside the handler to return different responses based on the method.
How do you keep mock data in sync with real API changes?
Use HAR recording with Playwright's routeFromHAR() to periodically capture real API responses. Set update: true to refresh fixtures, then switch back to update: false for test runs. Schedule monthly audits of mock data against live endpoints.
Should you mock APIs in performance tests?
No. Performance tests should hit real APIs to measure actual response times and network behavior. Mocking hides latency issues that affect real users. Use mocking only for functional and UI behavior tests where API speed is not the thing you're measuring.
What is the difference between page.route() and context.route()?
page.route() intercepts requests for a single page only. context.route() intercepts requests across all pages in the browser context, including new tabs and popups. Use context-level mocking for shared concerns like authentication and page-level mocking for test-specific overrides.
Savan Vaghani

Product Developer

Frontend Developer at TestDino, where he builds the interfaces that help engineering and QA teams make sense of their Playwright test results. He works primarily with React, TypeScript, and Next.js to create clean, developer-friendly dashboards for test analytics, failure classification, and CI pipeline health.

At TestDino, he’s responsible for the product’s frontend architecture, user experience, and ensuring every feature feels intuitive from the first click. He’s also involved in building out the platform’s onboarding experience and GitHub integration interfaces.

Get started fast

Step-by-step guides, real-world examples, and proven strategies to maximize your test reporting success