Skip to content

feat!: rewrite package to new network source architecture#48

Open
christoph-fricke wants to merge 23 commits intomswjs:mainfrom
christoph-fricke:feat/network-source-api
Open

feat!: rewrite package to new network source architecture#48
christoph-fricke wants to merge 23 commits intomswjs:mainfrom
christoph-fricke:feat/network-source-api

Conversation

@christoph-fricke
Copy link
Copy Markdown

@christoph-fricke christoph-fricke commented Apr 8, 2026

This PR rewrites @msw/playwright to be based on the new network source architecture released in MSW v2.13.0.

Changes

  • defineNetworkFrame uses the new defineNetwork API with a PlaywrightSource underneath
  • The context option has been widened to accept either a BrowserContext or Page
  • Added a new routePattern option for providing a custom pattern passed to context.route(...).
  • Added a new websocketPattern options for providing a custom pattern passed to context.routeWebSocket(...).

Todo

  • All WebSocket related tests are broken after updating MSW to v2.13.2:
      [chromium] › tests/multiple-pages.test.ts:48:1 › intercepts a WebSocket connection on a programmatically created page
      [chromium] › tests/websockets.test.ts:31:1 › sends a text data to the client
      [chromium] › tests/websockets.test.ts:56:1 › sends a buffer data to the client
      [chromium] › tests/websockets.test.ts:84:1 › sends a blob data to the client
      [chromium] › tests/websockets.test.ts:109:1 › closes the client connection
      [chromium] › tests/websockets.test.ts:134:1 › closes the client connection with a custom reason
      [chromium] › tests/websockets.test.ts:162:1 › closes the client connection with a non-configurable code
      [chromium] › tests/websockets.test.ts:190:1 › connects to the actual server
    
  • Scafhold PlaywrightSource that extends NetworkSource.
    • Accept either BrowserContext or Page as an interception target
  • Extract reusable parts from the current implementation into utils.
  • Support for HTTP request interception:
    • Implement PlaywrightHttpNetworkFrame extends HttpNetworkFrame.
    • Integrate PlaywrightHttpNetworkFrame into PlaywrightSource.
    • Adjust HTTP based tests to verify implementation
    • Infer and propagate baseUrl for WebSocket frames
  • Support for WebSocket request interception:
    • Implement PlaywrightWebSocketNetworkFrame extends WebSocketNetworkFrame.
    • Integrate PlaywrightHttpNetworkFrame into PlaywrightSource.
    • Adjust WebSocket based tests to verify implementation
    • Infer and propagate baseUrl for WebSocket frames
  • Replace defineNetworkFixture implementation with defineNetwork wrapper implementation

Comment thread src/playwright-source.ts
Comment thread src/playwright-source.ts Outdated
Copy link
Copy Markdown
Author

@christoph-fricke christoph-fricke Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kettanaito I am struggling a bit with implementing WebSocket handling, and would like to align on an approach before continuing. I don't have experience with using WebSocket mocking in MSW, which makes this a bit harder as well. So far I thought about three approaches to tackling this.

1. PlaywrightWebSocketNetworkFrame extends WebSocketNetworkFrame

Similar to the PlaywrightHttpNetworkFrame implementation, this seems like the most obvious choice. When constructing an implementation of the frame, it requires a WebSocketClientConnection and WebSocketServerConnection connection. First idea: Let's use the already existing Playwright connection classes that implement the WebSocket(Client|Server)ConnectionProtocol.

const frame = new PlaywrightWebSocketNetworkFrame({
  connection: {
    client: new PlaywrightWebSocketClientConnection(route),
    server: new PlaywrightWebSocketServerConnection(route),
    info: { protocols: [] },
  },
})

This does not work! It results in type errors for both client and server:

  • client: Type 'PlaywrightWebSocketClientConnection' is missing the following properties from type 'WebSocketClientConnection': socket, transport, [kEmitter$1]
  • server: Type 'PlaywrightWebSocketServerConnection' is missing the following properties from type 'WebSocketServerConnection': client, transport, createConnection, mockCloseController, and 7 more.

This also explains the TS errors in the existing fixture after updating MSW when calling webSockerHandler.run(...), and might explain why all WebSocket related tests have been failing.

=> Takeaway: Implementing WebSocket(Client|Server)ConnectionProtocol is not enough. We have to use WebSocket(Client|Server)Connection if we want to extend WebSocketNetworkFrame.

2. Implementing a WebSocketTransport

Since WebSocket(Client|Server)Connection are already usable classes, it might be enough to implement a custom WebSocketTransport, and construct WebSocket(Client|Server)Connection directly with a PlaywrightWebSocketTransport.

My concern with this approach is that this might not work. The two classes appear to be a bit too closely related to an actual WebSocket. Both require a WebSocket instance for construction and do operations with it to some extend. I fear that we cannot provide a reliable WebSocket instance (mock) with the stuff we get from Playwright's WebSocketRoute.

3. Extend a "low-level" NetworkFrame<"ws">

