playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature]: Add Server Side mocking

Open Romarionijim opened this issue 1 year ago • 18 comments

🚀 Feature Request

What I would like to see and experience is for playwright to allow pure server side mocking in addition to the existing client side api mocking via routes - similar to Nock and Jest. With one command we can intercept the request of the backend api and then re-use the intercepted request in our tests to assert the mocked response data. We mock the endpoint once and re use across different tests in the same file.

Example

import {serverSideMock as mock} from '@playwright/test'

const scope = mock('https://pokeapi.co/api/v2')
 .get('/pokemon')
 .reply(200,{
    count:3000,
    next:"https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
    previous:"Random",
    results: [
        { name: 'fake1', url: 'https://pokeapi.co/api/v2/pokemon/1/' },
        { name: 'fake2', url: 'https://pokeapi.co/api/v2/pokemon/2/' },
        ],
    })

test('mock pokemon API response and assert count is 3000',{tag:['@POKE']}, async({request}) => {
  const response = await request.get(url);
  const resObj = await response.json();
  expect(resObj.count).toBe(3000)
})

test('mock should fail due to count mismatch',{tag:['@POKE']}, async({request}) => {
  const response = await request.get(url);
  const resObj = await response.json();
  expect(resObj.count).toBe(1500)
})


Motivation

Motivation is to complement Playwright and make it a real full stack test automation and not just UI - it already supports api testing and mocking, but mocking for the client side is not enough if we are working on a Rest API automation project using playwright - by adding the server side mocking, we Don't need to use other frameworks or libraries. we'll have one automation framework that provides a full stack solution for both the frontend UI and the backend.

Romarionijim avatar May 13 '24 16:05 Romarionijim

Great feature request! Some initial questions which help us to prioritise: Are you using a Meta-Framework like Next/Nuxt/SvelteKit/Remix etc? Are you using MSW? Are you using fetch() in the backend?

mxschmitt avatar May 13 '24 20:05 mxschmitt

Great feature request! Some initial questions which help us to prioritise: Are you using a Meta-Framework like Next/Nuxt/SvelteKit/Remix etc? Are you using MSW? Are you using fetch() in the backend?

I'm using those meta frameworks and i was looking for this feature too. Currently waiting for https://github.com/mswjs/msw/pull/1617 to be merged, but would be great if this was provided by Playwright. It also comes in handy with component testing: https://github.com/microsoft/playwright/issues/19399#issuecomment-1642171000

sand4rt avatar May 13 '24 20:05 sand4rt

@mxschmitt the frameworks that are used at the company I work at is Nest js for the backend and Angular on the frontend. I saw nock was being used for mocking.

Romarionijim avatar May 14 '24 08:05 Romarionijim

I would love to get this working for my Remix apps!

kennethvdberghe avatar May 30 '24 08:05 kennethvdberghe

I second this too.

david-mambou avatar Jul 15 '24 23:07 david-mambou

I can't stress enough how much this is needed! I'm loosing hours here trying to see how I can mock API request in my nextjs app.

I just upgraded the app from version 13 to 14 and now I'm installing another libraries and configurations to make this possible.

It should be as simple as route.fulfill() but for the backend... currently it's too much trouble for something almost any application that connects in a back-end API would need.

alestuber avatar Jul 26 '24 15:07 alestuber

Great feature request! Some initial questions which help us to prioritise: Are you using a Meta-Framework like Next/Nuxt/SvelteKit/Remix etc? Are you using MSW? Are you using fetch() in the backend?

In my case, I'm using fetch() from Next.js server components / SSR pages

mmersiades avatar Aug 29 '24 05:08 mmersiades

Upvote for this as well, it would be great to be able to use Playwright as a one-stop framework for all things testing. In my case, the application was using got.

esaari avatar Sep 21 '24 22:09 esaari

Very much wanted for me too! Without server side mocking I can't meaningfully scaffold my tests without coupling them to the data in my local/staging database.

I'm using NextJS and their extension of thefetch() api to call my backend services in server side components.

hammond756 avatar Sep 26 '24 19:09 hammond756

+1 Adding my vote for this feature.

kalm42 avatar Oct 01 '24 13:10 kalm42

Also in dire need of this feature for NUXT app

owaisahmad-dev avatar Oct 17 '24 08:10 owaisahmad-dev

Joining the fun, this would REALLY help.

ofirpardo-artlist avatar Nov 21 '24 08:11 ofirpardo-artlist

@mxschmitt I find it very necessary, nowadays I am forced to use msw or mockserver/wiremock with their client libraries

hernaanm avatar Dec 20 '24 19:12 hernaanm

I really need this feature

lubiah avatar Dec 28 '24 19:12 lubiah

would love this.

Wasifz9 avatar Jan 08 '25 20:01 Wasifz9

Yes, we need this desperately!

rileyjhardy avatar Feb 11 '25 17:02 rileyjhardy

Ditto. Cost us HOURS...

jpowell avatar Feb 11 '25 18:02 jpowell

Until it's natively supported, I've developed an experimental tool request-mocking-protocol, that allows to mock server-side requests in Playwright. Example code:

