Skip to main content
k6 provides comprehensive support for testing WebSocket connections through the k6/ws module, enabling you to test real-time communication, bidirectional messaging, and WebSocket server performance.

Basic WebSocket Connection

Connect to a WebSocket server and handle events:
import ws from "k6/ws";
import { check } from "k6";

export default function () {
  const url = "ws://echo.websocket.org";
  const params = { tags: { my_tag: "hello" } };

  const response = ws.connect(url, params, function (socket) {
    socket.on('open', function open() {
      console.log('connected');
      socket.send(Date.now());
    });

    socket.on('message', function incoming(data) {
      console.log(`Received: ${data}`);
    });

    socket.on('close', function close() {
      console.log('disconnected');
    });

    socket.on('error', function (e) {
      console.log('Error:', e.error());
    });

    // Close connection after 2 seconds
    socket.setTimeout(function () {
      console.log('2 seconds passed, closing the socket');
      socket.close();
    }, 2000);
  });

  check(response, { "status is 101": (r) => r && r.status === 101 });
}
The WebSocket connection blocks the VU execution until the socket is closed or an error occurs. This is by design to maintain state during the connection lifecycle.

WebSocket Events

The socket object emits several events you can listen to:
Triggered when the connection is established.
socket.on('open', function open() {
  console.log('WebSocket connection opened');
  socket.send('Hello Server!');
});

Sending Messages

Send messages to the WebSocket server:
import ws from "k6/ws";

export default function () {
  const url = "ws://echo.websocket.org";

  ws.connect(url, function (socket) {
    socket.on('open', function () {
      // Send text message
      socket.send('Hello World!');

      // Send JSON data
      socket.send(JSON.stringify({
        type: 'message',
        payload: { user: 'test', content: 'Hello' }
      }));

      // Send binary data
      socket.send(new Uint8Array([1, 2, 3, 4]).buffer);
    });

    socket.on('message', function (data) {
      console.log('Echo:', data);
      socket.close();
    });
  });
}

Ping/Pong and Keep-Alive

Implement keep-alive mechanisms using ping/pong frames:
import ws from "k6/ws";
import { check } from "k6";

export default function () {
  const url = "ws://echo.websocket.org";
  const params = { tags: { my_tag: "keepalive" } };

  const response = ws.connect(url, params, function (socket) {
    socket.on('open', function open() {
      console.log('connected');
      
      // Send ping every 1 second
      socket.setInterval(function timeout() {
        socket.ping();
        console.log("Pinging every 1sec");
      }, 1000);
    });

    socket.on('pong', function () {
      console.log('PONG received - connection alive');
    });

    socket.on('message', function incoming(data) {
      console.log(`Roundtrip time: ${Date.now() - data} ms`);
      
      // Send another message after 500ms
      socket.setTimeout(function timeout() {
        socket.send(Date.now());
      }, 500);
    });

    socket.on('close', function close() {
      console.log('disconnected');
    });

    // Close after 5 seconds
    socket.setTimeout(function () {
      socket.close();
    }, 5000);
  });

  check(response, { "status is 101": (r) => r && r.status === 101 });
}

Multiple Event Handlers

You can register multiple handlers for the same event:
socket.on('pong', function () {
  console.log('First PONG handler');
});

socket.on('pong', function () {
  console.log('Second PONG handler');
});

// Both handlers will be called when a pong is received

Connection Parameters

Configure WebSocket connections with various parameters:
import ws from "k6/ws";

export default function () {
  const url = "wss://secure.websocket.org";
  
  const params = {
    headers: {
      "Authorization": "Bearer token123",
      "X-Custom-Header": "value",
    },
    tags: {
      websocket_type: "secure",
      endpoint: "production",
    },
    jar: http.cookieJar(), // Use shared cookie jar
    compression: "deflate", // Enable compression
  };

  ws.connect(url, params, function (socket) {
    // Handle connection
  });
}

Available Parameters

ParameterTypeDescription
headersObjectCustom HTTP headers for the handshake
tagsObjectTags for metrics categorization
jarCookieJarCookie jar for authentication
compressionStringCompression algorithm (e.g., “deflate”)

Response Object

The ws.connect() function returns a response object with connection information:
const response = ws.connect(url, function (socket) {
  // ...
});

console.log('Status:', response.status);      // 101 for successful upgrade
console.log('URL:', response.url);            // WebSocket URL
console.log('Headers:', response.headers);    // Response headers
console.log('Body:', response.body);          // Response body (if any)

Testing Scenarios

import ws from "k6/ws";
import { check } from "k6";
import { Counter } from "k6/metrics";

const messagesReceived = new Counter('messages_received');

export default function () {
  const url = "ws://echo.websocket.org";
  const testMessages = ['Hello', 'World', 'Test'];
  let receivedCount = 0;

  ws.connect(url, function (socket) {
    socket.on('open', function () {
      testMessages.forEach(msg => socket.send(msg));
    });

    socket.on('message', function (data) {
      receivedCount++;
      messagesReceived.add(1);
      
      check(data, {
        'message echoed correctly': (d) => testMessages.includes(d),
      });

      if (receivedCount === testMessages.length) {
        socket.close();
      }
    });
  });
}

Error Handling

Properly handle errors and connection failures:
import ws from "k6/ws";
import { check } from "k6";

export default function () {
  const url = "ws://echo.websocket.org";

  const response = ws.connect(url, function (socket) {
    socket.on('error', function (e) {
      const errorMsg = e.error();
      
      // Ignore expected close errors
      if (errorMsg === "websocket: close sent") {
        return;
      }

      console.error('WebSocket error:', errorMsg);
      
      // Record error for analysis
      check(null, {
        'no unexpected errors': false,
      });
    });

    socket.on('close', function () {
      console.log('Connection closed gracefully');
    });

    // Test logic here
    socket.setTimeout(() => socket.close(), 2000);
  });

  // Verify successful connection
  check(response, {
    'connection established': (r) => r && r.status === 101,
    'no connection error': (r) => !r.error,
  });
}
WebSocket connections in the init context are not supported. Always use WebSockets within the default function or other VU-scoped functions.

Metrics

k6 automatically collects WebSocket metrics:
  • ws_connecting - Time spent establishing the connection
  • ws_session_duration - Total session duration
  • ws_msgs_sent - Number of messages sent
  • ws_msgs_received - Number of messages received
  • ws_ping - Ping timing

Best Practices

  1. Always close connections explicitly to free up resources
  2. Handle all error cases to ensure test reliability
  3. Use tags to categorize different WebSocket endpoints
  4. Implement timeouts to prevent infinite connections
  5. Monitor message rates to avoid overwhelming the server
  6. Test reconnection logic by intentionally closing connections

Build docs developers (and LLMs) love