Add Server-Sent Events (SSE) recording support#6
Conversation
SSE streams never complete naturally - they remain open until cancelled. This made recording impossible because the recording logic ran after the streaming loop exited, but the loop never exited. This change adds a flush mechanism that allows the test trait to trigger recording when the test completes (and cancels the SSE connection). Key changes: PlaybackURLProtocol: - Add StreamingDelegate class for delegate-based URLSession streaming - Use delegate approach instead of session.bytes(for:) to properly handle cancellation and collect streamed data - Register a finish handler with PlaybackStore that saves collected data when flush() is called - Track streamTask and urlSessionTask for proper cancellation in stopLoading() PlaybackStore: - Add flush() method that triggers all registered streaming finish handlers - Add registerStreamingProtocol/unregisterStreamingProtocol for tracking active streaming connections - Extract checkRequest() from handleRequest() to separate the "should we use recorded data or hit network?" decision from the actual network call - Add recordResponse() for recording after streaming completes - Preserve handleRequest() for non-streaming use cases with a note about its streaming limitation ReplayTrait (Traits.swift): - Call flush() before printing the recording success message - This ensures SSE data is saved before the test tears down The flush mechanism works by: 1. When a streaming request starts, register a finish handler with the store 2. The handler captures the collected data and response metadata 3. When flush() is called (at test completion), all handlers execute 4. Each handler saves its collected data to the HAR file 5. The "Recorded HTTP traffic" message prints after all recordings complete
Sources/Replay/Playback.swift
Outdated
| startTime: startTime | ||
| ) | ||
| } catch { | ||
| // Recording failed silently |
There was a problem hiding this comment.
Would it make sense to accumulate errors instead of swallowing them here? (Genuine question; I don't have a sense right now of whether we get an opportunity to emit them any point later...)
|
Hi @zshannon! Thank you for taking a look at this. At first blush, this looks like a sound implementation. I left a handful of comments, but otherwise I think this is ready to go. Lemme give this another once over and test it out on another project using SSEs. |
…ering - Log recording failures instead of silently swallowing errors - Reorder StreamingDelegate declarations to group dataStream computed property with its backing storage
|
Thanks for the speedy consideration! I wasn't sure on the errors bit either so compromised with printing at least it'll show up in the test console output for the user now. |
Sources/Replay/Playback.swift
Outdated
| startTime: startTime | ||
| ) | ||
| } catch { | ||
| print("Replay: Failed to record streaming response: \(error)") |
There was a problem hiding this comment.
Printing is a totally reasonable way to deal with this, though it never feels great to pollute stdout like this. I was holding out hope for some opportunity to communicate errors accumulated by the URL protocol later, but I can't find an obvious place to do that.
So yeah, I think it's fine, since we'd be spamming console while testing, not when a user is running an app.
Looking at the specific log line, I wonder if we can make this more helpful:
- Which request failed to respond?
- Is it confusing to call this a streaming response?
- How much should we try to rhyme with the message printed when recording succeeds
Apparently using a static method was enough to trip up the Swift 6.2.3 compiler on Linux
Sources/Replay/Playback.swift
Outdated
| private var streamTask: Task<Void, Never>? | ||
| private var urlSessionTask: URLSessionTask? | ||
|
|
||
| private static func description(for request: URLRequest) -> String { |
There was a problem hiding this comment.
Wild that this was enough to trip up the Swift 6.2.3 compiler on Linux
8f93b96 to
8a08d02
Compare
|
Tested 8a08d02, works well. |
|
Aces. Merging this now. Thanks so much for your contribution, @zshannon! |
|
This is now available in 0.3.0. |
SSE streams never complete naturally - they remain open until cancelled. This made recording impossible because the recording logic ran after the streaming loop exited, but the loop never exited.
This change adds a flush mechanism that allows the test trait to trigger recording when the test completes (and cancels the SSE connection).
Key changes:
PlaybackURLProtocol:
PlaybackStore:
ReplayTrait (Traits.swift):
The flush mechanism works by: