DEV Community

Styrow.dev
Styrow.dev

Posted on Originally published at styrow.dev

Architecting Robust Playwright E2E Validation for Complex, Asynchronous WebSocket Data Streams

🚨 Senior Engineer Challenge: How do you reliably test complex, asynchronous WebSocket data streams in Playwright without flaky tests?

📌 Problem Statement
Modern web apps use WebSockets for real-time data, like trading platforms. Testing these streams end-to-end (E2E) in Playwright is challenging due to:
❌ Asynchronous message arrival and potential out-of-order delivery.
❌ High volume of diverse message types, requiring specific filtering.
❌ Need for robust assertions on data structure, values, and event sequences within a timeframe.
❌ Requirement for a reusable, maintainable solution to prevent flakiness.

💡 Solution & Code Walkthrough
A robust strategy involves intercepting, buffering, filtering, and flexible asynchronous assertions. We decouple data capture from assertion logic.

• Conceptual Architecture:
1. Interceptor: Captures all WebSocket frames.
2. Buffer: Stores captured JSON messages.
3. Filter/Waiter: Provides methods to retrieve specific messages based on criteria, with timeouts.

• Playwright Implementation (TypeScript/JavaScript):

// websocketInterceptor.ts
import { Page, WebSocket } from '@playwright/test';

export class WebSocketInterceptor {
  private messages: any[] = [];

  async setup(page: Page) {
    page.on('websocket', ws => {
      ws.on('framereceived', event => {
        try {
          const message = JSON.parse(event.payload.toString());
          this.messages.push(message);
        } catch (e) {
          // Optionally ignore non-JSON frames
        }
      });
    });
  }

  // Waits for a message matching a predicate within a timeout
  async waitForMessage(predicate: (msg: any) => boolean, timeoutMs = 10000): Promise<any> {
    const startTime = Date.now();
    while (Date.now() - startTime < timeoutMs) {
      const foundMessageIndex = this.messages.findIndex(predicate);
      if (foundMessageIndex > -1) {
        const message = this.messages[foundMessageIndex];
        this.messages.splice(foundMessageIndex, 1); // Remove to prevent re-matching
        return message;
      }
      await page.waitForTimeout(50); // Polling interval
    }
    throw new Error(`Message not found within ${timeoutMs}ms.`);
  }

  // Clears all buffered messages
  clearMessages() {
    this.messages = [];
  }
}
Enter fullscreen mode Exit fullscreen mode
// test-scenario.spec.ts
import { test, expect } from '@playwright/test';
import { WebSocketInterceptor } from './websocketInterceptor';

test('validates real-time trade updates', async ({ page }) => {
  const wsInterceptor = new WebSocketInterceptor();
  await wsInterceptor.setup(page);

  await page.goto('YOUR_TRADING_APP_URL');

  // Trigger an action that should emit a WebSocket message
  await page.click('button#buyStock');

  // ✅ Assert on a specific trade confirmation message
  const tradeConfirmation = await wsInterceptor.waitForMessage(msg =>
    msg.type === 'TRADE_CONFIRMATION' && msg.status === 'SUCCESS' && msg.symbol === 'AAPL'
  );

  expect(tradeConfirmation).toBeDefined();
  expect(tradeConfirmation.orderId).toBe('XYZ123');
  expect(tradeConfirmation.price).toBeGreaterThan(100);

  wsInterceptor.clearMessages(); // Clean up for subsequent steps/tests
});
Enter fullscreen mode Exit fullscreen mode

🔑 Key Takeaways
• Decouple Capture & Assert: Intercept messages into a buffer first, then assert.
• Flexible Waiting: Use waitForMessage with predicates and timeouts to handle asynchronous data and out-of-order arrival.
• Targeted Assertions: Filter by message type, then validate structure and specific values.
• Reusability: Encapsulate logic in a WebSocketInterceptor class for maintainability.

❓ Quick Summary Q&A
Q: How do you handle out-of-order WebSocket messages in Playwright?
A: Buffer all messages, then use waitForMessage with a predicate to search for the desired message regardless of its arrival order.
Q: What is the core Playwright API for WebSocket interception?
A: page.on('websocket', ws => ws.on('framereceived', event => { /* ... */ })).

TAGS: playwright, websocket, e2e testing, automation, real-time data, typescript

────────────────────────────────────────
Download our app for more advanced testing strategies!
────────────────────────────────────────

📲 𝐅𝐑𝐄𝐄 𝐌𝐎𝐁𝐈𝐋𝐄 𝐀𝐏𝐏 — 𝟔𝟎𝟎+ 𝐒𝐃𝐄𝐓 𝐐&𝐀𝐬
Practice real-world interview scenarios offline on the free QA Automation & SDET Prep app:

🤖 𝐆𝐨𝐨𝐠𝐥𝐞 𝐏𝐥𝐚𝐲 (𝐀𝐧𝐝𝐫𝐨𝐢𝐝):
https://play.google.com/store/apps/details?id=com.app.seleniuminterviewquestions&referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3Dselenium_20261028

🍎 𝐀𝐩𝐩 𝐒𝐭𝐨𝐫𝐞 (𝐢𝐎𝐒):
https://apps.apple.com/app/id6786760948?pt=128640464&ct=devto_selenium_20261028&mt=8

────────────────────────────────────────
────────────────────────────────────────

Top comments (0)