v1.17.0
gRPC requests can load their schema straight from .proto files, without server reflection or a compiled descriptor set. GraphQL subscriptions work over SSE and WebSocket.
The parser is stricter as well. A few mistakes that 1.16.1 let through, usually by quietly dropping a workflow step or a directive, are parse errors in this release. If you have workflows, gRPC requests or WebSocket tests, read the upgrade notes at the end before you update.
gRPC with .proto files
Point @grpc-proto at your .proto files and Resterm compiles them when it sends the request, so there's no protoc step.
### Say Hello
# @grpc acme.v1.Greeter/SayHello
# @grpc-proto ./proto/acme/v1/greeter.proto
# @grpc-import-path ./proto
GRPC {{grpc.host}}
{"name": "Ada"}Here greeter.proto imports acme/v1/types.proto from proto/. If a file doesn't compile, the error tells you where:
error[protocol]: compile proto files
╰─> acme/v1/types.proto:6:3
╰─> field acme.v1.Greeting.text: unknown type strng
- Without
@grpc-import-path, the folder of each listed file is its import path. Add import paths when imports start from a root folder, as withprotoc -I. Both directives can repeat, and paths can use{{variables}}. - The standard
google/protobuf/*.protofiles are built in. Others, such asgoogle/api/annotations.proto, have to be on an import path. - Resterm reads the files on every send, so an edit to a
.protofile applies to the next request without a restart. - proto2, proto3 and edition 2023 files work. Edition 2024 isn't supported yet.
- A request can use
@grpc-protoor@grpc-descriptor, but not both. - The navigator lists the
.protofiles your requests use, and completion after@grpc-protosuggests only.protofiles. Explain shows each proto file and import path. resterm collection exportbundles the proto files together with every file they import.
See the proto files docs.
GraphQL subscriptions
@graphql works with @sse and @websocket. @operation, @query and @variables behave the same as over HTTP.
1.16.1 dropped @graphql on these requests without a warning. An SSE request went out with an empty body, and a WebSocket opened without subscribing to anything.
Over SSE
### Order events
# @graphql
# @operation OrderEvents
# @sse duration=30s idle=10s
# @assert stream.summary().reason == "complete"
POST {{graphql.endpoint}}
subscription OrderEvents($id: ID!) {
orderEvents(id: $id) { status }
}
# @variables
{ "id": "{{order.id}}" }The query is sent like any other GraphQL request, in the body for POST or as query parameters for GET. The stream ends at the first complete event and the summary reason is complete. If the server closes the stream before that, the reason is eof. This is the distinct connections mode of GraphQL over SSE.
Over WebSocket
### Order events over WebSocket
# @graphql
# @operation OrderEvents
# @query < ./queries/order-events.graphql
# @variables < ./queries/order-events.json
# @websocket
GET {{graphql.wsEndpoint}}
Authorization: Bearer {{token}}Resterm uses the graphql-transport-ws subprotocol. It sends connection_init, waits for connection_ack, subscribes and answers each ping. When the operation ends with complete or error, Resterm closes the socket with code 1000. @ws steps run after the subscription starts, so # @ws wait 20s keeps listening for up to 20 seconds.
A GraphQL error doesn't fail the request, the same as over HTTP. Check for it with @assert. A message that breaks the protocol does fail it, with exit code 26. Here is the stream summary after a server sent a next message whose payload wasn't an object:
closeCode: 4400,
closeReason: "server sent next without an object payload",
closedBy: "error",
errorClass: "protocol",
- The request also fails if the server closes the socket before
connection_ack(exit code 26), or ifidleruns out before it arrives (exit code 20). After the ack, a close with any code, such as 4403, is a normal ending. - Only
graphql-transport-wsis supported, not the oldersubscriptions-transport-ws. On a@graphqlrequest, any othersubprotocolsvalue is a parse error. connection_inithas no payload, so send credentials in the request headers.- For a WebSocket request, Explain shows the
subscribemessage Resterm is going to send.
WebSocket
These changes apply to every WebSocket request, not only GraphQL ones.
Invalid handshakes fail
If the server accepts the upgrade with a subprotocol Resterm never offered, the request fails with exit code 26:
error[protocol]: dial websocket
╰─> failed to WebSocket dial
╰─> WebSocket protocol violation: unexpected Sec-WebSocket-Protocol from server: "chat"
1.16.1 showed 101 Switching Protocols, skipped the @ws steps and passed.
Closing
After @ws close or the last step, Resterm waits up to 5 seconds for the server to answer the close, and anything that arrives in that time goes into the transcript. closedBy is client, and closeCode and closeReason are the ones Resterm sent, even when the server answers late, with its own code, or not at all.
In 1.16.1 a server that never answered failed the request with exit code 20 after 5 seconds. A server that sent one more message and then closed with a different code, such as 1001, failed it with closedBy: "error".
Why a session ended
In the Stream tab, the row that ends a WebSocket session shows the reason, for example idle timeout after 1s.
Stricter request files
Workflows
A line that starts a request also ends the workflow above it. That can be a request directive such as @name, a > script line or plain text. 1.16.1 dropped every @step after that line with a warning and ran a shorter workflow:
# @workflow demo
# @step First using=First
# @name Oops
# @step Last using=LastWARN demo.http:4: @step is not valid in the current context and was ignored
PASS WORKFLOW demo [1 passed, 0 failed, 0 skipped in 4.832666ms]
1. PASS First [200 OK in 3.744583ms]
In 1.17.0 the file doesn't run:
error[parse]: @step is outside the workflow because @name on line 3 starts a request
--> demo.http:4:3
|
4 | # @step Last using=Last
| ^
The same goes for @if, @switch and their branches. Blank lines and plain comments keep the workflow open.
A workflow line with no #, like @step Last using=Last, used to become a file variable, and the step was gone without any warning. Now it fails with @step has no comment marker. Write it as # @step.
See the workflows docs.
One protocol per request
@grpcand the other@grpc-*directives need aGRPCrequest line. On aGETline, 1.16.1 turned the request into a gRPC call that failed withgrpc target not specified. On an SSE or WebSocket request it ignored them. The error is now@grpc requires a GRPC request line.@ssetogether with@websocketor a@wsstep is an error. Before, the request ran as a WebSocket.@graphql,@sse,@websocketand@wscan't be used on aGRPCline.@graphqlused to be ignored there, and@sseor@websocketsent an HTTP stream instead of the gRPC call.- Turning a protocol off, as in
@sse off, never conflicts.
Options without a value
In most directives a bare key means true. For an option that expects a value, that was almost always a typo, so it's an error now and the caret points at the key:
error[parse]: @step option "vars.token" needs a value. Write it as vars.token=value
--> login.http:2:25
|
2 | # @step Auth using=Auth vars.token
| ^
1.16.1 ran that workflow and passed. The rule covers @poll, @retry, @retry-backoff, @profile, @ssh, @k8s, @sse, @websocket, @workflow, @step, @if, @mock, @match and @expect. Switches such as compression and persist still work without a value.
Better errors
- A missing
@usemodule saysmodule missing.rts not foundand points at the@useline. - A request that uses a broken shared
@sshprofile says what's wrong with it, as inssh profile "jump" is invalid: line 1: @ssh global scope requires host. Shared@k8sprofiles with a repeated option work the same way, and every bad option on an@sshor@k8sline is listed. @retry-backoff 1ssays@retry-backoff must start with exponential(initial, max), got "1s".- A
@settingor@useplaced above@workflowgets the hintMove it below @workflow to share it.
Other fixes
- Completion after
@websocketofferssubprotocolas well assubprotocols. - Parse warnings and errors are listed in line order.
Upgrading from 1.16.1
Some files that used to run now stop with a parse error and exit code 2. Check for these:
- In a workflow, a
@step,@ifor@switchafter a line that starts a request, such as@nameor plain text. Move the steps above that line. A workflow line without#fails too. @grpc-*directives on a request that isn'tGRPC,@ssetogether with@websocketor@ws, and@graphql,@sse,@websocketor@wson aGRPCline. Remove the directive that doesn't belong.- A bare key for an option that takes a value, such as
vars.tokenon@step,subprotocolson@websocketornameon@mock. Give it a value or delete it. A mock server with such a line won't start. @graphqlwith@websocketand asubprotocolsvalue other thangraphql-transport-ws.
These changed without a parse error:
@graphqlon an SSE or WebSocket request sends the operation instead of being dropped.- A WebSocket upgrade with a subprotocol Resterm didn't offer fails with exit code
26. - After Resterm closes a WebSocket,
closedByisclienteven when the server answers late, with its own code, or never. Update asserts that expect"server","timeout"or"error"in those cases. - Some messages changed: a missing
@usemodule, invalid shared@sshand@k8sprofiles,@retry-backoff, and empty quotes on@runand@grpc-metadata. A missing module is a script error now, so a template that loads one exits with24instead of25. Update CI checks that match the old text.
Upgrading from 1.16.0 or earlier
If you're on 1.16.0 or earlier, also read the 1.16.1 release notes. macOS 12 is no longer supported, and some TLS-inspecting proxies drop HTTPS connections. The 1.16.0 notes cover the RestermScript changes.