Claude
Skills
Sign in
Back

network-mock-server

Included with Lifetime
$97 forever

Set up request interception to mock API responses: record real responses for baseline, replay them deterministically, modify response fields, add latency per endpoint, simulate errors, and persist mocks across navigation. Supports both Playwright page.route() and CDP Fetch domain for lower-level control.

Backend & APIs

What this skill does


# Network Mock Server

Create a flexible API mocking layer that intercepts network requests and returns
controlled responses. Record real API responses as a baseline, then replay,
modify, or replace them for testing. Mocks persist across page navigations
within the same browser context.

## When to Use

- Testing frontend behavior against specific API response shapes.
- Reproducing edge cases by modifying real API response fields.
- Simulating slow endpoints to test loading states and timeouts.
- Testing error handling by returning specific HTTP status codes per endpoint.
- Creating deterministic test environments that do not depend on backend availability.
- Recording API responses for offline development or snapshot testing.

## Prerequisites

- **Playwright MCP server** connected and responding (all `mcp__playwright__browser_*` tools available).
- **Chromium-based browser** required for CDP `Fetch.enable` (optional, for lower-level control).
- Target page must make API requests (XHR/fetch) that you want to intercept.

## Workflow

### Step 1 -- Navigate and Record Baseline API Responses

First, load the page normally and capture all API responses as a baseline.

```
browser_navigate({ url: "<target_url>" })
```

```
browser_wait_for({ time: 5 })
```

Capture all network requests made during page load:

```
browser_network_requests({ includeStatic: false })
```

### Step 2 -- Record Detailed API Responses

Install a response recorder that captures full response bodies for API calls.

```javascript
browser_run_code({
  code: `async (page) => {
    const recorded = [];

    // Listen to all responses
    page.on('response', async (response) => {
      const url = response.url();
      // Only record API calls, skip static assets
      if (url.includes('/api/') || url.includes('/graphql') ||
          response.request().resourceType() === 'xhr' ||
          response.request().resourceType() === 'fetch') {
        try {
          const body = await response.text();
          recorded.push({
            url: url,
            method: response.request().method(),
            status: response.status(),
            headers: response.headers(),
            contentType: response.headers()['content-type'] || null,
            body: body.substring(0, 50000),
            timestamp: Date.now()
          });
        } catch (e) {
          recorded.push({
            url: url,
            method: response.request().method(),
            status: response.status(),
            error: 'Could not read body: ' + e.message,
            timestamp: Date.now()
          });
        }
      }
    });

    // Store reference for later harvest
    page.__recordedResponses = recorded;

    // Reload to capture from scratch
    await page.reload({ waitUntil: 'networkidle' });

    return 'Response recorder installed and page reloaded';
  }`
})
```

### Step 3 -- Harvest Recorded Responses

```javascript
browser_run_code({
  code: `async (page) => {
    const recorded = page.__recordedResponses || [];
    return {
      totalRecorded: recorded.length,
      endpoints: recorded.map(r => ({
        method: r.method,
        url: r.url,
        status: r.status,
        contentType: r.contentType,
        bodySize: r.body ? r.body.length : 0,
        bodyPreview: r.body ? r.body.substring(0, 500) : null
      }))
    };
  }`
})
```

### Step 4 -- Set Up Mock Replay (Exact Replay)

Replay recorded responses for deterministic behavior.

```javascript
browser_run_code({
  code: `async (page) => {
    const recorded = page.__recordedResponses || [];
    if (recorded.length === 0) return { error: 'No recorded responses to replay' };

    // Build a lookup map: method+url -> response
    const mockMap = {};
    for (const r of recorded) {
      const key = r.method + ' ' + new URL(r.url).pathname;
      if (!mockMap[key]) mockMap[key] = r; // First response wins
    }

    // Clear any existing routes
    await page.unrouteAll();

    // Install mock routes
    await page.route('**/*', (route) => {
      const req = route.request();
      const key = req.method() + ' ' + new URL(req.url()).pathname;
      const mock = mockMap[key];

      if (mock) {
        route.fulfill({
          status: mock.status,
          contentType: mock.contentType || 'application/json',
          body: mock.body,
          headers: { 'x-mocked': 'true' }
        });
      } else {
        // Pass through non-mocked requests
        route.continue();
      }
    });

    return {
      mockedEndpoints: Object.keys(mockMap).length,
      endpoints: Object.keys(mockMap)
    };
  }`
})
```

### Step 5 -- Mock with Modified Responses

Modify specific fields in recorded responses (e.g., change user name, empty arrays,
alter counts).

```javascript
browser_run_code({
  code: `async (page) => {
    await page.unrouteAll();

    // Example: modify a specific endpoint's response
    await page.route('**/api/users**', (route) => {
      route.fulfill({
        status: 200,
        contentType: 'application/json',
        headers: { 'x-mocked': 'modified' },
        body: JSON.stringify({
          users: [
            { id: 1, name: 'Mock User 1', email: '[email protected]', role: 'admin' },
            { id: 2, name: 'Mock User 2', email: '[email protected]', role: 'user' }
          ],
          total: 2,
          page: 1
        })
      });
    });

    // Example: return empty collection
    await page.route('**/api/notifications**', (route) => {
      route.fulfill({
        status: 200,
        contentType: 'application/json',
        headers: { 'x-mocked': 'empty' },
        body: JSON.stringify({ notifications: [], unread: 0 })
      });
    });

    // Pass through everything else
    await page.route('**/*', (route) => {
      if (!route.request().url().includes('/api/users') &&
          !route.request().url().includes('/api/notifications')) {
        route.continue();
      }
    });

    return 'Modified mocks installed for /api/users and /api/notifications';
  }`
})
```

Reload and verify:

```javascript
browser_run_code({
  code: `async (page) => {
    await page.reload({ waitUntil: 'networkidle' });
    return 'Page reloaded with modified mocks';
  }`
})
```

```
browser_take_screenshot({ type: "png", filename: "mock-modified-response.png" })
```

### Step 6 -- Mock with Latency

Add artificial delay to specific endpoints to test loading states.

```javascript
browser_run_code({
  code: `async (page) => {
    await page.unrouteAll();

    await page.route('**/api/**', async (route) => {
      const url = route.request().url();

      // Add 3-second delay to specific endpoints
      if (url.includes('/api/search') || url.includes('/api/data')) {
        await new Promise(resolve => setTimeout(resolve, 3000));
      }

      // Add 1-second delay to all other API calls
      else {
        await new Promise(resolve => setTimeout(resolve, 1000));
      }

      // Continue to real server (with delay applied)
      route.continue();
    });

    return 'Latency mocks installed: 3s for search/data, 1s for other APIs';
  }`
})
```

```
browser_take_screenshot({ type: "png", filename: "mock-latency-loading.png" })
```

### Step 7 -- Mock Error Responses Per Endpoint

Return different error codes for different endpoints.

```javascript
browser_run_code({
  code: `async (page) => {
    await page.unrouteAll();

    const errorConfig = {
      '/api/auth': { status: 401, body: { error: 'Unauthorized', message: 'Token expired' } },
      '/api/users': { status: 500, body: { error: 'Internal Server Error', message: 'Database connection failed' } },
      '/api/upload': { status: 413, body: { error: 'Payload Too Large', message: 'File exceeds 10MB limit' } },
      '/api/search': { status: 429, body: { error: 'Too Many Requests', message: 'Rate limit exceeded. Retry after 60s', retryAfter: 60 } }
    };

    await page.route('**/api/**', (route) => {
      const pathname = new URL(route.request().url()).pathname;

 

Related in Backend & APIs