Websocket Tester

Test WebSocket connections, message flows, and real-time features. Connect to endpoints, send/receive messages, test reconnection logic, measure latency, validate message schemas, and load test concurrent connections.

Audits

Pass

Install

openclaw skills install websocket-tester

WebSocket Tester

Test WebSocket endpoints without a browser. Connect, send messages, validate responses, test reconnection, measure latency, and load test concurrent connections — for chat, real-time updates, game servers, IoT, and streaming APIs.

Use when: "test websocket", "websocket not connecting", "debug real-time", "ws connection issues", "websocket load test", "test socket.io", "chat server testing", or when building/debugging real-time features.

Commands

1. connect — Test WebSocket Connection

# Basic connection test with wscat (Node.js)
npx wscat -c "wss://$HOST/ws" 2>&1 &
WSCAT_PID=$!
sleep 3
kill $WSCAT_PID 2>/dev/null

# Or with websocat (Rust)
echo "test" | websocat "wss://$HOST/ws" 2>&1

# Or with Python
python3 -c "
import asyncio, websockets, time, json

async def test_connection():
    url = 'wss://$HOST/ws'
    start = time.time()
    try:
        async with websockets.connect(url, close_timeout=5) as ws:
            latency = (time.time() - start) * 1000
            print(f'✅ Connected in {latency:.0f}ms')
            print(f'Protocol: {ws.subprotocol or \"none\"}')
            print(f'Headers: {dict(ws.response_headers)}')

            # Test ping/pong
            pong = await ws.ping()
            await pong
            print('✅ Ping/Pong working')

            # Listen for initial messages (server hello, auth challenge)
            try:
                msg = await asyncio.wait_for(ws.recv(), timeout=3)
                print(f'Server sent: {msg[:200]}')
            except asyncio.TimeoutError:
                print('No initial message from server (normal for some protocols)')

    except websockets.exceptions.InvalidStatusCode as e:
        print(f'❌ Connection rejected: HTTP {e.status_code}')
    except ConnectionRefusedError:
        print(f'❌ Connection refused — server not listening on WebSocket endpoint')
    except Exception as e:
        print(f'❌ Error: {e}')

asyncio.run(test_connection())
" 2>&1

2. test-messages — Send and Validate Messages

import asyncio, websockets, json, time

async def test_messages():
    async with websockets.connect('wss://$HOST/ws') as ws:
        # Test 1: Send and receive
        test_msg = json.dumps({'type': 'ping', 'data': 'hello'})
        await ws.send(test_msg)
        print(f'→ Sent: {test_msg}')

        response = await asyncio.wait_for(ws.recv(), timeout=5)
        print(f'← Received: {response[:200]}')

        # Validate response schema
        try:
            data = json.loads(response)
            assert 'type' in data, 'Missing type field'
            print('✅ Response is valid JSON with type field')
        except json.JSONDecodeError:
            print('⚠️ Response is not JSON — might be binary or plain text')
        except AssertionError as e:
            print(f'❌ Schema validation: {e}')

        # Test 2: Measure round-trip latency
        latencies = []
        for i in range(10):
            start = time.time()
            await ws.send(json.dumps({'type': 'ping', 'seq': i}))
            await asyncio.wait_for(ws.recv(), timeout=5)
            latencies.append((time.time() - start) * 1000)

        avg = sum(latencies) / len(latencies)
        p95 = sorted(latencies)[int(len(latencies) * 0.95)]
        print(f'\\nLatency (10 round-trips): avg={avg:.1f}ms, p95={p95:.1f}ms')

asyncio.run(test_messages())

3. reconnect — Test Reconnection Logic

async def test_reconnection():
    """Simulate connection drops and verify client reconnects"""

    # Test 1: Server closes connection
    async with websockets.connect('wss://$HOST/ws') as ws:
        print('Connected. Sending close frame...')
        await ws.close(1001, 'Testing reconnection')
        print(f'Close code: {ws.close_code}, reason: {ws.close_reason}')

    # Test 2: Reconnect and verify state
    async with websockets.connect('wss://$HOST/ws') as ws:
        print('✅ Reconnected successfully')
        # Check if server restored session state
        await ws.send(json.dumps({'type': 'get_state'}))
        state = await asyncio.wait_for(ws.recv(), timeout=5)
        print(f'State after reconnect: {state[:200]}')

    # Test 3: Connection with auth token
    async with websockets.connect(
        'wss://$HOST/ws',
        extra_headers={'Authorization': f'Bearer {TOKEN}'}
    ) as ws:
        print('✅ Authenticated connection')

4. load-test — Concurrent Connection Load Test

import asyncio, websockets, time, statistics

async def load_test(url, num_connections, duration_sec, messages_per_sec):
    """Load test WebSocket endpoint"""
    results = {
        'connected': 0,
        'failed': 0,
        'messages_sent': 0,
        'messages_received': 0,
        'latencies': [],
        'errors': [],
    }

    async def client(client_id):
        try:
            async with websockets.connect(url, close_timeout=5) as ws:
                results['connected'] += 1
                end_time = time.time() + duration_sec

                while time.time() < end_time:
                    start = time.time()
                    await ws.send(json.dumps({
                        'type': 'message',
                        'client': client_id,
                        'ts': start
                    }))
                    results['messages_sent'] += 1

                    try:
                        await asyncio.wait_for(ws.recv(), timeout=5)
                        results['messages_received'] += 1
                        results['latencies'].append((time.time() - start) * 1000)
                    except asyncio.TimeoutError:
                        results['errors'].append(f'Client {client_id}: timeout')

                    await asyncio.sleep(1.0 / messages_per_sec)

        except Exception as e:
            results['failed'] += 1
            results['errors'].append(f'Client {client_id}: {e}')

    tasks = [client(i) for i in range(num_connections)]
    await asyncio.gather(*tasks)

    # Report
    lats = results['latencies']
    print(f'\\n=== WebSocket Load Test Results ===')
    print(f'Connections: {results["connected"]}/{num_connections} ({results["failed"]} failed)')
    print(f'Messages: {results["messages_sent"]} sent, {results["messages_received"]} received')
    if lats:
        print(f'Latency: avg={statistics.mean(lats):.1f}ms, '
              f'p50={statistics.median(lats):.1f}ms, '
              f'p95={sorted(lats)[int(len(lats)*0.95)]:.1f}ms, '
              f'p99={sorted(lats)[int(len(lats)*0.99)]:.1f}ms')
    if results['errors']:
        print(f'Errors ({len(results["errors"])}):')
        for e in results['errors'][:5]:
            print(f'  - {e}')

# Run: 100 concurrent connections, 60s, 1 msg/sec each
asyncio.run(load_test('wss://$HOST/ws', 100, 60, 1))

5. diagnose — Common WebSocket Issues

Connection failures:

  • HTTP 101 upgrade rejected → check server supports WebSocket
  • 403 on connect → CORS or auth issue
  • Connection timeout → firewall blocking WS, or wrong port
  • SSL/TLS error → certificate issue on wss://

Message issues:

  • Messages not received → check subscription/room join logic
  • Duplicate messages → missing deduplication, reconnect replay
  • Out-of-order → add sequence numbers, reorder buffer
  • Large messages rejected → check server max frame size

Performance:

  • High latency → check server-side processing time
  • Connection drops → heartbeat/ping interval too long
  • Memory growth → connection leak (not closing on client disconnect)