test('mock pokemon API response and assert count is 3000', async({ page, mockServerRequest }) => {
  // set up server-side mock
  await mockServerRequest.GET('https://pokeapi.co/api/v2, {
    body: { count: 3000 }
  });

  await page.goto('/');

  await expect(page).toContainText('3000');
});

The fixture mockServerRequest is defined as:

import { test as base } from '@playwright/test';
import { MockClient } from 'request-mocking-protocol';

export const test = base.extend<{ mockServerRequest: MockClient }>({
  mockServerRequest: async ({ context }, use) => {
    const mockClient = new MockClient();
    mockClient.onChange = async (headers) => context.setExtraHTTPHeaders(headers);
    await use(mockClient);
  },
});

Feel free to try and share the feedback in the repo.

vitalets avatar Mar 11 '25 10:03 vitalets

Hi all, We've also hit a point where we would like to mock server-side data requests, as playwright currently only supports client-side intercepting. We currently have a POC using msw, but would love to have a built-in solution. Was thinking of a kind of interceptor/proxy which the app can point to;

Image

When running the e2e tests, the app will use a url pointing to a playwright interceptor/proxy server, this switching can be done through a environment variable. (handled in the app) In the playwright e2e tests, the user can set mock data for certain endpoints of the interceptor/proxy server, which this server will serve. Once a page of the app is visited, it make a server-side fetch to the interceptor/proxy server, fetching the mock data and display the mock data in a server-side rendered page.

(extra: In case the endpoint is not mocked, it can be forwarded to the actual server)

Netail avatar Oct 29 '25 15:10 Netail

+1 for making this feature a priority! The lack of server-side mocking in Playwright is a serious problem for teams using SSR apps. Native support will save us hundreds of hours of engineering time and make Playwright a very convenient solution for writing UI automated tests. Please consider upgrading #30766.

mrAnfield avatar Dec 23 '25 08:12 mrAnfield

For anyone needing this. I implemented something that allow me to use like this in the tests:

Image

function is:

import { serializeRequest } from '@/libs/utils';
import micromatch from 'micromatch';

export function mockServerApi({ next, url, body = {}, status, method, headers = {} }: any) {
  let originalRequest;
  let resolveRequest;
  const requestPromise = new Promise((resolve, reject) => {
    resolveRequest = resolve;
  });

  const defaultStatus = {
    GET: 200,
    POST: 201,
    PUT: 200,
    PATCH: 200,
    DELETE: 204
  };

  next.onFetch(async (request) => {
    const methodMatches = !method || request.method.toUpperCase() === method.toUpperCase();

    const urlMatches = (requestUrl) => {
      if (typeof url === 'function') {
        return url(requestUrl);
      } else if (url instanceof RegExp) {
        return url.test(requestUrl);
      } else if (typeof url === 'string' || url instanceof String) {
        return micromatch.isMatch(requestUrl, url);
      } else {
        throw new Error(`Invalid type for URL for match. Allowed values are: Function, RegExp, String.`);
      }
    };

    if (urlMatches(request.url) && methodMatches) {
      originalRequest = await serializeRequest(request);
      resolveRequest(originalRequest);

      const responseStatus = status ?? defaultStatus[request.method] ?? 200;

      return new Response(JSON.stringify(body ?? {}), {
        status: responseStatus,
        headers: {
          'Content-Type': 'application/json',
          ...headers
        }
      });
    }
  });

  const response: any = {
    getRequest: () => requestPromise // It needs to return a getter function here, otherwise the value is still undefined since next.onFetch wasn't called yet. It was just registered until the mocked request actually happens.
  };

  return response;
}

I also needed to create a fixture using nextjs experimental testmode for proxy nextjs experimental testmode for proxy:

import { test as base } from 'next/experimental/testmode/playwright';

/**
 * Custom test fixture with strict mocking enabled by default.
 * This ensures all network requests must be explicitly mocked or they will throw an error.
 *
 * This helps:
 * - Catch unexpected API calls
 * - Prevent flaky tests from real network requests
 * - Ensure complete mocking coverage
 * - Make tests faster and more reliable
 */
export const test = base.extend({
  next: async ({ next }, use) => {
    next.onFetch((request) => {
      const url = request.url;
      const method = request.method;

      // Throw error for unmocked requests
      throw new Error(
        `❌ Unmocked backend request detected!\n` +
          `   Method: ${method}\n` +
          `   URL: ${url}\n\n` +
          `   Add a mock for this request in your test using:\n` +
          `   next.onFetch((request) => {\n` +
          `     if (request.url.includes('${new URL(url).pathname}')) {\n` +
          `       return new Response(JSON.stringify({ /* your mock data */ }), {\n` +
          `         headers: { 'Content-Type': 'application/json' }\n` +
          `       });\n` +
          `     }\n` +
          `     throw new Error(\`Unmocked: \${request.url}\`);\n` +
          `   });\n`
      );
    });

    await use(next);
  }
});

export { expect } from 'next/experimental/testmode/playwright';
```

This fixture make tests throw an error if any real API request isn't mocked, which helps.

Hope this helps someone else!

alestuber avatar Dec 23 '25 21:12 alestuber