Open gabrielmanara opened 3 years ago
@gabrielmanara would you like to intercept websocket creation or websocket frames?
@aslushnikov I'd like to intercept the frames to be able to change the messages (send and received).
It would be very cool: to block received or sent websocket frames by regexp on data, to match&replace websocket received or sent frames, to send on "client" my own websocket frame, on existing connection.
I asked about this in the Slack chat yesterday and Andrey said this hadn't been added as the team were unsure of the use case and asked me to file an issue but I can see there is one already :)
Just to elaborate a bit on what has already been posted above by others:
For HTTP requests Playwright can control the responses so the UI can be tested in isolation without having to actually connect to a server - it would be good if something similar could be done for websockets.
Playwright's own websocket tests use a TestServer to host a websocket server on localhost in order to send the 'incoming' message, ideally this would not be necessary in the same way it is not necessary for HTTP requests/responses.
The use case is to test a UI that uses websocket communication in isolation from a real/fake backend. The current websocket inspection capabilities make it possible to e.g. verify that a certain message is sent when the user clicks on a button, but the missing part is the ability to verify a change to the DOM based on a message that was received. I'd like to be able to do this entirely within Playwright, without having to mock websockets myself or use another library to do it.
For us, I think this could help us out with our usage of firestore that if I understand correctly uses websockets under the hood as well. We would be interested to listen to the websocket frames and have a sort of waitUntil. What @kiririk proposes above would be very useful as well.
For us, we would like to have the ability to intercept the messages (sent and received) and mock them.
My team is also interested in this. We are adding a page to our site that is going to be heavily reliant on websockets to keep the UI up to date, so being able to match what @jonny-philip is requesting would help ensure that the UI is responding correctly to the messages coming through that WebSocket
Just out of sheer curiosity since my team is in dire need of this type of functionality, how long does the feedback collection process usually take? We'd like to choose this as our solution, but we also just need some kind of solution by a given date so we'd like to get an understanding if we should be looking elsewhere for a temporary solution, or if we can prioritize other efforts and come back to this.
@btvanhooser if you are looking for a temporary solution you can find details in the playwright slack channel, I used mock-socket. It's not a pretty solution due to the difficulty in importing a package in a playwright script and making that available in the page context, but it does work.
I do hope this feature gets some traction through :)
Definitely something I'll try out :) thanks for the heads up
I'll migrate my automation framework from Cypress to Playwright if this feature comes out. This will be a real game changer for my use case. We're moving from http request polling to websocket connections for almost every major feature of the application under test.
I agree with @mrpicklez70 , this would be a gamechanger for e2e testing
Checking in on this again. Really anticipating this feature more than anything else in the library. Trying other routes has failed for us unfortunately so we really need this in order to get coverage where web sockets exist and that's growing with each sprint :/
Subscribing.
Note that if you use Socket.io, you can disable websockets in your server with io.set('transports', ['polling']);
, or if you're on NestJS, @WebSocketGateway({ transports: ['polling'] })
.
Disabling websockets temporarily in test is obviously not ideal, but it can be a temporary stopgap measure for e2e testing.
Sadly having this feature missing makes our lives miserable :< Some time ago we were blocked because duplicate websocket was crashing Playwright - this was fixed. But now we have another bug in Playwright wegarding iframes and Websockets and we are blocked again :< With this feature we could mock/disable websockets and use the framework.
Been another Quarter, so I felt like checking in to see if this is at least being planned. Really looking forward to this feature as application continues to switch over to using web sockets.
That would be an amazing feature to have. We recently deployed an application that relies on websocket, and being able to be independent from the backend would be a game changer for us.
I came up with an example of how to work around this: https://github.com/kylecoberly/playwright-socket-mocking-example
It's not bulletproof, but it's fairly simple and should allow you to send socket messages to a client and assert that messages were sent to the server.
@aslushnikov any updates on a possible timeline for this feature. My team is still highly interested and it's coming up on 2 years of collecting feedback
I came up with an example of how to work around this: https://github.com/kylecoberly/playwright-socket-mocking-example
It's not bulletproof, but it's fairly simple and should allow you to send socket messages to a client and assert that messages were sent to the server.
The issue with this is that simultaneous tests all run the extend independently, causing port conflicts attempting to set up multiple servers :(
You could probably lift that socket server out of the function to keep the same reference for every call.
how about you implement a sharedWorker (https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) that sits on the network stack and simulates websockets - postMessaging to the worker via an api and let the worker listen/emit on local ?
This would give you the capability to both intercept from userspace and emit to userspace - while leaving normal ws to passthrough.
You'd be able to mock things like you do http requests etc.
Echoing previous requests to say this feature would be incredibly helpful. Hopefully the requests gain enough traction to get it onto the roadmap soon.
For now, even if PlayWright would allow rerouting WebSocket connection requests to a different (local) URL, that would enable people to build out solutions that mock responses from a local WebSocketServer.
The MVP of this would also be really helpful - basically - allowing page.route()
to also intercept the web socket protocol - and then rather than mocking and stubbing messages from there - allowing devs to overwrite the request.url
so it is routed elsewhere.
I managed to figure out how to do this given a pretty complex but typical setup: we have an iFrame that is embedded in a form which itself loads an external document (a payment form) which establishes a websocket connection. I also needed to monkey patch the content security directives but combining this with a locally hosted WebSocket server now allows us to meaningfully sniff and record web socket messages using the WebSocket
PlayWright class and then replay those messages later on from a local WS server.
It's a long example but shows how to both sniff WebSocket messages using the built in PlayWright class as well as demonstrates how, by searching for matching wss
protocol urls (i.e. wss://payments.yourapp.com/ws
), you can monkey patch your application to instead connect to a local server.
import { Page } from '@playwright/test';
import { Mockmock } from 'mock-mock';
import WebSocket, { WebSocketServer } from 'ws';
const MM = Mockmock.instance;
const interceptWebSocketMessages = async (
page: Page,
frameUrl: string,
remoteWssUrl: string,
localWssUrl: string,
port = 3030
) => {
const webSocketId = 'wsPaymentMsgs';
// if recording sniff all websocket frames (incoming msgs) and save them
if (MM.isRecording) {
page.on('websocket', (ws) => {
ws.on('framereceived', (data) => {
MM.record(webSocketId, JSON.parse(data.payload.toString()));
});
});
}
// if replaying intercept the payments form request, relax CSP policies,
// and point payments form to local wss server
if (MM.isReplaying) {
await page.route(frameUrl, async (route) => {
const response = await route.fetch();
const frameHtml = await response.text();
const body = frameHtml.replace(remoteWssUrl, localWssUrl);
const headers = response.headers();
const csp = headers['content-security-policy'];
headers['content-security-policy'] = csp.replace(
"connect-src 'self'",
"connect-src 'self' localhost:* ws://localhost:*"
);
await route.fulfill({ response, body, headers });
});
// launch the local web socket server
const wss = new WebSocketServer({ port });
wss.on('connection', (ws: WebSocket) => {
// handle errors
ws.on('error', (error: Error) => {
console.log('Local websocket server error!');
console.log(JSON.stringify(error));
});
// once a connection is established, replay the mocked data every 250ms
const recursivelySendData = () => {
const frame = MM.replay(webSocketId, 'data');
if (typeof frame === 'undefined') {
// we've run out of messages to replay so stop sending data
return;
}
ws.send(JSON.stringify(frame.mock));
setTimeout(recursivelySendData, 250);
};
recursivelySendData();
});
}
};
MM is shorthand for accessing the Mockmock singleton instance, it's an in house tool used for recording and replaying mocked data saved to local fixture files that we are hoping to open source soon - replace MM calls with your own mock logic as needed.
Greetings, it would be good to have this feature, any updates when it will be implemented ?))
Please when will it be released
I managed to figure out how to do this given a pretty complex but typical setup: we have an iFrame that is embedded in a form which itself loads an external document (a payment form) which establishes a websocket connection. I also needed to monkey patch the content security directives but combining this with a locally hosted WebSocket server now allows us to meaningfully sniff and record web socket messages using the
WebSocket
PlayWright class and then replay those messages later on from a local WS server.It's a long example but shows how to both sniff WebSocket messages using the built in PlayWright class as well as demonstrates how, by searching for matching
wss
protocol urls (i.e.wss://payments.yourapp.com/ws
), you can monkey patch your application to instead connect to a local server.import { Page } from '@playwright/test'; import { Mockmock } from 'mock-mock'; import WebSocket, { WebSocketServer } from 'ws'; const MM = Mockmock.instance; const interceptWebSocketMessages = async ( page: Page, frameUrl: string, remoteWssUrl: string, localWssUrl: string, port = 3030 ) => { const webSocketId = 'wsPaymentMsgs'; // if recording sniff all websocket frames (incoming msgs) and save them if (MM.isRecording) { page.on('websocket', (ws) => { ws.on('framereceived', (data) => { MM.record(webSocketId, JSON.parse(data.payload.toString())); }); }); } // if replaying intercept the payments form request, relax CSP policies, // and point payments form to local wss server if (MM.isReplaying) { await page.route(frameUrl, async (route) => { const response = await route.fetch(); const frameHtml = await response.text(); const body = frameHtml.replace(remoteWssUrl, localWssUrl); const headers = response.headers(); const csp = headers['content-security-policy']; headers['content-security-policy'] = csp.replace( "connect-src 'self'", "connect-src 'self' localhost:* ws://localhost:*" ); await route.fulfill({ response, body, headers }); }); // launch the local web socket server const wss = new WebSocketServer({ port }); wss.on('connection', (ws: WebSocket) => { // handle errors ws.on('error', (error: Error) => { console.log('Local websocket server error!'); console.log(JSON.stringify(error)); }); // once a connection is established, replay the mocked data every 250ms const recursivelySendData = () => { const frame = MM.replay(webSocketId, 'data'); if (typeof frame === 'undefined') { // we've run out of messages to replay so stop sending data return; } ws.send(JSON.stringify(frame.mock)); setTimeout(recursivelySendData, 250); }; recursivelySendData(); }); } };
MM is shorthand for accessing the Mockmock singleton instance, it's an in house tool used for recording and replaying mocked data saved to local fixture files that we are hoping to open source soon - replace MM calls with your own mock logic as needed.
This is fine for a typical scenario where you use playwright for component testing, but if you want to do a smoketest or a system integration test you'd need to be able to do a Man-in-the-middle approach instead. It should be fairly simple to implement in playwright since they playwright team could potentially just always proxy network request in the browser to an internal abstraction layer - if nothing is hooking into that just continue as normal - otherwise just echo messages to whatever listener I registered.
We need an official release for this feature
It's really helpful if we could have this feature!
We really need websocket interception, while sniffing of websocket data can be done with existing websocket class, we can't change the contents of sent messages or decide to send them or not.
I agree that it would be useful for testing websocket based APIs and the way that they behave when sockets are blocked
how about you implement a sharedWorker (https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) that sits on the network stack and simulates websockets - postMessaging to the worker via an api and let the worker listen/emit on local ?
This would give you the capability to both intercept from userspace and emit to userspace - while leaving normal ws to passthrough.
You'd be able to mock things like you do http requests etc.
@mattoni - Hi, did you have any success with this method? I've also been dealing with the problem of port conflicts, and have to stick to 1 worker because of this.
just echoing what everyone here is saying: we really need this feature. :] I love playwright, and this is the first blocker I've had with it (thanks for the good work, btw).
I've tried most of the workarounds, and unfortunately none of them worked for me =/
We really need this feature, the workarounds are feasible on the long run.
@manjumuthaiya Sorry for the late reply. Not exactly, I created a custom websocket server that sets up a 'controller' and receivers, where the controller can send ws messages that get propagated to all 'receivers'. the receivers are paired up with a controller id, so when multiple tests are running a controller id is set up and the receiver i inject into the running browser matches so it only receives messages sent by that controller. It was a lot of work and not perfect. A solution native to playwright would be more ideal but it solves our needs for now.
I also totally agree with everyone here, that official support for mocking web socket messages would be an awesome addition to playwright. For now, I also created a generic workaround solution that is very limited at the moment but might be a good starting point for further improvements. Parallelization of tests for example would need a slight modification. You can find the helper class here: https://github.com/LoaderB0T/playwright-easy-network-stub/blob/main/src/playwright-easy-ws-stub.ts
@LoaderB0T Thanks for sharing this. This worked for Playwright 1.40.0. I just had to also mock the connection creation which returned the address to websocket connection url.
Any plans to implement this in 2024?
Any updates?
Would love this feature as well
Adding my voice to the thread that support for this would be huge! Please!
FYI: My Workaround for my special Setup with Stomp-js and Spring-Stomp on the backend:
Using the spring-stomp-server as Mock for automatic tests:
On a deployed stage the real backend can also be used for the automatic tests, but for local runs, it is easier to start spring-stomp-server concurrently with "ng serve" without transitive dependencies like databases.
With Stomp-js you can then directly push messages from your tests to the spring-stomp-server (or the real backend). And then verify that your App reacts accordingly.
Downside: The tests cannot be executed in parallel with the other tests. For that, the tests must be split into "parallel" and "serial" runs.
I've been following up on the WebSockets supports feature, and I saw you've already implemented the option to listen to the WebSockets on the page and would be nice to be possible to intercept these WebSockets calls as well, something similar with Route.