Nati Kamusher
Nati Kamusher

Reputation: 771

Dynamic Mock Data with MSW and Playwright: Synchronizing Across Threads

I am using Playwright for end-to-end testing and Mock Service Worker (MSW) for mocking API responses. My setup involves:

•   Playwright to test a web application. 
•   MSW to intercept network requests in the browser and serve mock data.

The Problem:

I need to dynamically customize the mock response for a specific API route (GET /user) on a per-test basis.

For example:

•   In an “Owner role” test, GET /user should return: { id: 'abc', role: 'owner' } 
•   In a “User role” test, the same route should return: { id: 'abc', role: 'user' } 

However, since MSW runs in the browser’s service worker thread, it operates independently of the main Playwright thread. I cannot directly update the handlers or mock data from Playwright during the test.

My Setup:

  1. Environment: • Playwright • MSW: • Node.js • Chromium
  2. MSW Handler Code: My MSW handler for the GET /user route is as follows:
import { rest } from 'msw';

const handlers = [
  rest.get('/user', (req, res, ctx) => {
    const mockData = window.mockData || {};
    const response = mockData['/user'] || { id: 'default', role: 'guest' };
    return res(ctx.json(response));
  }),
];

export { handlers };

This uses window.mockData to dynamically return mock responses.

  1. Playwright Test Code: I attempt to set the mock data before running the test using page.evaluate:
test('Owner role test', async ({ page }) => {
  // Set mock data dynamically
  await page.evaluate(() => {
    window.mockData = {
      '/user': { id: 'abc', role: 'owner' },
    };
  });

  await page.goto('/some-page');
  const response = await page.evaluate(async () => {
    const res = await fetch('/user');
    return res.json();
  });

  expect(response).toEqual({ id: 'abc', role: 'owner' });
});

Similarly, for the “User role” test, I set the mock data to { id: 'abc', role: 'user' }.

What I’ve Tried:

1.  Using page.evaluate:

This approach dynamically sets window.mockData within the browser context, which the MSW handler reads. While this works in isolated tests, I’m unsure if this is the best practice or if there’s a more robust method to manage this communication across threads. 2. Resetting MSW Handlers (resetHandlers): Since MSW operates in a separate thread, resetting handlers from Playwright (via server.resetHandlers) doesn’t directly affect the service worker thread. 3. Custom API Endpoint: I’ve considered adding a custom API route in MSW (POST /mock-config) to update the mock data dynamically, but this feels like overengineering.

What I Need:

I’m looking for the best practice or recommended approach to: 1. Dynamically update the mock data for specific routes during individual tests. 2. Ensure synchronization between Playwright, MSW, and the application. 3. Maintain clean isolation between tests to avoid cross-test contamination.

Additional Notes:

1.  If there’s a better way to manage mock data for MSW in Playwright, I’m open to refactoring.
2.  If using page.evaluate is considered acceptable, are there any pitfalls I should be aware of?

I’d appreciate detailed guidance or examples from anyone familiar with Playwright and MSW.

Let me know if I should clarify or expand on anything!

Upvotes: 0

Views: 284

Answers (1)

Leon
Leon

Reputation: 11

There is a package called playwright-msw that should solve your problem. You basically disable your default msw setup and instead pass the mock service worker it into the Playwright test setup. Then you can overwrite mocks on a test by test basis using worker.use.

Just be aware that this package hasn't been updated in a while and the maintainer is not responding to pull requests, so that might lead to issues in the future. So far it is still working for me though.

Upvotes: 0

Related Questions