QA Engineer Skills 2026QA-2026Network Mocking and API Testing

Network Mocking and API Testing

Playwright can intercept, modify, and mock network requests at the browser level — without a proxy server. This lets you test edge cases (server errors, slow responses, empty states) that are difficult or impossible to reproduce with a live backend, and it lets you combine UI tests with API verification in a single test.


Request Interception

page.route() intercepts requests matching a URL pattern and lets you fulfill, abort, or modify them.

Mocking API Responses

test('shows empty state when no products exist', async ({ page }) => {
  // Mock the API to return an empty array
  await page.route('**/api/products', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([]),
    });
  });

  await page.goto('/products');
  await expect(page.getByText('No products found')).toBeVisible();
});

test('handles server error gracefully', async ({ page }) => {
  await page.route('**/api/products', async (route) => {
    await route.fulfill({ status: 500 });
  });

  await page.goto('/products');
  await expect(page.getByRole('alert')).toHaveText(/something went wrong/i);
});

Modifying Requests

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

// Modify response data (e.g., inject a test flag)
await page.route('**/api/config', async (route) => {
  const response = await route.fetch();
  const json = await response.json();
  json.featureFlags.newCheckout = true;
  await route.fulfill({ response, body: JSON.stringify(json) });
});

Aborting Requests

// Block analytics and tracking in tests
await page.route('**/{analytics,tracking}/**', (route) => route.abort());

// Block images to speed up tests
await page.route('**/*.{png,jpg,jpeg,svg}', (route) => route.abort());

Waiting for Network Events

// Wait for a specific API response after triggering an action
const responsePromise = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Place Order' }).click();
const response = await responsePromise;
expect(response.status()).toBe(201);

// Wait for a request to be sent
const requestPromise = page.waitForRequest('**/api/search*');
await page.getByRole('searchbox').fill('laptop');
const request = await requestPromise;
expect(request.url()).toContain('q=laptop');

API Testing with request Fixture

Playwright's request fixture makes direct HTTP calls without a browser — ideal for API setup, teardown, and pure API testing.

test('API: create and retrieve a product', async ({ request }) => {
  // Create
  const createResponse = await request.post('/api/products', {
    data: { name: 'Widget', price: 9.99 },
  });
  expect(createResponse.status()).toBe(201);
  const product = await createResponse.json();

  // Retrieve
  const getResponse = await request.get(`/api/products/${product.id}`);
  expect(getResponse.status()).toBe(200);
  const retrieved = await getResponse.json();
  expect(retrieved.name).toBe('Widget');
});

Combining UI + API Tests

The most powerful pattern: use the API to set up data, verify through the UI, then validate via API.

test('user can update their profile', async ({ page, request }) => {
  // Setup: create user via API
  await request.post('/api/users', {
    data: { name: 'Alice', email: 'alice@test.com' },
  });

  // UI: update the profile
  await page.goto('/profile');
  await page.getByLabel('Name').fill('Alice Smith');
  await page.getByRole('button', { name: 'Save' }).click();
  await expect(page.getByRole('alert')).toHaveText('Profile updated');

  // Verify: check via API that the change persisted
  const response = await request.get('/api/users/alice');
  const user = await response.json();
  expect(user.name).toBe('Alice Smith');
});

Network Mocking Patterns

Pattern Use Case
Full mock Test UI in isolation from backend
Error simulation Verify error handling (500, 403, timeout)
Slow response Test loading states and spinners
Modified response Inject feature flags or test-specific data
Abort requests Block analytics, images, third-party scripts
Record & replay Snapshot real responses for deterministic tests

Simulating Slow Responses

test('shows loading spinner for slow API', async ({ page }) => {
  await page.route('**/api/reports', async (route) => {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    await route.fulfill({ status: 200, body: '[]' });
  });

  await page.goto('/reports');
  await expect(page.getByTestId('spinner')).toBeVisible();
  await expect(page.getByTestId('spinner')).not.toBeVisible({ timeout: 5000 });
});

Key Takeaways

  • page.route() intercepts requests at the browser level — no proxy server needed
  • Mock APIs to test edge cases: empty states, errors, slow responses, feature flags
  • Use request fixture for direct API calls — faster than UI for setup and verification
  • Combine UI + API testing: set up via API, interact via UI, verify via API
  • Block analytics and unnecessary resources to speed up test execution