WebSocket & Real-Time Testing
Some SAP applications use WebSocket connections for real-time features such as collaborative editing, live notifications, and push-based data updates. This guide covers how to monitor, intercept, and test WebSocket communication alongside Praman's UI5 bridge.
SAP WebSocket Scenarios
WebSockets appear in several SAP contexts:
- SAP Fiori Notifications: The notification channel uses a WebSocket or long-polling connection to push alerts to the FLP shell.
- Collaborative editing: SAP applications like S/4HANA journals or SAP Build may use WebSockets for multi-user editing.
- OData V4 with server-sent events: Some OData V4 services push change notifications via WebSocket channels.
- Custom real-time dashboards: Business applications that display live KPIs or process monitoring data.
Monitoring WebSocket Frames
Playwright provides built-in WebSocket monitoring through the page.on('websocket') event:
import { test, expect } from 'playwright-praman';
test('monitor WebSocket messages', async ({ page, ui5 }) => {
const wsMessages: { type: 'sent' | 'received'; data: string }[] = [];
page.on('websocket', (ws) => {
ws.on('framesent', (frame) => {
wsMessages.push({ type: 'sent', data: frame.payload.toString() });
});
ws.on('framereceived', (frame) => {
wsMessages.push({ type: 'received', data: frame.payload.toString() });
});
});
await page.goto('/app#/dashboard');
await ui5.waitForUI5();
await test.step('Trigger an action that sends a WebSocket message', async () => {
await ui5.click({ id: 'subscribeBtn' });
await ui5.waitForUI5();
});
await test.step('Verify WebSocket communication occurred', async () => {
expect(wsMessages.length).toBeGreaterThan(0);
const sentMessages = wsMessages.filter((m) => m.type === 'sent');
expect(sentMessages.length).toBeGreaterThan(0);
});
});
Waiting for WebSocket Events
Combine Playwright's WebSocket monitoring with Praman's waitForUI5() to wait for both the WebSocket message and the resulting UI update:
test('data refreshes after WebSocket push', async ({ page, ui5 }) => {
await page.goto('/app#/orders');
await ui5.waitForUI5();
await test.step('Wait for real-time update', async () => {
// Create a promise that resolves when a specific WS message arrives
const wsUpdate = new Promise<string>((resolve) => {
page.on('websocket', (ws) => {
ws.on('framereceived', (frame) => {
const payload = frame.payload.toString();
if (payload.includes('orderStatusChanged')) {
resolve(payload);
}
});
});
});
// Trigger the update from another source (API call, another user action, etc.)
await page.request.post('/api/orders/123/status', {
data: { status: 'Shipped' },
});
// Wait for the WebSocket message
const message = await wsUpdate;
expect(message).toContain('orderStatusChanged');
// Wait for UI5 to process the update
await ui5.waitForUI5();
});
await test.step('Verify UI reflects the update', async () => {
const status = await ui5.getText({ id: 'orderStatusField' });
expect(status).toBe('Shipped');
});
});
Intercepting WebSocket Connections
Use Playwright's route API to intercept WebSocket upgrade requests for testing error scenarios:
test('app handles WebSocket connection failure gracefully', async ({ page, ui5 }) => {
// Block WebSocket connections
await page.route('**/ws/**', (route) => {
route.abort('connectionfailed');
});
await page.goto('/app#/dashboard');
await ui5.waitForUI5();
await test.step('Verify fallback behavior', async () => {
// App should fall back to polling or show a connection warning
const warning = ui5.control({
controlType: 'sap.m.MessageStrip',
properties: { type: 'Warning' },
});
await expect(warning).toBeDefined();
});
});
Combining WebSocket Testing with ui5.waitForUI5()
The Praman bridge's waitForUI5() monitors the UI5 framework's internal pending request counter, but WebSocket messages are not tracked as pending requests by default. This means waitForUI5() may resolve before a WebSocket-triggered model update has been processed.
To handle this, add an explicit wait for the expected UI change after waitForUI5():
test('wait for WS-driven model update', async ({ page, ui5 }) => {
await ui5.waitForUI5();
// waitForUI5 ensures HTTP requests are done, but WS updates may still be pending
// Use Playwright's built-in waiting for the expected text change
await expect(page.getByText('Live: Connected')).toBeVisible({ timeout: 10_000 });
// Now the WS-driven update is reflected in the DOM
const status = await ui5.getText({ id: 'connectionStatus' });
expect(status).toBe('Connected');
});
Common Pitfalls
- waitForUI5() does not wait for WebSocket updates: The UI5 stability check monitors XHR/fetch requests and setTimeout callbacks, not WebSocket frames. Always add an explicit wait for the expected DOM or model change after a WebSocket event.
- WebSocket timing in CI: WebSocket connections may take longer to establish in CI environments. Use generous timeouts (10-15 seconds) for WebSocket-related waits.
- Binary WebSocket frames: Some SAP services send binary WebSocket frames (e.g., for file collaboration). Use
frame.payloadas aBufferinstead of calling.toString(). - Multiple WebSocket connections: An SAP FLP page may open several WebSocket connections (notifications, collaboration, analytics). Filter by URL pattern to isolate the one you are testing.