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:
open
message
close
error
ping/pong
Triggered when the connection is established. socket . on ( 'open' , function open () {
console . log ( 'WebSocket connection opened' );
socket . send ( 'Hello Server!' );
});
Triggered when a message is received from the server. socket . on ( 'message' , function incoming ( data ) {
console . log ( `Received message: ${ data } ` );
// Echo back
socket . send ( data );
});
Triggered when the connection is closed. socket . on ( 'close' , function close () {
console . log ( 'Connection closed' );
});
Triggered when an error occurs. socket . on ( 'error' , function ( e ) {
if ( e . error () != "websocket: close sent" ) {
console . log ( 'Unexpected error:' , e . error ());
}
});
Triggered when ping/pong frames are received. socket . on ( 'ping' , function () {
console . log ( 'PING received' );
});
socket . on ( 'pong' , function () {
console . log ( 'PONG received' );
});
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
Parameter Type Description headersObject Custom HTTP headers for the handshake tagsObject Tags for metrics categorization jarCookieJar Cookie jar for authentication compressionString Compression 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
Echo Server Test
Load Testing
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
Always close connections explicitly to free up resources
Handle all error cases to ensure test reliability
Use tags to categorize different WebSocket endpoints
Implement timeouts to prevent infinite connections
Monitor message rates to avoid overwhelming the server
Test reconnection logic by intentionally closing connections