Product documentation
Updated September 27, 2026

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 Markdown

Applies 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

TermMeaning
GatewayThe 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 codecThe message format the gateway speaks. Two are supported: JSON-RPC 2.0 over WebSocket and OmniTroop.
HandshakeThe first exchange on a new connection, which signs you in. It decides your trust level (role) for the whole connection.
Auth token referenceA pointer to the credential — for example key:Administrator — resolved at run time. You never type the token itself into an endpoint.
Access keyA credential you save once under a name (such as Administrator) and then pick by name. Stored encrypted; it is never shown again.
SessionOne open connection. Steps in a workflow share it, so later steps see what earlier ones did.
Refused vs deadA 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:// or wss://) 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

  1. In the project tree, open the import dialog (Import API to Project, or Import API to Feature).
  2. Under Import Method, click WebSocket Gateway.
  3. Enter the Gateway address, for example ws://127.0.0.1:18050/ws.
  4. 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.
  5. Click Test connection.

When the connection works, a Connected card shows:

LineMeaning
Recognised asThe gateway type (for example OmniTroop gateway or JSON-RPC gateway) and version, if it reports one.
Signed in asThe role the gateway granted this key, for example Admin — full access.
ResponseHow 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.

  1. 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.
  2. 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.
  3. With more than 12 actions, use Search actions…, Select all N, Select safe only and Clear to manage the selection.
  4. 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.updateList is changing; sessions.listActive is 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:

FieldPurpose
RPC methodThe action name, for example agents.list.
WebSocket URLThe full gateway address. Must start with ws:// or wss://.
Frame codecJSON-RPC 2.0 over WebSocket or OmniTroop.
Auth token referencekey:NAME for a saved access key, env:NAME for an environment variable, or secretref://provider/path#field for a secret manager.
Default paramsThe 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:

TestAssertions
Happy path (not created for methods marked as changing data)WebSocket: Handshake Succeeded, WebSocket: Call Succeeded (true), and a response-time check.
Unknown method rejectedWebSocket: 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.

AssertionWhat it checksExample
WebSocket: Handshake SucceededThe connection opened and the gateway accepted the credentials.Every positive test.
WebSocket: Role EqualsThe trust level the gateway granted this connection.admin, viewer
WebSocket: Scope GrantedThe connection was granted a scope. A * grant satisfies any scope.agents:read
WebSocket: Call SucceededThe reply reported success. Set it to false to require a refusal.true for positives, false for negatives
WebSocket: Error Code EqualsThe error code in a refusal. Accepts text or numbers.METHOD_NOT_FOUND, -32601
WebSocket: Event ReceivedThe gateway pushed an event with this name during the call. Keepalive messages are ignored.agent.updated
WebSocket: Event Field EqualsSome 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.

  1. Open or create a workflow.
  2. 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.
  3. 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.
  4. Add WS Close at the end with the same WebSocket URL and Auth reference, then connect it to Stop.
  5. 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 happenedStep statusConnection
The reply refused the callFAILEDStays open; later steps can continue.
The handshake was rejected, the connection dropped, the server closed it, or it went idle too longERRORGone. A later WS Connect opens a new one.
WS Close with nothing left to closePASSED—

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

SymptomWhy it happensWhat to do
WebSocket Gateway is missing from the import dialogYour 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 runnerThe test was run on the Agent.Switch to Engine and run again.
Every call fails with an unauthorized errorThe 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 anythingNo 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 callWebSocket calls have no status.Replace it with WebSocket: Call Succeeded.
The "unknown method rejected" test fails because the call succeededThe 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.

Automating this

The pack snapshot is the wsRpcGuard field on PUT /api/test-run-packs/:id:

FieldDefaultMeaning
enabledfalseTurn the snapshot on.
backupMethod / restoreMethodGateway defaultsThe actions called before the first test and after the last.
backupParams / restoreParams{} / unsetParams for each. When restoreParams is unset, the backup's reply is sent back. Values may use {{backup.<path>}}.
restoreWhenalwaysonSuccess leaves a failed run's state in place for inspection.
requireBackuptrueA 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)

SettingDefaultWhat switching it does
WSRPC_TRANSPORT_ENABLEDtrueSet to false to remove WebSocket-RPC testing entirely. The import option, endpoints and workflow blocks become unavailable.

Related articles

Next steps

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.