Mocking External APIs in Playwright Tests for Deterministic E2E Runs
Learn how to mock external API calls in Playwright using network request interception. This guide covers prerequisites, step‑by‑step procedure with code examples, expected checks, and recovery options to keep your E2E tests deterministic and fast.
09 Jul 2026, 17:13 UTC

Desired Outcome
Replace real HTTP responses with controlled mock data so that end‑to‑end tests run without depending on external services. This makes tests deterministic, fast, and able to assert specific UI states that would be hard to reproduce with a live backend.
Prerequisites
- Node.js ≥ 14
- Playwright Test installed as a dev dependency:
npm i -D @playwright/test - A test file using the Playwright Test runner (e.g.,
example.spec.ts) - Basic familiarity with
async/awaitin JavaScript or TypeScript
Procedure
Register a route handler before the page makes the target request. The handler intercepts matching URLs and fulfills them with a fabricated payload.
Step 1: Define the mock data
const mockUser = { id: 42, name: 'Test User', role: 'admin' };
Step 2: Intercept the API call in test.beforeEach
test.beforeEach(async ({ page }) => {
await page.route('**/api/user/**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockUser)
});
});
});
The glob pattern **/api/user/** matches any request whose path contains /api/user/. Adjust the pattern to your API structure.
Step 3: Trigger the UI action that consumes the mocked endpoint
test('displays mocked user name', async ({ page }) => {
await page.goto('/dashboard');
await page.click('[data-testid="load-profile"]');
// The click fires a fetch to /api/user/42 which is now intercepted
await expect(page.locator('.user-name')).toHaveText('Test User');
});
Step 4: Clean up after each test
test.afterEach(async ({ page }) => {
await page.unroute('**/api/user/**');
});
Removing the route prevents leakage into subsequent tests.
Expected Checks
- Assert that the mocked data appears in the DOM:
expect(page.locator('.user-name')).toHaveText('Test User') - Verify no network errors appear in the browser console.
- Optionally enable tracing (
npx playwright test --trace on) and inspect the trace viewer: the intercepted request should show a fulfilled entry with status 200 and the mocked JSON body.
Recovery Options
await page.unroute('**/api/user/**'); placed before the failing action.page.route registration; the test will then hit the actual backend.test.afterEach (as shown) to avoid cross‑test contamination.Limitations & Practical Verification
- Interception only works for requests initiated by the page context (fetch, XHR, navigation). Service workers, WebSocket frames, or requests made by browser extensions are not intercepted.
- Over‑scoping the glob (e.g.,
**/api/**) can unintentionally block assets like images or CSS, causing missing UI elements and false negatives. - Order matters: the route must be registered before the navigation or action that triggers the request. Placing
page.routeafterpage.gotooften misses the call and leads to flaky tests.
Quick verification: Run the test suite (npx playwright test). A passing test that asserts the mocked text confirms the interception worked. Open the generated trace (npx playwright show-trace trace.zip) and locate the network entry labeled “fulfilled” with your mocked status and body. Finally, remove the route handler and re‑run; the test should now fail or behave differently, proving the earlier success depended on the mock.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.