# 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.

Source: https://totalshiftleft.ai/help-center/product-documentation/sse-streaming-testing

> **Applies 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

1. Open the test and run it as usual.
2. 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 `id` if present, and its payload, pretty-printed where it is JSON.
3. The header shows how many events arrived, for example *42 events*.
4. 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

1. Open the test's assertions and add a new assertion.
2. Choose from the **Streaming (SSE)** category.
3. 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:

1. Open the test in the test editor and go to its request configuration.
2. Tick **Stream the response (Server-Sent Events)**.
3. 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]`. |

4. Add **SSE: First Event Within (ms)** with your latency target, for example `2000`.
5. 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]`.

1. Run the test once and confirm the response viewer shows an event list.
2. Tick **Stream the response (Server-Sent Events)** and set **Stop when an event contains** to `[DONE]`.
3. Add these assertions:
   - **SSE: First Event Within (ms)**: `3000` — the model starts answering within three seconds.
   - **SSE: Event Field Equals**: `choices[0].delta.role` equals `assistant`.
   - **SSE: Whole Stream Contains**: the phrase the answer must include.
   - **SSE: Final Event Contains**: `[DONE]` — the stream finished rather than being cut off.
4. 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

- [Test run](/help-center/product-documentation/test-run)
- [Datasets](/help-center/product-documentation/data-sources)
- [JSON-RPC 2.0 and MCP Server Testing](/help-center/product-documentation/json-rpc-and-mcp-testing)

## 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. |

