Skip to content

Can't overwrite handlers from within test #21

Closed

Description

@kleinfreund

Minimal reproduction: https://github.com/kleinfreund/msw-playwright-reproduction

I wrote a basic test that only passes if an msw handler set-up in that test is applied instead of the default handler used by the application:

import { expect } from "@playwright/test";
import { http, HttpResponse } from "msw";

import { test } from "../playwright.setup.js";

test("has expected list items", async ({ network, page }) => {
  network.use(
    http.get(
      "/api/list",
      () => (HttpResponse.json([
        { name: "Alice" },
      ])),
    ),
  );

  await page.goto("/");

  const items = await page.getByRole("listitem");
  await expect(items).toHaveCount(1);
});

The setup file:

import { createNetworkFixture } from "@msw/playwright";
import { test as testBase } from "@playwright/test";

import { handlers } from "./mocks/handlers.js";

export const test = testBase.extend({
  network: createNetworkFixture({ initialHandlers: handlers }),
});

And the application:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>msw-playwright-reproduction</title>
  </head>
  <body>
    <ul></ul>

    <script type="module">
      const { worker } = await import("./mocks/browser.js");
      await worker.start();

      const response = await fetch("/api/list")
      const items = await response.json()
      const list = document.querySelector("ul")
      list.innerHTML = items.map((item) => `<li>${item.name}</li>`).join("")
    </script>
  </body>
</html>

I skipped any sort environment variable switch for the sake of keeping the reproduction minimal. In a real application, the first two script lines would only run during tests or development.

The test fails (run with npx playwright test) but I expect it to pass. Symptomatically, I'd say the application just never sees handlers added in tests, but that's just an assumption.

What am I doing wrong?

Activity

  1. maxcell commented on Sep 10, 2025

    @maxcell

    I was investigating something similar and saw your issue opened here! It is a small issue to fix fortunately!

    In your application code you are putting the browser integration (worker.start()) AND in your playwright code there is an integration (@msw/playwright) and I think they are colliding. The browser integration (index.html) is taking priority. Take out the worker start up script and the start function and it should all be working afterwards!

  2. kleinfreund commented on Sep 10, 2025

    @kleinfreund
    ContributorAuthor

    In your application code you are putting the browser integration (worker.start()) AND in your playwright code there is an integration (@msw/playwright) and I think they are colliding. The browser integration (index.html) is taking priority. Take out the worker start up script and the start function and it should all be working afterwards!

    You're correct that this fixes the test, but it also breaks the development environment of my application: it no longer uses msw.

    I can fix this by using an environment variable VITE_API_MODE to control when to start the msw worker. It would know the values "real" | "mocked-by-tests" | "mocked-by-app" of which "real" would be the default (i.e. the production case). The "mocked-by-app" value would be used during development and also during node-based testing (e.g. via vitest) where I don't run into this problem. Specifically the Playwright tests would use "mocked-by-tests". It might as well use "real", but one might as well do it right.

    Only I was hoping for an integration that has me not mess around with my application setup.


    For maintainers: I'm fine with this being closed if that's supposed to work like this. I'm mainly interested in arriving at a sort of text book setup that I can make use of in multiple projects. That being said, would it make sense to update https://github.com/mswjs/examples so that the examples have a test that overwrites a default handler for testing purposes while maintaining a development environment that itself uses mocked API calls? If yes and if that can be achieved in a sensible manner (ideally for most if not all examples), I'd be happy to contribute some of this (I'm familiar with vitest/jest/cypress/playwright).

  3. kettanaito commented on Sep 10, 2025

    @kettanaito
    Member

    Thanks for opening this, @kleinfreund. @maxcell is correct, you need to choose a single integration. You can keep your app integration, just make it conditional. For tests, I'd recommend @msw/playwright for now so you don't have to deal with Playwright limitations (one of which is causing this issue as well).

    Alternatively, you can disable service workers in Playwright tests altogether, which is something Playwright actively recommends due to the limitations of their request interception algorithm not working well if there's a service worker controlling the page.

    Completely agree that the official examples using Playwright have to be updated to use this package! Any help there would be largely appreciated.

  4. kleinfreund commented on Sep 10, 2025

    @kleinfreund
    ContributorAuthor

    Even just adding @msw/playwright to the respective example proves difficult as Playwright continues to report a Error: test file "example.test.ts" should not import test file "playwright.setup.ts" error despite no testMatch setting matching the setup file.

    playwright.config.ts:

    import { defineConfig } from '@playwright/test'
    
    export default defineConfig({
      webServer: {
        command: 'pnpm dev --port 50789',
        url: 'http://localhost:50789',
      },
      use: {
        baseURL: 'http://localhost:50789',
        launchOptions: {
          devtools: true,
        },
      },
      projects: [
        {
          name: 'setup',
          testMatch: 'playwright.setup.ts',
        },
        {
          name: 'tests',
          dependencies: ['setup'],
        },
      ],
    })

    Nothing seems to help: moving the tests into a tests directory and configured testDir, explicitly setting testMatch to not match the setup file, renaming the setup file to something else (e.g. setup.ts). In isolation, I can't reproduce this behavior in Playwright either so it doesn't immediately seem like a bug on their end. This ... should just work. For reference: https://playwright.dev/docs/test-global-setup-teardown

    I'm quite confused, but it's getting late, too.

  5. kettanaito commented on Sep 10, 2025

    @kettanaito
    Member

    Why use test file at all? I might take a look at this when I have a minute. Thanks for investigating!

  6. kleinfreund commented on Sep 11, 2025

    @kleinfreund
    ContributorAuthor

    Why use test file at all? I might take a look at this when I have a minute. Thanks for investigating!

    Even though it's not working, yet, I've put up my preliminary work in mswjs/examples#131. The TL;DR is that with Playwright's recommended way of adding a global setup file, the setup file, from which we need to import the modified test function as per @msw/playwrights instructions, becomes itself a test file (as far as Playwright is concerned) and so it refuses to run the tests.

    But let's continue any discussion on this in that pull request.

  7. kleinfreund commented on Sep 11, 2025

    @kleinfreund
    ContributorAuthor

    For posterity, the issue with the unexpected 'should not import test file' error described in #21 (comment) happens, seemingly, when the setup file is in testDir (see bug report: in microsoft/playwright#37396).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions