Q32 of 40 · Karate

How do you handle WebSockets in Karate?

KarateSeniorkaratewebsocketsreal-timeasyncapi-testing

Short answer

Short answer: Use * def ws = karate.webSocket(url, messageHandler) to open a connection, ws.send(message) to send frames, and karate.listen(timeout) to receive a message. The handler function is called on each incoming message and can set a Karate variable to resolve the listen call. Assert on the received message with normal match syntax.

Detail

Karate's WebSocket support is built in (since 1.0) via the karate.webSocket() API:

# Open a WebSocket connection
* def handler = function(msg){ karate.signal(msg) }
* def ws = karate.webSocket('wss://api.example.com/ws/prices', handler)

# Send a subscription message
* ws.send('{ "action": "subscribe", "channel": "BTC-USD" }')

# Wait for a message (max 5 seconds)
* def message = karate.listen(5000)

# Assert the received message
* match message == { channel: 'BTC-USD', price: '#number', timestamp: '#string' }

# Close the connection
* ws.close()

karate.signal: called from inside the handler to pass the received message back to the scenario. karate.listen(timeout) blocks until signal is called (or timeout).

Multiple messages: to receive N messages, accumulate them in an array inside the handler and signal when the count reaches N.

STOMP over WebSocket (Spring Boot): Karate doesn't have native STOMP support. Use the stomp.js library via embedded JavaScript, or test STOMP endpoints via the HTTP-based fallback (polling endpoint) if the API exposes one.

Testing presence and real-time features: WebSocket tests are inherently time-sensitive — set realistic timeouts (5-30s) and handle the case where the message arrives before the listen() call (use a shared variable in the handler).

// EXAMPLE

websocket-test.feature

Feature: Real-time price feed via WebSocket

  Scenario: Subscribe to BTC price feed and receive first update
    * def received = []
    * def handler = function(msg) {
        karate.log('received:', msg);
        received.push(msg);
        if (received.length >= 1) {
          karate.signal(received[0]);    // signal on first message
        }
      }

    # Open connection
    * def ws = karate.webSocket('wss://api.example.com/ws/prices', handler)

    # Subscribe
    * ws.send('{ "action": "subscribe", "channel": "BTC-USD" }')

    # Wait up to 10s for first price update
    * def firstUpdate = karate.listen(10000)

    # Assert message structure
    * match firstUpdate == {
        channel:   'BTC-USD',
        price:     '#number',
        bid:       '#number',
        ask:       '#number',
        timestamp: '#string'
      }
    * match firstUpdate.price == '#? _ > 0'

    # Unsubscribe and close
    * ws.send('{ "action": "unsubscribe", "channel": "BTC-USD" }')
    * ws.close()

// WHAT INTERVIEWERS LOOK FOR

Correct karate.webSocket() + karate.signal() + karate.listen() pattern, handling asynchronous message receipt, and closing the connection after the test. The accumulated-messages pattern for receiving N messages is a senior detail.

// COMMON PITFALL

Not closing the WebSocket connection after the test — this leaves connections open, eventually exhausting the server's connection pool and causing unrelated tests to fail with connection refused errors.