Real-Time Application Testing: WebSocket Basics and Mock Interception
How to test a chat and a real time app with fixed data
Some modern apps requires instant feedback:
Chat: you can send and receives the messages at the moment
Real time apps: For example a website that displays the live price updates for various company stocks or the current values or crypto currency.
Notifications: When you see a message when you got a new email, or when you receive some like in one of your post on your social media you are instantly notified.
Rideshare apps: With Uber, Rapid, Lyft you can see where is the driver and get status updates.
Collaborative tools: Like Google docs, Miro, Figma, where you and other user can see and update the same document.
The communication protocol that enables bi-directional real time connection between the server and one or more clients is WebSocket.
You can create your own WebSocket with some popular libraries: SignalR for .Net, Firebase real time database, socket.io for Javascript.
How to test WebSocket apps
Testing a WebSocket app requires a different approach than web apps, some of the challenges are:
Connectivity: You can check that clients can connect without any problem and the connection is closed when the user exit the app or the app can reconnect after some internet connection issues
Message exchange: Check that the messages contains all the information and the information is correct for example on rideshare apps you need to test that all the status are correct.
Performance: You need to check if the WebSocket allows the expected number of the users, you can use the performance tools like K6, JMeter, Gatling or Artillery.
Security: You need to check unauthorized access, if are encrypted (check that is using wss://, cross site scripting, denial of service (DoS).
Testing with playwright
Playwright introduce a functions to test WebSocket
page.routeWebSocket() you can intercept, modify and mock a web socket on the page
browserContext.routeWebSocket() is the same than the previous but with all pages inside the browser.
How to test a chat
First you can check the web socket messages on the console network tab and Socket.
To test a chat demo sample: https://websockets.thecodeboss.dev/ it’s a WebSocket chat built by Aaron Krauss.
I wanted to simulate 2 users so I open 2 different browser and open a new page
const browserContext1 = await browser.newContext();
const browserContext2 = await browser.newContext();
const userPage1 = await browserContext1.newPage();
const userPage2 = await browserContext2.newPage();
After only for logging I added the page.routeWebsocket to intercept and log the messages, and added in the console.
It’s important add the routeWebSocket before the page is loaded.
const route = await userPage1.routeWebSocket(/websockets/, ws => {
const server = ws.connectToServer();
server.onMessage(message =>
{
// Log the server messages
console.log(`Server: ${message}`);
ws.send(message);
});
ws.onMessage(message => {
//Log the client messages
console.log(`Client ${message}`);
server.send(message);
});
});
return route;
For notifications on a rideshare app because at this moment playwright doesn’t include a wait for WebSocket message I added the WebSocket messages in an array and with a manual retry I am searching for the expected status.
Now go to the chat in both pages
const url = 'https://websockets.thecodeboss.dev/';
await userPage1.goto(url);
await userPage2.goto(url);
Now send a message with user 1
const user1Message = 'Hello from User 1!';
await userPage1.locator.getByRole('textbox', { name: 'Message' }).fill(user1Message);
await userPage1.locator.getByRole('button', { name: 'Submit' })click();
Check that both pages contains the same message
await expect(userPage1.page.locator('#messages td').first(), `Chat 1 have the text: ${user1Message}`).toHaveText(user1Message);
await expect(userPage2.page.locator('#messages td').first(), `Chat 2 have the text: ${user1Message}`).toHaveText(user1Message);
To practice you can add the steps to add a message with the second user and change to POM. You can compare with my solution
How to mock and test real time application
Now I want to test the https://stockticker.azurewebsites.net/ that is a SignalR app that returns some stock prices on real time.
Because the prices changes fast you can hardcode one of the values returned by the websocket (mock) and compare that is the same number on the UI.
SignalR has some custom format with some H (Hub), M (Method), A (Arguments), R (Response) properties so I have to check the format and after I updated only one of the values when the message is updateStockPrice.
const MOCK_CHANGE_VALUE = 1;
await page.routeWebSocket(/stockticker/, ws => {
//Connect to the server
const server = ws.connectToServer();
// Intercept messages
server.onMessage(message => {
try {
// Convert Buffer to string if necessary
const messageStr = typeof message === 'string' ? message : message.toString();
//Parse to JSON
const data = JSON.parse(messageStr);
// Change stock.Change to 1
if (data.R && Array.isArray(data.R)) {
data.R.forEach((stock: { Symbol: any; Price: number; Change: number; PercentChange: number; }) => {
stock.Change = MOCK_CHANGE_VALUE;
});
}
// Handle SignalR update messages
if (data.C && data.M && Array.isArray(data.M)) {
//Change the stock.Change on the updateStockPrice method
data.M.forEach((messageItem: { H: string; M: string; A: any[]; }) => {
if (messageItem.H === 'stockTickerMini' &&
messageItem.M === 'updateStockPrice' &&
messageItem.A && Array.isArray(messageItem.A))
{
messageItem.A.forEach(stock => {
stock.Change = MOCK_CHANGE_VALUE;
);
}
});
}
ws.send(JSON.stringify(data));
}
catch (e) {
//Log the error message
console.error('Failed to parse websocket: ' + e);
ws.send(message);
}
});
// Forward client -> server messages without modification
ws.onMessage(message => {
server.send(message);
});
});
Now you can check on the UI the same value, because the page take some time to load I added the wait for to wait that the stock is loaded correctly
await page.goto('https://stockticker.azurewebsites.net/');
// Wait for the GOOG row to appear
const row = page.getByRole('row', { name: 'GOOG' });
await row.waitFor();
await expect(row.locator('td').nth(3), 'Change should be 1 because was intercepted').toHaveText('â–² ' + MOCK_CHANGE_VALUE);
Thank you for reading, and feel free to suggest a topic for a new article and share if you think it is useful. Enjoy testing!!