In the first part of this article, we explored the limitations of cy.intercept
for WebSocket testing and introduced a Cypress hybrid framework for handling real-time WebSocket interactions. Now, let’s dive into real-world payment testing scenarios, where WebSockets play a vital role in ensuring accurate transaction updates, live balance tracking, and seamless user experiences.
Why WebSockets does matter for payment testing?
Built-in payment systems in a web applications frequently rely on real-time updates to provide users with immediate feedback on their transactions. Unlike traditional HTTP polling, which repeatedly requests data from the server, WebSockets enable instant push notifications, making them ideal for:
- Live transaction status updates (e.g., "Payment Processing", "Payment Successful", "Payment Failed").
- Real-time balance updates after deposits, withdrawals, or refunds.
- Instant fraud alerts and security notifications.
Since Cypress doesn’t natively support WebSocket testing, we need to extend its capabilities using custom commands and WebSocket event listeners. This section will walk through specific payment testing scenarios, including:
- Validating real-time payment status updates.
- Ensuring balance updates after transactions.
- Handling error cases, disconnections, and retries.
Real-time SignalR connection for balance and payment status updates
The connectWStoListen
is a custom Cypress command is designed to establish a real-time WebSocket connection using SignalR. It listens for updates related to balance changes and payment status in applications that rely on WebSockets for instant transaction validation.
Cypress.Commands.add(
"connectWStoListen",
(url, { onBalanceUpdate, onPaymentStatusUpdate }) => {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
Cypress.env("currentWebSocket", ws); // Store WebSocket instance globally
...
Introducing the handshake data
To establish a SignalR connection, we first send a handshake request to the server. This ensures that both client and server agree on communication protocols before exchanging actual data. The format is commonly used across the different types of a web sockets:
...
const handshakeData = '{"protocol":"json","version":1}\x1e';
let receivedEmptyResponse = false; // Track server's empty {} response
let readyToListen = false; // Track when we're ready to listen for custom messages
...
The \x1e
at the end of handshakeData
is a special SignalR delimiter, marking the end of the message.
Once the handshake is complete, we can start listening for messages and handling errors.
Handling WebSocket messages & errors
Once the connection is established, the SignalR receives real-time messages from the server. Our Cypress command processes these messages and handles different server responses.
ws.onopen = () => {
console.log("SignalR WebSocket connection established");
ws.send(handshakeData); // Step 1: Send handshake message
ws.onmessage = (event) => {
const rawMessage = event.data;
const messages = rawMessage.split("\x1e").filter((m) => m);
messages.forEach((message) => {
try {
const parsedMessage = JSON.parse(message);
// Step 2: Check for initial empty response {}
if (
Object.keys(parsedMessage).length === 0 &&
!receivedEmptyResponse
) {
console.log("Received empty response {} from server.");
receivedEmptyResponse = true;
ws.send('{"type":6}\x1e'); // Step 3: Send type after empty server response
}
// Step 4: Wait for server to respond with {"type":6}
else if (
parsedMessage.type === 6 &&
receivedEmptyResponse &&
!readyToListen
) {
console.log(
"Received server confirmation type 6. Ready for further messages.",
);
readyToListen = true;
resolve(); // Signal that we're ready for payment deposit request
}
// Only listen for custom messages
// ReceivePaymentStatusUpdate and ReceiveBalanceUpdate
// after setup is complete
if (
readyToListen &&
parsedMessage.type === 1 &&
parsedMessage.target
) {
if (
parsedMessage.target === "ReceivePaymentStatusUpdate" &&
onPaymentStatusUpdate
) {
onPaymentStatusUpdate(parsedMessage.arguments[0]);
}
if (
parsedMessage.target === "ReceiveBalanceUpdate" &&
onBalanceUpdate
) {
onBalanceUpdate(parsedMessage.arguments[0]);
}
}
} catch (error) {
console.error("Failed to parse SignalR message:", error);
}
});
};
ws.onerror = (error) => {
console.log(WebSocket error: ${error.message});
reject(error);
};
ws.onclose = () => {
console.log("SignalR WebSocket connection closed");
};
};
});
},
);
Error Handling: The WebSocket gracefully logs errors and closes connections to prevent resource leaks.
Automatic Retrying: If pending transactions remain unresolved, Cypress will retry until confirmation is received.
Implementing WebSocket balance verification
Now that our WebSocket connection is in place, we can create a dedicated method to validate real-time balance updates.
connectWStoVerifyBalance() {
// Construct your WebSocket SignalR URL
// The URL is based on configuration for specific testing
// environment
const urlWS = `${envConfig.webSocketAPI}${bearerToken}`;
cy.connectWStoListen(urlWS, {
onPaymentStatusUpdate: (paymentStatus) => {
const parsedStatus = JSON.parse(paymentStatus);
// Wait and retry if payment status is still 'pending'
if (parsedStatus.status === "pending") {
console.warn("Payment status is pending. Retrying...");
}
// Do an assertion that status is updated to Success
expect(parsedStatus.status).to.equal("success");
console.log("Payment status updated successfully:", parsedStatus);
},
onBalanceUpdate: (updatedBalance) => {
// Do required assertions for a balance update
// Save new balance value if it is necessary
console.log("Balance updated successfully:", updatedBalance);
// Close WebSocket connection
const ws = Cypress.env("currentWebSocket");
if (ws) ws.close();
},
});
});
}
Using WebSockets in a Cypress test case
Now, let’s implement WebSocket validation in a Cypress test case to verify that payments are processed correctly and that balance updates happen in real time.
describe("Deposit Feature - Real-Time WebSocket Validation", () => {
it(`Verify that a user can deposit min amount and payment is successful`, () => {
// Step 1: User Registration & Setup
cy.registerNewUser();
cy.confirmUserIsRegistered();
// Step 2: Establish WebSocket Connection for Balance Monitoring
// using our created method
cy.connectWStoVerifyBalance();
// Step 3: Navigate to payments and do required actions to
// initiate a deposit transaction
cy.navigateToPayments();
cy.accessDepositTab();
cy.verifyMinimumDepositAmount();
cy.validatePendingPaymentStatus();
// Step 4: Simulate payment process via API/webhook/UI
cy.updateDepositStatusViaAPI();
// Step 5: Verify successful payment update on UI
cy.validateSuccessfulPaymentMessage();
});
});
Since we've added console logs to our Cypress custom command connectWStoListen
, we will see real-time status updates in the Cypress console. This provides full visibility into how SignalR WebSocket connections handle transaction updates.
Conclusion
WebSockets provide real-time transaction tracking for modern payment systems, ensuring that users get immediate feedback. By extending Cypress with custom WebSocket commands, we can:
- Test instant balance updates.
- Validate payment processing workflows.
- Ensure seamless, real-time UX in financial apps.
Would you like to know more about Cypress and QA insights? Follow me on LinkedIn🚀 or subscribe to my TG channel🚀
Top comments (0)