WebSocket-RPC Testing
Some services are not called with independent HTTP requests. You open a WebSocket connection, sign in once during the handshake, and then send a series of calls over that same connection.
View as MarkdownApplies to: Trial and Enterprise editions · Web app and Desktop app · Runs on the Engine only (not the Agent)
Overview
Some services are not called with independent HTTP requests. You open a WebSocket connection, sign in once during the handshake, and then send a series of calls over that same connection. The server remembers what happened earlier on the connection, and a refused call is just a message on a perfectly healthy socket.
Shift-Left Studio tests these WebSocket-RPC gateways directly. You connect to a gateway, see the actions it offers, choose which ones to test, and Studio creates an endpoint and generated tests for each. For stateful scenarios — sign in, do something, check the result on the same connection — you build a workflow with dedicated WS Connect, WS Call and WS Close blocks.
Three things make WebSocket-RPC different from HTTP, and this article explains each: there is no HTTP status code at all, a refused call is not the same as a broken connection, and the connection itself carries the credential.
Key concepts
| Term | Meaning |
|---|---|
| Gateway | The WebSocket server you are testing, reached at a ws:// or wss:// address. |
| Method (action) | The name of an operation you call on the gateway, such as agents.list. Each method becomes one endpoint. |
| Frame codec | The message format the gateway speaks. Two are supported: JSON-RPC 2.0 over WebSocket and OmniTroop. |
| Handshake | The first exchange on a new connection, which signs you in. It decides your trust level (role) for the whole connection. |
| Auth token reference | A pointer to the credential — for example key:Administrator — resolved at run time. You never type the token itself into an endpoint. |
| Access key | A credential you save once under a name (such as Administrator) and then pick by name. Stored encrypted; it is never shown again. |
| Session | One open connection. Steps in a workflow share it, so later steps see what earlier ones did. |
| Refused vs dead | A refused call is a logical failure on a working connection. A dead connection is an error. They are reported differently. |
Before you begin
- Your licence must be Trial or Enterprise. On other editions the WebSocket options do not appear.
- You need the gateway address (starting with
ws://orwss://) and, if the gateway requires one, an access key. - WebSocket-RPC tests run on the Engine (the server, including the Engine built into the Desktop app). They do not run on the Agent (local runner).
- A WebSocket-RPC endpoint does not need a project base URL. It carries its own full gateway address.
Step 1 — Connect to a gateway
- In the project tree, open the import dialog (Import API to Project, or Import API to Feature).
- Under Import Method, click WebSocket Gateway.
- Enter the Gateway address, for example
ws://127.0.0.1:18050/ws. - Under Access key (if your gateway needs one):
- Choose a saved key from the list, or No key (the gateway is open), or
- Click Add a new key, enter a name in Name this key, e.g. Administrator, paste the key into Paste the key, and click Save key.
- Click Test connection.
When the connection works, a Connected card shows:
| Line | Meaning |
|---|---|
| Recognised as | The gateway type (for example OmniTroop gateway or JSON-RPC gateway) and version, if it reports one. |
| Signed in as | The role the gateway granted this key, for example Admin — full access. |
| Response | How fast the handshake was: Fast, Normal or Slow. |
If the card says the gateway type does not confirm keys when connecting, the first real call will tell you whether the key works.
Note: Access keys are saved per project, so two projects can each have an Administrator key pointing at different gateways. Saving a key under an existing name replaces the credential without breaking any endpoint that uses it.
Step 2 — Choose which actions to test
Many gateways announce every action they support when you connect. Studio reads that list and classifies each action in the same step, so testing the connection and choosing what to test happen together.
- Read the summary under Choose what to test, for example Your gateway listed 544 actions across 90 areas. The 120 safe ones are already selected.
- Review each action's badge:
- Safe to run — a recognised read action (such as list, get or status). Selected by default.
- Changes data — the action changes the gateway when a test runs. Not selected unless you tick it.
- Changes data? — the gateway did not say what the action does, so Studio assumes the careful answer and treats it as changing data.
- With more than 12 actions, use Search actions…, Select all N, Select safe only and Clear to manage the selection.
- Click Create tests for N actions.
Important: A generated happy-path test really runs its action. The classification is deliberately cautious: only a recognised read verb counts as safe, and actions such as run, send, test, check or validate are treated as changing data because each of them executes something. An action like
files.updateListis changing;sessions.listActiveis safe. Tick a Changes data action only if you are testing against a gateway you can afford to change.
If the gateway cannot list what it supports, Studio says so; you can add methods one at a time after connecting. You can also import a JSON descriptor file: set API Type: to WebSocket-RPC, choose Upload File, and upload a file such as { "wsUrl": "ws://host/ws", "codec": "jsonrpc2", "methods": ["agents.list"] }.
Step 3 — Review an endpoint
Each action becomes a WebSocket-RPC endpoint. Open one to see or edit:
| Field | Purpose |
|---|---|
| RPC method | The action name, for example agents.list. |
| WebSocket URL | The full gateway address. Must start with ws:// or wss://. |
| Frame codec | JSON-RPC 2.0 over WebSocket or OmniTroop. |
| Auth token reference | key:NAME for a saved access key, env:NAME for an environment variable, or secretref://provider/path#field for a secret manager. |
| Default params | The params sent with the call. |
| Call timeout (ms), Idle timeout (ms), Max session (ms) | Limits for one call, for a quiet connection, and for a whole session. |
There is deliberately no place to type a literal token. A handshake token authorises the whole connection and is often long-lived and powerful; a literal would travel with every export and backup.
Step 4 — Generate and understand the tests
Generation creates, for each method:
| Test | Assertions |
|---|---|
| Happy path (not created for methods marked as changing data) | WebSocket: Handshake Succeeded, WebSocket: Call Succeeded (true), and a response-time check. |
| Unknown method rejected | WebSocket: Handshake Succeeded, WebSocket: Call Succeeded (false), grounded in the gateway's own error codes. |
| Missing required field (where the gateway names required fields) | WebSocket: Call Succeeded (false). |
Methods that change data get no happy-path test, but their negative tests are still generated, because the gateway refuses those calls before acting.
Why there is no status code assertion
A WebSocket call has no HTTP status. A status check such as "status equals 200" would pass on a call the gateway flatly refused, so generated WebSocket-RPC tests never include one. Use WebSocket: Call Succeeded instead. For negative tests that expect a refusal for authorization reasons, the handshake assertion is also left out, because a correct gateway may refuse either at the handshake or at the call.
Assertion reference
All assertions are in the WebSocket-RPC category. Each one fails with a readable message if it is used on a response that is not from a WebSocket gateway.
| Assertion | What it checks | Example |
|---|---|---|
| WebSocket: Handshake Succeeded | The connection opened and the gateway accepted the credentials. | Every positive test. |
| WebSocket: Role Equals | The trust level the gateway granted this connection. | admin, viewer |
| WebSocket: Scope Granted | The connection was granted a scope. A * grant satisfies any scope. | agents:read |
| WebSocket: Call Succeeded | The reply reported success. Set it to false to require a refusal. | true for positives, false for negatives |
| WebSocket: Error Code Equals | The error code in a refusal. Accepts text or numbers. | METHOD_NOT_FOUND, -32601 |
| WebSocket: Event Received | The gateway pushed an event with this name during the call. Keepalive messages are ignored. | agent.updated |
| WebSocket: Event Field Equals | Some pushed event has a field at this path equal to a value. | payload.status = ready |
The run result shows a verdict in words — Accepted, Refused or No reply — instead of a status code.
Step 5 — Build a stateful workflow
For scenarios that depend on connection state, use workflow blocks from the WebSocket Session group in the flow editor palette.
- Open or create a workflow.
- Drag WS Connect onto the canvas and connect it after Start. Set WebSocket URL, Frame codec and Auth reference. Optionally set Handshake overrides (JSON), Handshake timeout (ms) and Idle timeout (ms). This block publishes the granted role, scopes and server information as variables you can assert on.
- Add one or more WS Call blocks. Set RPC method and Params (JSON) (just the params), and the same WebSocket URL and Auth reference as the Connect block.
- Add WS Close at the end with the same WebSocket URL and Auth reference, then connect it to Stop.
- Save and run.
Important: A WS Call block does not open a connection by itself. It must match an earlier WS Connect block — the same URL and auth reference together name the session. If Call blocks connected on their own, each step would get its own fresh connection and a stateful test would silently become several unrelated ones.
Testing permissions. Run the same flow with two auth references — for example an administrator and a viewer. Assert WebSocket: Role Equals after each Connect, and WebSocket: Call Succeeded set to false on the call the viewer should be denied. Two auth references against one URL always produce two separate connections, so the two roles never share a session.
Refused versus dead
| What happened | Step status | Connection |
|---|---|---|
| The reply refused the call | FAILED | Stays open; later steps can continue. |
| The handshake was rejected, the connection dropped, the server closed it, or it went idle too long | ERROR | Gone. A later WS Connect opens a new one. |
| WS Close with nothing left to close | PASSED | — |
A refusal is what negative tests are designed to provoke, so it does not stop the run. WS Close passes on an already-dead connection so an earlier failure is not reported twice. All connections are closed when the run ends, however it ends.
Step 6 — Protect gateway state during a pack run (optional)
Because happy-path tests really run their actions, a pack of WebSocket-RPC tests can change live gateway state. A test run pack can be wrapped in a snapshot: Studio calls a backup action before the first test and a restore action after the last.
- If the backup fails, the pack stops and every test is recorded as not run, with the gateway's reason.
- Restore runs on every exit — pass, fail, cancel or error.
- If the restore fails, it appears as an error row on the run, not just in a log.
This setting is not yet available in the pack editor; it is set through the API (see "Automating this"). The default backup and restore action names for OmniTroop gateways come from the vendor's documentation and have not been verified against a live gateway, so confirm them for your gateway.
Troubleshooting
| Symptom | Why it happens | What to do |
|---|---|---|
| WebSocket Gateway is missing from the import dialog | Your edition does not include it, or it is turned off on your installation. | Check your licence (Trial or Enterprise), or ask your administrator. |
| The URL must start with ws:// or wss:// | The address uses http:// or has no scheme. | Enter the full WebSocket address. |
| WebSocket-RPC tests are not supported by the local runner | The test was run on the Agent. | Switch to Engine and run again. |
| Every call fails with an unauthorized error | The auth reference names a key that does not exist, or the key is wrong. | Check the key name; a missing key fails by name rather than connecting anonymously. |
| A WS Call step errors before sending anything | No earlier WS Connect with the same URL and auth reference, so there is no session to call on. | Add or match a WS Connect block. |
| A status-code assertion passes on a refused call | WebSocket calls have no status. | Replace it with WebSocket: Call Succeeded. |
| The "unknown method rejected" test fails because the call succeeded | The test is calling a real method. | Regenerate the test so it targets a method the gateway cannot know. |
Best practices
- Keep Changes data actions unticked unless you test against a disposable gateway.
- Save one access key per role and pick it by name; rotate by saving a new credential under the same name.
- Use workflows, not single tests, for anything that depends on earlier steps.
- End every connection with WS Close.
- Run the Workflow Doctor (Check workflow) on WebSocket workflows before running them.
Related articles
- Workflows
- Test run packs
- Test run
- JSON-RPC 2.0 and MCP Server Testing
- Workflow Doctor: Diagnose and Fix a Workflow
Automating this
The pack snapshot is the wsRpcGuard field on PUT /api/test-run-packs/:id:
| Field | Default | Meaning |
|---|---|---|
enabled | false | Turn the snapshot on. |
backupMethod / restoreMethod | Gateway defaults | The actions called before the first test and after the last. |
backupParams / restoreParams | {} / unset | Params for each. When restoreParams is unset, the backup's reply is sent back. Values may use {{backup.<path>}}. |
restoreWhen | always | onSuccess leaves a failed run's state in place for inspection. |
requireBackup | true | A failed backup stops the pack. |
Saved access keys are listed with GET /api/wsrpc/keys (names only) and saved with POST /api/wsrpc/keys.
For administrators (self-hosted installations)
| Setting | Default | What switching it does |
|---|---|---|
WSRPC_TRANSPORT_ENABLED | true | Set to false to remove WebSocket-RPC testing entirely. The import option, endpoints and workflow blocks become unavailable. |
Previous
JSON-RPC 2.0 and MCP Server Testing
Product documentation
Next
Testing Server-Sent Events (Streaming) Responses
Product documentation
Related articles
- Generate Endpoint Test Cases · Product documentation
- Test Cases: Open, Edit, and Save · Product documentation
- Test Configuration · Product documentation
- Execution Mode · Product documentation
- Run Tests and Review Results · Product documentation
- Test Generation Settings · Product documentation
Next steps
- Getting started · Install + connect your spec
- Configuration fundamentals · Stabilize runs
- Initial configuration · Users, licensing, projects
- Release notes · Updates and fixes
Still stuck?
Tell us what you’re trying to accomplish and we’ll point you to the right setup—installation, auth, or CI/CD wiring.