[Fix] Stabilize media-dependent audio room integration tests - #1224
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR adds injectable audio-engine availability to WebRTC factory construction. It wires the selected factory through coordinators and state adaptation. Integration helpers use silent audio settings and simplified sequential assertions. Simulator audio-session handling uses an empty rendering mode fallback. ChangesWebRTC audio injection and integration coverage
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CallIntegrationTests
participant StreamVideoHelper
participant WebRTCCoordinatorFactory
participant PeerConnectionFactory
participant WebRTCStateAdapter
CallIntegrationTests->>StreamVideoHelper: buildClient with silent audio environment
StreamVideoHelper->>WebRTCCoordinatorFactory: provide optional PeerConnectionFactory
WebRTCCoordinatorFactory->>PeerConnectionFactory: configure audio-engine override
WebRTCCoordinatorFactory->>WebRTCStateAdapter: pass selected factory
WebRTCStateAdapter->>PeerConnectionFactory: read audio-engine availability override
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
StreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests+Helpers.swift (1)
67-85: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winComplete cleanup before propagating a session-cleanup failure.
If the assertion at Line 67 throws,
permissions.dismantle()andclient.dismantle()at Lines 84-85 do not run. The next test can inherit retained calls, audio state, or mocked permissions.Capture the assertion failure, complete all cleanup, and then rethrow the captured error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@StreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests`+Helpers.swift around lines 67 - 85, Ensure the cleanup helper captures any error from the sessionID assertion instead of exiting immediately, then always completes registered-call, audio-device, permission, and client cleanup before rethrowing the captured error. Update the surrounding async cleanup flow in Call_IntegrationTests helpers while preserving successful execution behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/StreamVideo/WebRTC/PeerConnectionFactory.swift`:
- Around line 76-116: Update PeerConnectionFactory.build and its private init to
require a nonnil audioDeviceModuleSource whenever audioDevice is provided,
rejecting the mismatched combination before constructing the factory. Preserve
support for callers that provide neither device or provide matching audioDevice
and audioDeviceModuleSource values, and ensure the exposed audioDeviceModule
uses the supplied source.
---
Outside diff comments:
In
`@StreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests`+Helpers.swift:
- Around line 67-85: Ensure the cleanup helper captures any error from the
sessionID assertion instead of exiting immediately, then always completes
registered-call, audio-device, permission, and client cleanup before rethrowing
the captured error. Update the surrounding async cleanup flow in
Call_IntegrationTests helpers while preserving successful execution behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 749cb736-7c3f-4286-b5af-0fb05fb7373b
📒 Files selected for processing (6)
Sources/StreamVideo/WebRTC/PeerConnectionFactory.swiftSources/StreamVideo/WebRTC/v2/WebRTCCoordinator.swiftSources/StreamVideo/WebRTC/v2/WebRTCCoordinatorProviding.swiftStreamVideoTests/IntegrationTests/Call_IntegrationTests.swiftStreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests+Helpers.swiftStreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests+StreamVideoHelper.swift
Generated by 🚫 Danger |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@StreamVideoTests/IntegrationTests/Call_IntegrationTests.swift`:
- Around line 385-395: Update the participant join flow in the integration test
to assert, after participantUserFlow.perform completes and before
creatorFlow.call.end(), that the participant client's activeCall is this call.
Keep the existing eventual assertion for activeCall becoming nil after the call
ends.
In
`@StreamVideoTests/Utils/AudioSession/RTCAudioStore/Components/AVAudioSessionObserver_Tests.swift`:
- Around line 25-27: Rename the test method test_snapshot_renderingModeIsEmpty
to test_snapshot_rendering_mode_is_empty_ to follow the repository’s naming
convention, without changing its assertions or behavior.
- Around line 25-27: Scope test_snapshot_renderingModeIsEmpty to simulator
builds so the empty-string assertion runs only where AVAudioSession.Snapshot
does not expose the device rendering mode. Use the project’s existing
conditional-compilation conventions, or add a separate device assertion for
supported iOS 17.2+ builds.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 10461037-c58a-4b9b-8468-dfa5e83bd4d6
📒 Files selected for processing (7)
Sources/StreamVideo/Utils/AudioSession/RTCAudioStore/Components/AVAudioSessionObserver.swiftSources/StreamVideo/WebRTC/PeerConnectionFactory.swiftSources/StreamVideo/WebRTC/v2/WebRTCStateAdapter.swiftStreamVideoTests/IntegrationTests/Call_IntegrationTests.swiftStreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests+Helpers.swiftStreamVideoTests/IntegrationTests/Components/Helpers/Call_IntegrationTests+StreamVideoHelper.swiftStreamVideoTests/Utils/AudioSession/RTCAudioStore/Components/AVAudioSessionObserver_Tests.swift
| .perform { try await $0.call.join() } | ||
|
|
||
| try await withThrowingTaskGroup(of: Void.self) { group in | ||
| group.addTask { | ||
| try await creatorFlow | ||
| .perform { try await $0.call.join() } | ||
| .subscribe(for: CustomVideoEvent.self) | ||
| .assertEventually { (event: CustomVideoEvent) in event.custom["state"] == "joined" } | ||
| .perform { try await $0.call.end() } | ||
| } | ||
| let participantFlow = try await participantUserFlow | ||
| .perform { try await $0.call.join() } | ||
|
|
||
| group.addTask { | ||
| try await participantUserFlow | ||
| .perform { try await $0.call.join() } | ||
| .perform { try await $0.call.sendCustomEvent(["state": "joined"]) } | ||
| .assertEventuallyInMainActor { $0.call.streamVideo.state.activeCall == nil } | ||
| } | ||
| _ = try await creatorFlow.call.end() | ||
|
|
||
| try await group.waitForAll() | ||
| } | ||
| try await participantFlow | ||
| .assertEventuallyInMainActor(timeout: 30) { | ||
| $0.call.streamVideo.state.activeCall == nil | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Establish participant membership before ending the call.
The assertion only checks the final activeCall state. The test can pass if the participant never becomes active before end() runs. Assert that the participant client has this call as its active call after joining and before Line 390.
Proposed fix
let participantFlow = try await participantUserFlow
.perform { try await $0.call.join() }
+ .assertEventuallyInMainActor {
+ $0.call.streamVideo.state.activeCall?.cId == $0.call.cId
+ }
_ = try await creatorFlow.call.end()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .perform { try await $0.call.join() } | |
| try await withThrowingTaskGroup(of: Void.self) { group in | |
| group.addTask { | |
| try await creatorFlow | |
| .perform { try await $0.call.join() } | |
| .subscribe(for: CustomVideoEvent.self) | |
| .assertEventually { (event: CustomVideoEvent) in event.custom["state"] == "joined" } | |
| .perform { try await $0.call.end() } | |
| } | |
| let participantFlow = try await participantUserFlow | |
| .perform { try await $0.call.join() } | |
| group.addTask { | |
| try await participantUserFlow | |
| .perform { try await $0.call.join() } | |
| .perform { try await $0.call.sendCustomEvent(["state": "joined"]) } | |
| .assertEventuallyInMainActor { $0.call.streamVideo.state.activeCall == nil } | |
| } | |
| _ = try await creatorFlow.call.end() | |
| try await group.waitForAll() | |
| } | |
| try await participantFlow | |
| .assertEventuallyInMainActor(timeout: 30) { | |
| $0.call.streamVideo.state.activeCall == nil | |
| } | |
| .perform { try await $0.call.join() } | |
| let participantFlow = try await participantUserFlow | |
| .perform { try await $0.call.join() } | |
| .assertEventuallyInMainActor { | |
| $0.call.streamVideo.state.activeCall?.cId == $0.call.cId | |
| } | |
| _ = try await creatorFlow.call.end() | |
| try await participantFlow | |
| .assertEventuallyInMainActor(timeout: 30) { | |
| $0.call.streamVideo.state.activeCall == nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@StreamVideoTests/IntegrationTests/Call_IntegrationTests.swift` around lines
385 - 395, Update the participant join flow in the integration test to assert,
after participantUserFlow.perform completes and before creatorFlow.call.end(),
that the participant client's activeCall is this call. Keep the existing
eventual assertion for activeCall becoming nil after the call ends.
| func test_snapshot_renderingModeIsEmpty() { | ||
| XCTAssertEqual(AVAudioSession.Snapshot().renderingMode, "") | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Rename the test to match the repository convention.
Use test_snapshot_rendering_mode_is_empty_() instead of test_snapshot_renderingModeIsEmpty.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@StreamVideoTests/Utils/AudioSession/RTCAudioStore/Components/AVAudioSessionObserver_Tests.swift`
around lines 25 - 27, Rename the test method test_snapshot_renderingModeIsEmpty
to test_snapshot_rendering_mode_is_empty_ to follow the repository’s naming
convention, without changing its assertions or behavior.
Source: Coding guidelines
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Scope this assertion to simulator builds.
AVAudioSession.Snapshot uses source.renderingMode on supported Swift 6 device builds running iOS 17.2 or later. The unconditional empty-string assertion can fail on those devices. Compile this test only for simulator targets, or assert the device path separately.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@StreamVideoTests/Utils/AudioSession/RTCAudioStore/Components/AVAudioSessionObserver_Tests.swift`
around lines 25 - 27, Scope test_snapshot_renderingModeIsEmpty to simulator
builds so the empty-string assertion runs only where AVAudioSession.Snapshot
does not expose the device rendering mode. Use the project’s existing
conditional-compilation conventions, or add a separate device assertion for
supported iOS 17.2+ builds.
SDK Size
|
StreamVideo XCSize
|
|



🎯 Goal
Prevent audio-room integration tests from activating the simulator's real audio capture stack, which can cause intermittent TCC, MediaServices, and CoreSimulator failures during repeated runs.
The original
CallSettingsand production media behavior remain unchanged.📝 Summary
🛠 Implementation
PeerConnectionFactorycan now receive an optionalRTCAudioDeviceandRTCAudioDeviceModuleControlling. The configured factory is forwarded throughWebRTCCoordinatorFactoryandWebRTCCoordinator.When no factory or audio device is injected, the existing production path continues to create the standard WebRTC audio engine with the configured audio-processing module.
The affected integration tests inject:
RTCAudioDevicethat does not start platform audio capture or playback.AVAudioSessionactivation.The tests retain their original audio and video
CallSettings.Integration-test teardown now waits until the call's WebRTC session identifier is cleared before dismantling clients and resets the permission helper to avoid leaking state between iterations.
🎨 Showcase
Not applicable. There are no UI changes.
🧪 Manual Testing Notes
Validated on an iPhone 17 Pro simulator running iOS 26.5:
Unable to initAndStart recordingerrors.xcodebuild ... build-for-testingpassed.swiftformat --lint --config .swiftformat .passed.swiftlint lint --config .swiftlint.yml --strictpassed.A subsequent live-SFU run encountered a separate pre-join timeout while
call.createmade no progress. It occurred before WebRTC joining or audio activation and did not reproduce the media-service crash.☑️ Contributor Checklist
Summary by CodeRabbit
New Features
Bug Fixes