Skip to content

Lifecycle change since MSW 2.13 #2706

@ghostd

Description

@ghostd

Prerequisites

Environment check

  • I'm using the latest msw version
  • I'm using Node.js version 20 or higher

Browsers

Chromium (Chrome, Brave, etc.)

Reproduction repository

https://github.com/mswjs/msw

Reproduction steps

Hi

Here is a test file to reproduce the case:

import { HttpResponse, http } from "msw"
import { setupWorker, type SetupWorker } from "msw/browser"
import { useState } from "react"
import { expect, test as testBase } from "vitest"
import { userEvent } from "vitest/browser"

import { render } from "vitest-browser-react"

/**
 * Minimal reproduction for the MSW browser lifecycle race observed with MSW 2.13.x:
 * starting the worker for each test and stopping it in teardown can leave a window
 * where the next test's requests bypass the worker and hit Vite directly.
 *
 * Expected repro:
 * - Running this whole file fails on the second test with a real 404.
 * - Running only the second test passes.
 */

const server = setupWorker()

interface WorkerFixtures {
    worker: SetupWorker
}

const it = testBase.extend<WorkerFixtures>({
    worker: [
        async ({}, use) => {
            await server.start({
                quiet: true,
            })

            await use(server)

            server.resetHandlers()

            // HERE the test pass if we comment this line
            server.stop()
        },
        { auto: true },
    ],
})

function Probe() {
    const [status, setStatus] = useState("idle")

    return (
        <>
            <button
                onClick={async () => {
                    const response = await fetch("/api/msw-stop-race")

                    setStatus(response.ok ? await response.text() : `error:${response.status}`)
                }}
            >
                Request
            </button>
            <div>{status}</div>
        </>
    )
}

it("starts and stops the worker without making a request", () => {
    expect(true).toBe(true)
})

it("should still intercept the next test request", async ({ worker }) => {
    worker.use(
        http.get("/api/msw-stop-race", () => {
            return HttpResponse.text("ok")
        }),
    )

    const screen = await render(<Probe />)

    await userEvent.click(screen.getByRole("button", { name: "Request" }))

    await expect.element(screen.getByText("ok")).toBeInTheDocument()
})

Here is my vitest settings:

        test: {
            projects: [
                {
                    test: {
                        include: ["**/*.browser.test.{ts,tsx}"],
                        name: "browser",
                        browser: {
                            enabled: true,
                            headless: true,
                            provider: playwright(),
                            instances: [{ browser: "chromium" }],
                        },
                    },
                },
            ],
        },

Current behavior

The test hangs and fails with a timeout if we call server.stop()

Expected behavior

The test was fine with MSW 2.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds:triageIssues that have not been investigated yet.scope:browserRelated to MSW running in a browser

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions