PracticeQAAutomation logoPracticeQAAutomation

Network Mocking

Practice faking the server: errors, slow responses, timeouts, empty lists, broken data and dropped connections — with Playwright's page.route and Cypress's cy.intercept. Every recipe below is run against this page in our test suite.

The app under test

A product list that loads from /mock/products.json (and a greeting from /mock/profile.json). It shows a spinner while loading, and a different message for every kind of failure — that's what you'll fake with mocks.

Hi…

Loading products…

…

Show code for

1. Fake a server error, then recover

The first call returns 500, the next one goes through to the real server. Check the error message, click Try again, and count the calls. Tip: scope role=alert to the app — Next.js adds its own hidden alert region to every page, so a page-wide getByRole('alert') finds two.

Playwright
let calls = 0;
await page.route('**/mock/products.json*', async (route) => {
  calls++;
  if (calls === 1) return route.fulfill({ status: 500, body: 'Internal Server Error' });
  return route.continue();                       // let the real request through
});
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('The server had a problem (500)');
await page.getByRole('button', { name: 'Try again' }).click();
await expect(page.getByTestId('net-item')).toHaveCount(8);
expect(calls).toBe(2);

2. Slow response — check the loading state

Delay the response by 2 seconds so the spinner is guaranteed to show, then check it disappears when the data arrives.

Playwright
await page.route('**/mock/products.json*', async (route) => {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  await route.continue();
});
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-loading')).toBeVisible();
await expect(page.getByTestId('net-item')).toHaveCount(8);   // auto-waits up to 5 s
await expect(page.getByTestId('net-loading')).toBeHidden();

3. Empty list

Return an empty array and check the empty state — a case that's hard to set up on a real server.

Playwright
await page.route('**/mock/products.json*', (route) => route.fulfill({ json: [] }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-empty')).toHaveText('No products found.');
await expect(page.getByTestId('net-item')).toHaveCount(0);

4. Your own test data (two endpoints)

Replace both the greeting and the product list with data you control, so the test never depends on what's on the server.

Playwright
await page.route('**/mock/profile.json', (route) =>
  route.fulfill({ json: { name: 'Test User', plan: 'Free' } }));
await page.route('**/mock/products.json*', (route) =>
  route.fulfill({ json: [{ id: 1, name: 'Mocked Phone', price: 1, category: 'electronics' }] }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-profile')).toHaveText('Hi, Test User · Free plan');
await expect(page.getByTestId('net-item')).toHaveCount(1);
await expect(page.getByTestId('net-item')).toContainText('Mocked Phone');

5. Network failure (offline)

Abort the request as if the connection dropped. The app should say it can't reach the server — not crash.

Playwright
await page.route('**/mock/products.json*', (route) => route.abort('internetdisconnected'));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText("Can't reach the server");

6. Timeout — without waiting 5 seconds

The app gives up after 5 seconds. Never answer the request, and move the page's clock forward instead of really waiting.

Playwright
await page.clock.install();                                   // control the page's timers
await page.route('**/mock/products.json*', () => {});          // never respond
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-loading')).toBeVisible();
await page.clock.runFor(6000);                                 // jump 6 seconds ahead
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('taking too long');

7. Check the request the app sends

Change the category and prove the app asked for the right thing — here, the category query parameter.

Playwright
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-item')).toHaveCount(8);
const requestPromise = page.waitForRequest((r) => r.url().includes('/mock/products.json?'));
await page.getByLabel('Category').selectOption('books');
const request = await requestPromise;
expect(request.method()).toBe('GET');
expect(new URL(request.url()).searchParams.get('category')).toBe('books');
await expect(page.getByTestId('net-item')).toHaveCount(3);

8. Change part of a real response

Let the real request happen, then edit one field before the app sees it — useful for edge values like a price of ₹1.

Playwright
await page.route('**/mock/products.json*', async (route) => {
  const response = await route.fetch();            // the real response
  const products = await response.json();
  products[0].price = 1;                           // edit just one field
  await route.fulfill({ response, json: products });
});
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-item')).toHaveCount(8);
await expect(page.getByTestId('net-item').first()).toContainText('₹1');

9. Status codes & headers (401, 429 + Retry-After)

Each status should give the user a clear message. The 429 message reads the Retry-After header.

Playwright
await page.route('**/mock/products.json*', (route) => route.fulfill({ status: 401 }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('Your session has expired');

await page.unroute('**/mock/products.json*');
await page.route('**/mock/products.json*', (route) =>
  route.fulfill({ status: 429, headers: { 'Retry-After': '30' }, body: '' }));
await page.getByRole('button', { name: 'Try again' }).click();
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText('try again in 30 seconds');

10. Broken or unexpected data

Send JSON that doesn't parse, or JSON in the wrong shape. A robust app shows a friendly message instead of a blank page.

Playwright
await page.route('**/mock/products.json*', (route) =>
  route.fulfill({ status: 200, contentType: 'application/json', body: '{ not valid json' }));
await page.goto('/dynamic/network-mocking');
await expect(page.getByTestId('net-app').getByRole('alert')).toContainText("couldn't read");

await page.unroute('**/mock/products.json*');
await page.route('**/mock/products.json*', (route) => route.fulfill({ json: { items: 'wrong shape' } }));
await page.getByRole('button', { name: 'Try again' }).click();
await expect(page.getByTestId('net-last')).toContainText('unexpected shape');