-
-
Notifications
You must be signed in to change notification settings - Fork 530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support cross-process interception via setupRemoteServer
#1617
base: main
Are you sure you want to change the base?
Conversation
@SebastianSedzik, yes, the intention is to iterate on the current design to allow multiple remote connections to the same
This feature will allow you to control the network in one Node.js (e.g. the server-side of your Next.js) from another (e.g. your test).
For in-application usage, this already works. Check out this Next.js + MSW example for the full setup to enable this. |
I have some thoughts regarding the application spawn order and identifying individual tests/workers. Will share once I have a prototype confirming my ideas. Looks promising. |
This is really great. Thank you for your hard work! 🎉 |
The tests are failing because I'm on pnpm 9 and #2211 isn't merged yet. |
Blockers
|
UpdateAlright, there's a lot to unpack. In short, I've got the MVP of CPRI completely working. This includes the test/app paradox solved, the association between the app's runtime and a particular test/worker closure, and the ability to control the server-side network from the test. Documenting those decisions for posterity. Test/app paradoxSolved by two things:
Not only does this solve the test/app paradox because it guarantees the fixed order of things (first test, then Test/app bindingEven when spawned within individual test cases, your app still communicates with a single instance of This is solved by using the Test/app binding occurs due to What's next?A ton of todo's to cover! The public API isn't the most ergonomic thing, and I'm considering simplifying it quite a bit while exposing the customization options to the end developer. There are also some security considerations I want to address, mostly concerning safe defaults of the WebSocket server CORS. This is also a good time to sponsor this effort. Thank you. |
Bug: Empty buffer sent to WebSocket connection
No idea why this happens. Something down the line sends an empty buffer to the server, SocketIO tries to emit the Root causemsw/src/node/SetupServerApi.ts Line 191 in fa8a197
Forwarding the
Note that |
Bug: Deep objects cannot be emitted with SocketIONot sure if it's MSW interfering in some way, but emitting the Would be nice to add an integration test to Interceptors to confirm or debunk this. ✅ SolvedMoved away from WebSockets to HTTP for the internal server. |
Test failures
This is caused by migrating to HTTP for the internal server. Since Vitest likely runs some of these tests in parallel, it attempts to call This is a good precursor into figuring out whether the internal server port is fixed or random. I likely lean toward making it random and passing it along with |
Intention
Introduce an API that allows one process to modify the traffic of another process. The most apparent application for this is testing server-side behaviors of a JavaScript application:
This API is designed exclusively for use cases when the request-issuing process and the request-resolving process (i.e. where you run MSW) are two different processes.
Proposed API
With consideration to the existing MSW user experience, I suggest we add a
setupRemoteServer()
API that implements theSetupApi
interface and has a similar API tosetupServer
. The main user-facing distinction here is thatsetupRemoteServer
is affecting a remote process, as indicated by the name.The
.listen()
and.close()
methods of the remote server become async since they now establish and terminate an internal server instance respectively.You can then operate with the
remote
server as you would with a regularsetupServer
, keeping in mind that it doesn't affect the current process (your test) but instead, any remote process that runssetupServer
(your app).By fully extending the
SetupApi
, thesetupRemoteServer
API provides the user with full network-managing capabilities. This includes defining initial and runtime request handlers, as well as observing the outgoing traffic of a remote process using the Life-cycle API (remote.events.on(event, listener)
). I think this is a nice familiarity that also provides the user with more power when it comes to controlling the network.Implementation
I've considered multiple ways of implementing this feature. Listing them below.
(Chosen) WebSocket server
The
setupRemoteServer
API can establish an internal WebSocket server that can route the outgoing traffic from any server-side MSW instance anywhere and deliver it to the remote server to potentially resolve.Technically, the WebSocket server acts as a resolution point (i.e. your handlers) while the remote MSW process acts as a request supplier (similar to how the Service Worker acts in the browser).
Very roughly, this implies that the regular
setupServer
instances now have a fixed request handler that tries to check if any outgoing request is potentially handled by an existing remote WebSocket server:If no WebSocket server was found or establishing a connection with it fails within a sensible timeout period (~500ms), the
setupServer
instance of the app continues to operate as normal.IPC
The test process and the app process can utilize IPC (interprocess communication) to implement a messaging protocol. Using that protocol, the app can signal back any outgoing requests and the test can try resolving them against the request handlers you defined immediately in the test.
This approach is similar to the WebSocket approach above with the exception that it relies on IPC instead of a standalone running server. With that, it also gains its biggest disadvantage: the app process must be a child process of the test process. This is not easy to guarantee. Depending on the framework's internal implementation, the user may not achieve this parent/child relationship, and the IPC implementation will not work.
Given such a demanding requirement, I've decided not to use this implementation.
Limitations
useRemoteServer()
affects the network resolution for the entire app. This means that you cannot have multiple tests that override request handlers for the same app at the same time. I think this is more than reasonable since you know you're running 1 app instance that can only behave in a single way at a single point in time. Still, I expect users to be confused when they parallelize their E2E tests and suddenly see some network behaviors leaking across the test cases.Concerns
setupRemoteServer
only affects the server-side network behavior of any running application process with the server-side MSW integration? To affect the client-side network behavior from a test you have to 1) havesetupWorker
integration in the app; 2) set a globalwindow.worker
instance; 3) usewindow.worker.use()
to add runtime request handlers. This stays as it is right now, no changes here.The API is TBD and is subjected to change.
Roadmap
rest.all()
handler.response
insetupServer
.ReadableStream
from the remote request handler (may consider transferringReadableStream
over the WS messages instead ofArrayBuffer
, if that's allowed).ReadableStream
transfer over WebSockets that would be great.remotePort
andport
an implementation detail ofsetupRemoteServer
andsetupServer({ remote: true })
. The developer mustn't care about those.use()
test (may have something to do with the handlers management refactoring as a part of theserver.boundary()
).setupWorker
support (see feat: support cross-process interception viasetupRemoteServer
#1617 (comment)).socket.io
in favor ofws
if we don't need anything socketio-specificforwardLifeCycleEvents()
).Blockers
socket.io-parser
are broken for the CJS build).setupRemoteServer
#1617 (comment)).