Testing Server-Sent Events (Streaming) Responses
Test endpoints that answer with text/event-stream: see each event in the response viewer, assert on events, and stream with limits on events, duration and idle time.
View as MarkdownApplies to: All editions · Web app and Desktop app
Overview
Some endpoints do not return one response body. They answer with text/event-stream and send a series of events over time — an AI model streaming its answer word by word, a live feed of updates, or an MCP server replying over Streamable HTTP. These are Server-Sent Events (SSE).
Shift-Left Studio reads these streams into a numbered list of events that you can see in the response viewer and assert on. You can check how many events arrived, what any or the final event contained, what the whole stream said once joined together, and a field inside any event's JSON.
For most streams you do not need to configure anything: Studio notices the event-stream response and parses it automatically. If you also need to measure how quickly the first event arrived, or to stop reading a long stream on your own terms, you can turn on streaming mode for a test.
Key concepts
| Term | Meaning |
|---|---|
| Event | One message in the stream. It has a payload (the data), and optionally a type (event) and an id. |
| Buffered mode | The default. Studio reads the whole response, then parses the events. Every SSE assertion except first-event latency works. |
| Streaming mode | Opt-in per test. Studio reads the events as they arrive, which lets it measure first-event latency and stop the stream deliberately. |
| Sentinel | A marker that ends a stream, such as [DONE] at the end of many AI completions. |
| Whole stream | The payloads of every event joined together. Needed when text is split across events. |
Before you begin
- The endpoint must respond with the content type
text/event-stream. - SSE options and assertions apply to REST tests. SOAP and GraphQL tests do not offer them, and WebSocket-RPC is not an HTTP response at all. JSON-RPC and MCP replies that arrive as an event stream are unwrapped automatically — you assert on them with the JSON-RPC assertions.
- You need permission to edit tests.
Step 1 — Run the test and look at the events
- Open the test and run it as usual.
- Open the response. An event-stream response is shown as a numbered event list instead of raw text: each event shows its number, its type (message if none was given), its
idif present, and its payload, pretty-printed where it is JSON. - The header shows how many events arrived, for example 42 events.
- Click Raw to see the original stream text; click Auto to return to the event list.
If Studio kept only part of a very large stream, a Truncated for storage badge appears, and individual events that were cut short are marked clipped. The event count still reflects the full stream, so a count assertion is never fooled by the storage limit.
Step 2 — Add SSE assertions
- Open the test's assertions and add a new assertion.
- Choose from the Streaming (SSE) category.
- Enter the expected value and save the test.
Examples:
| Goal | Assertion | Value |
|---|---|---|
| The model finished its answer | SSE: Final Event Contains | [DONE] |
| The answer mentions the order number | SSE: Whole Stream Contains | Order 1042 |
| At least one progress update arrived | SSE: Event Count Greater Than | 0 |
| The first chunk identifies the speaker | SSE: Event Field Equals | choices[0].delta.role equals assistant |
| An error event appeared somewhere | SSE: Any Event Contains | "error" |
Tip: For AI completions, reach for SSE: Whole Stream Contains. A model streams "Hello" and " world" as separate events, so no single event contains "Hello world" — only the joined stream does.
Step 3 — Turn on streaming mode (optional)
Turn on streaming mode when you need either of these:
- First-event latency — whether the service started responding promptly. Total duration cannot tell you this: a stream that answers instantly and then runs for 30 seconds takes as long as one that stalls for 29 seconds and then sends everything at once.
- Deliberate termination — stop reading on a sentinel, after a number of events, after a time limit, or when the stream goes quiet, instead of waiting for the connection to time out.
To turn it on:
-
Open the test in the test editor and go to its request configuration.
-
Tick Stream the response (Server-Sent Events).
-
Optionally set the stop conditions. Leave a field empty to use its default:
Option Default Effect Max events 200 Stop after this many events. Max duration (ms) 60000 Hard limit on how long the stream is read. Idle timeout (ms) 15000 Stop if the stream goes quiet for this long. Stop when an event contains — End the read as soon as an event's payload contains this text, for example [DONE]. -
Add SSE: First Event Within (ms) with your latency target, for example
2000. -
Save and run.
Note: In streaming mode, the test's request timeout covers only the wait for the response to start. How long the stream itself is read is controlled by Max duration (ms), so you do not need to raise the request timeout for long streams.
Every streaming run records why reading stopped — the stream ended on its own, a stop phrase was found, the event limit or duration limit was reached, the stream went idle, or an error occurred. This lets you tell a complete short stream apart from one that was cut off.
Assertion reference
| Assertion | What it checks | Mode |
|---|---|---|
| SSE: Event Count Equals | The stream delivered exactly this many events. | Both |
| SSE: Event Count Greater Than | The stream delivered more than this many events. | Both |
| SSE: Any Event Contains | At least one event's payload contains the text. | Both |
| SSE: Final Event Contains | The last event contains the text (for example [DONE]). | Both |
| SSE: Whole Stream Contains | All event payloads joined together contain the text. | Both |
| SSE: Event Field Equals | Some event's JSON payload has a field at this path equal to a value. Use brackets for array positions, for example choices[0].delta.role. | Both |
| SSE: First Event Within (ms) | The first event arrived within this many milliseconds. | Streaming only |
If you add an SSE assertion to a test whose response is not an event stream, it fails with a clear message rather than passing silently. SSE: First Event Within (ms) on a buffered test tells you to turn on streaming mode. Dynamic tokens such as {{row.EXPECTED_EVENTS}} work in SSE assertion values, so data-driven tests can expect a different stream per row.
Generated tests
Test generation never adds SSE assertions automatically, for any protocol. API specifications do not mark an endpoint as streaming, so generated SSE checks would fail on endpoints that do not stream. Add them by hand to tests for streaming endpoints.
Worked example — an AI chat completion
A POST /v1/chat/completions endpoint streams a model's reply and ends with data: [DONE].
- Run the test once and confirm the response viewer shows an event list.
- Tick Stream the response (Server-Sent Events) and set Stop when an event contains to
[DONE]. - Add these assertions:
- SSE: First Event Within (ms):
3000— the model starts answering within three seconds. - SSE: Event Field Equals:
choices[0].delta.roleequalsassistant. - SSE: Whole Stream Contains: the phrase the answer must include.
- SSE: Final Event Contains:
[DONE]— the stream finished rather than being cut off.
- SSE: First Event Within (ms):
- Save and run.
Troubleshooting
| Symptom | Why it happens | What to do |
|---|---|---|
The response shows raw data: lines instead of an event list | You are in Raw view, or the response is not text/event-stream. | Click Auto; check the response content type. |
| An SSE assertion fails with Response is not a text/event-stream | The endpoint did not stream for this request. | Check the request (some APIs stream only when asked, for example with a stream: true flag). |
| SSE: First Event Within (ms) fails asking for streaming | The test runs in buffered mode. | Tick Stream the response (Server-Sent Events). |
| The test hangs until timeout | The stream never ends on its own. | Turn on streaming mode and set Stop when an event contains or Max duration (ms). |
| SSE: Any Event Contains fails on text you can see | The text is split across events. | Use SSE: Whole Stream Contains. |
| Truncated for storage badge appears | The stream exceeded what Studio keeps per run. | Counts stay accurate; for content checks, stop earlier with Max events. |
| The streaming option is missing | The test is SOAP, GraphQL or WebSocket-RPC. | SSE options apply to REST tests. |
Best practices
- Pair a content check with SSE: Final Event Contains so a stream cut off halfway is not mistaken for a complete one.
- Set Stop when an event contains for streams that end with a sentinel; it makes runs faster and more predictable.
- Use first-event latency, not total duration, to judge whether an AI endpoint is responsive.
- Assert on fields in structured events with SSE: Event Field Equals rather than searching for text.
Related articles
For administrators (self-hosted installations)
| Setting | Default | What switching it does |
|---|---|---|
SSE_STREAMING_ENABLED | true | Set to false to turn off event-stream parsing and streaming mode. |
SSE_MAX_STORED_EVENTS | 200 | Maximum events kept per response. |
SSE_MAX_EVENT_BYTES | 8192 | Maximum bytes kept per event payload. |
SSE_MAX_TOTAL_BYTES | 262144 | Maximum bytes kept across all events in one response. |
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.