Maybe we do not need to extend WebSocketNetworkFrame. All we have to do is resolve/proxy WebSocket messages using the WebSocketRoute (famous last words...). Maybe the simplest, most robust approach, is a PlaywrightWebSocketNetworkFrame that extends NetworkFrame<"ws"> directly, and uses WebSocketRoute instead of WebSocket(Client|Server)Connection.

I started this third approach in this file. But as you can see, msw/experimental is missing a bunch of exports to be able to directly extend NetworkFrame. So I am wondering if this approach should be avoided at all cost.


What are your thoughts on this? What do you think is the best approach for handling Playwright WebSocket routes with the new network source architecture? Are there missing pieces in MSW to make this feasible?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your first approach sounds correct. The type mismatch might as well be a bug. WebSocketClientConnectionProtocol type is a general type I created exactly for this reason (so the implementers don't have to worry about transport).

Yeah, it's definitely a bug. In websocket-frame.ts:

export interface WebSocketNetworkFrameOptions {
  connection: WebSocketConnectionData
}

I'm getting the WebSocketConnectionData from the Interceptors as-is, but that's a mistake. That type is a narrower type than what the frame should care about.

We should probably annotate that connection option as WebSocketHandlerConnection from WebSocketHandler.ts, which contains the broader WebSocketClientConnectionProtocol.

Can you open an issue for this in the MSW repo, please? 🙏 I think this is a great find.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

@christoph-fricke christoph-fricke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kettanaito The rewrite is complete so far. All tests pass (when the "type as value" bug is fixed in MSW or locally) and the new implementation feels quite solid already. I think next good steps are:

  1. We give the implementation a proper review
  2. Clarify open questions and resolve missing MSW parts
  3. Remove the old fixture.ts implementation
  4. Update the README and add some JSDoc comments to the source class and its options.

Comment thread src/frames/http-frame.ts
Comment on lines +9 to +11
import type { RequestHandler } from 'msw'
import type { NetworkFrameResolutionContext } from '../../node_modules/msw/lib/core/experimental/frames/network-frame.mjs'
import type { UnhandledFrameHandle } from '../../node_modules/msw/lib/core/experimental/on-unhandled-frame.mjs'
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ MSW should export these types before we merge this.

Comment on lines +14 to +16
import type { WebSocketHandler } from 'msw'
import type { NetworkFrameResolutionContext } from '../../node_modules/msw/lib/core/experimental/frames/network-frame.mjs'
import type { UnhandledFrameHandle } from '../../node_modules/msw/lib/core/experimental/on-unhandled-frame.mjs'
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ MSW should export these types before we merge this.

}

passthrough(): void {
this.#route.connectToServer()
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Not sure if this approach, or calling this.data.connection.server.connect() is better? The latter registers pending event listeners from the mock. Does it even matter in the cases where passthrough is called?

}
}

class PlaywrightWebSocketClientConnection implements WebSocketClientConnectionProtocol {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 PlaywrightWebSocketClientConnection and PlaywrightWebSocketServerConnection are pretty much copied. Only made slight adjustment such as aligning the #route field naming.

Comment thread src/playwright-source.ts
} from './route-utils.js'

export interface PlaywrightSourceOptions {
skipAssetRequests?: boolean
Copy link
Copy Markdown
Author

@christoph-fricke christoph-fricke Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 With support for custom route patterns, skipAssetRequests might be obsolete.

I added support for custom patterns, because in my experience most mock setups mock API(s) behind one (a few at most) endpoints. Alongside registering multiple PlaywrightSource sources, custom patterns can help reduce the interception overhead for this common case. What do you think about this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if we go with the route of mapping handlers to page.route(), then I suppose there's no need in skipAssetRequests anymore. Just need to make sure the tests pass.

Comment thread tests/regressions/use-wildcard.test.ts Outdated
Comment thread tests/internal/routes.test.ts Outdated
Comment thread tests/multiple-pages.test.ts Outdated
expect.soft(consoleSpy.callCount).toBe(2)
expect(consoleSpy.getCall(1)?.args).toEqual([
expect.soft(consoleSpy.callCount).toBe(3)
expect(consoleSpy.getCall(2)?.args).toEqual([
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 The implementation in fixture.ts never tried to resolve Vite's dev-server WebSocket here, because it aborts early when no WebSocket handlers are defined. I let the orchestration happen completely in WebSocketNetworkFrame.resolve(), which leads to the additional warning.

If needed, we could add an early passthrough in PlaywrightWebSocketNetworkFrame to avoid the additional WebSocket warning.

Comment on lines +29 to +32
connection: {
client: new PlaywrightWebSocketClientConnection(options.route),
server: new PlaywrightWebSocketServerConnection(options.route),
info: { protocols: [] },
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Depends on work in mswjs/msw#2710. Currently, this code surfaces a TS error.

Although, it (surprisingly) works at runtime already. At least all tests pass.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 6, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedmsw@​2.12.14 ⏵ 2.13.293 +110010096100

View full report

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants