feat: Add WebSocket-based syslog broadcast support for iOS - #1096
Conversation
Wire the existing IListensToSyslogMessages interface into IOSDriver, mirroring the Android logcat broadcast implementation. Adds WebSocket-based real-time iOS syslog streaming via the Appium ws://<host>:<port>/ws/session/<id>/appium/device/syslog endpoint. - IOSDriver now implements IListensToSyslogMessages using a StringWebSocketClient, with Start/StopSyslogBroadcast, listener registration (messages, errors, connect, disconnect) and RemoveAllSyslogListeners. - Change IListensToSyslogMessages Start/StopSyslogBroadcast return types from void to Task to match the async implementation and the Android IListensToLogcatMessages contract. - Add SyslogBroadcastTests integration tests paralleling the Android LogcatBroadcastTests suite. Completes the iOS half of the log broadcast feature (Android was merged in appium#969). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqd5nmE69tYMpCVYZ8ySrT
- StartSyslogBroadcast: stop the server-side broadcast if the WebSocket connection fails, preventing an orphaned broadcast on the Appium server. - StopSyslogBroadcast: wrap the stop command in try/finally so the client is always disconnected even if stopLogsBroadcast throws. - SyslogBroadcastTests: use SemaphoreSlim.WaitAsync and Task.Delay instead of blocking Wait/Thread.Sleep in async tests; catch WebDriverException (the actual awaited exception type) instead of AggregateException. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqd5nmE69tYMpCVYZ8ySrT
There was a problem hiding this comment.
Pull request overview
Adds iOS support for Appium’s WebSocket-based log broadcast feature by wiring IListensToSyslogMessages into IOSDriver and introducing integration tests to validate real-time syslog streaming behavior, complementing the existing Android logcat broadcast implementation.
Changes:
- Implemented WebSocket-driven iOS syslog broadcast start/stop and listener management on
IOSDriver. - Updated
IListensToSyslogMessagesstart/stop signatures to be async (Task) to align with the Android logcat contract. - Added
SyslogBroadcastTestsintegration tests mirroring the Android logcat broadcast suite.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| test/integration/IOS/Session/Logs/SyslogBroadcastTests.cs | Adds integration coverage for syslog WebSocket broadcast lifecycle and listener behavior. |
| src/Appium.Net/Appium/iOS/IOSDriver.cs | Implements iOS syslog broadcast and listener APIs via StringWebSocketClient. |
| src/Appium.Net/Appium/iOS/Interfaces/IListensToSyslogMessages.cs | Changes syslog broadcast interface methods to async Task signatures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…semaphore - StringWebSocketClient.DisconnectInternalAsync: null-guard _clientWebSocket (`?.State`) so DisconnectAsync is safe/idempotent when called before any successful ConnectAsync. Previously StopSyslogBroadcast/StopLogcatBroadcast invoked before a start would throw NullReferenceException. - SyslogBroadcastTests.CanHandleErrorsGracefully: remove the declared-but-never awaited errorSemaphore and its Release() call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqd5nmE69tYMpCVYZ8ySrT
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
src/Appium.Net/Appium/iOS/Interfaces/IListensToSyslogMessages.cs:31
- Changing Start/StopSyslogBroadcast from void to Task is a breaking public API change for any downstream implementations and some call sites (e.g., delegates expecting Action). If this package follows semver, consider adding new Task-returning Async methods (and keeping the void ones as obsolete shims) or coordinating a major version bump.
Task StartSyslogBroadcast();
src/Appium.Net/Appium/WebSocket/StringWebSocketClient.cs:279
- DisconnectInternalAsync only runs when State==Open, but when the server sends a Close frame ReceiveMessagesAsync calls DisconnectAsync (line 336) and the ClientWebSocket state is typically CloseReceived. That means the close handshake + _disconnected handlers can be skipped. Additionally, awaiting _receiveTask unconditionally can deadlock when DisconnectAsync is triggered from within ReceiveMessagesAsync.
if (_clientWebSocket?.State == WebSocketState.Open)
{
try
{
_cancellationTokenSource?.Cancel();
The message-received signals used bounded SemaphoreSlim maxCounts (1 and 10) while the syslog handler calls Release() per message. A burst of messages (or multiple listeners) could exceed the max and throw SemaphoreFullException, making the tests flaky. Use int.MaxValue as the max so releases can't overflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqd5nmE69tYMpCVYZ8ySrT
…test comment - StartSyslogBroadcast: construct the WebSocket endpoint via UriBuilder instead of string interpolation so IPv6 literal hosts (e.g. "::1") are bracketed correctly rather than throwing UriFormatException. - SyslogBroadcastTests.CanAddAndRemoveMultipleListeners: correct the comment to match the assertion (the semaphore wakes on the first listener, so the test asserts at least one invocation, not that both listeners have run). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqd5nmE69tYMpCVYZ8ySrT
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/Appium.Net/Appium/WebSocket/StringWebSocketClient.cs:279
- DisconnectInternalAsync can deadlock when a Close frame is received: ReceiveMessagesAsync calls DisconnectAsync() (StringWebSocketClient.cs:336), which then awaits _receiveTask (lines 283-286). If DisconnectAsync is running on the receive task itself, this is effectively awaiting itself and can hang indefinitely, leaving callers (e.g., IOSDriver.StopSyslogBroadcast) stuck. Consider skipping the await when DisconnectAsync is invoked from the receive loop task (compare Task.CurrentId to _receiveTask.Id), or otherwise avoid calling DisconnectAsync from inside ReceiveMessagesAsync.
if (_clientWebSocket?.State == WebSocketState.Open)
{
try
{
_cancellationTokenSource?.Cancel();
…CanAddAndRemoveMultipleListeners RemoveAllSyslogListeners() can race with in-flight message dispatch: a message already read from the socket before removal can still invoke the previously captured delegate. Resetting messageCount immediately after removal let such a late callback flip the post-removal EqualTo(0) assertion, making the test flaky. Wait briefly after removal (before resetting) so those callbacks drain and the assertion only reflects activity triggered after listeners were removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lqd5nmE69tYMpCVYZ8ySrT
List of changes
Implements the iOS half of the log broadcast feature. The Android logcat
side was merged in #969; this wires the existing (previously unused)
IListensToSyslogMessagesinterface intoIOSDriver, adding WebSocket-basedreal-time iOS syslog streaming via the Appium
ws://<host>:<port>/ws/session/<id>/appium/device/syslogendpoint.IOSDrivernow implementsIListensToSyslogMessagesusing aStringWebSocketClient, withStartSyslogBroadcast()(+ host / host+portoverloads),
StopSyslogBroadcast(), listener registration for messages,errors, connections and disconnections, and
RemoveAllSyslogListeners().IListensToSyslogMessagesStart/StopSyslogBroadcastreturntypes from
voidtoTask, matching the async implementation and theAndroid
IListensToLogcatMessagescontract.SyslogBroadcastTestsintegration tests paralleling the AndroidLogcatBroadcastTestssuite.Related to: #255
Types of changes
Documentation
Integration tests
Details
iOS syslog streaming: