-
Notifications
You must be signed in to change notification settings - Fork 187
feat: Add WebSocket-based log broadcast support for Android #969
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 27 commits
525defb
f174355
1ba8dc6
29f6149
18dbd45
9a47096
aa1dc9e
9b4e34e
8fb8e98
297e8d7
2f92495
f5ea24e
96e42b2
6b6f00b
aba9987
1313907
b373cbb
edcd995
147506a
bcfe603
9817502
09701b3
4b5fe71
4c98fbb
1a03163
8b4f000
a1458c9
d0269c4
38a9314
2694cbf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,21 +16,25 @@ | |
| using OpenQA.Selenium.Appium.Enums; | ||
| using OpenQA.Selenium.Appium.Interfaces; | ||
| using OpenQA.Selenium.Appium.Service; | ||
| using OpenQA.Selenium.Appium.WebSocket; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Drawing; | ||
| using System.Linq; | ||
| using OpenQA.Selenium.Appium.Android.Enums; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace OpenQA.Selenium.Appium.Android | ||
| { | ||
| public class AndroidDriver : AppiumDriver, | ||
| IStartsActivity, | ||
| INetworkActions, IHasClipboard, IHasPerformanceData, | ||
| ISendsKeyEvents, | ||
| IPushesFiles, IHasSettings | ||
| IPushesFiles, IHasSettings, IListensToLogcatMessages | ||
| { | ||
| private static readonly string Platform = MobilePlatform.Android; | ||
| private readonly StringWebSocketClient _logcatClient = new(); | ||
| private const int DefaultAppiumPort = 4723; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the AndroidDriver class | ||
|
|
@@ -427,5 +431,104 @@ public AppState GetAppState(string appId) => | |
| } | ||
| )?.ToString() ?? throw new InvalidOperationException("ExecuteScript returned null for mobile:queryAppState") | ||
| ); | ||
|
|
||
| #region Logcat Broadcast | ||
|
|
||
| /// <summary> | ||
| /// Start logcat messages broadcast via web socket. | ||
| /// This method assumes that Appium server is running on localhost and | ||
| /// is assigned to the default port (4723). | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This implementation uses a custom WebSocket endpoint and is temporary. | ||
| /// In the future, this functionality will be replaced with WebDriver BiDi log events | ||
| /// when BiDi support for Android device logs becomes available. | ||
| /// </remarks> | ||
| public async Task StartLogcatBroadcast() => await StartLogcatBroadcast("localhost", DefaultAppiumPort); | ||
|
|
||
| /// <summary> | ||
| /// Start logcat messages broadcast via web socket. | ||
| /// This method assumes that Appium server is assigned to the default port (4723). | ||
| /// </summary> | ||
| /// <param name="host">The name of the host where Appium server is running.</param> | ||
| /// <remarks> | ||
| /// This implementation uses a custom WebSocket endpoint and is temporary. | ||
| /// In the future, this functionality will be replaced with WebDriver BiDi log events | ||
| /// when BiDi support for Android device logs becomes available. | ||
| /// </remarks> | ||
| public async Task StartLogcatBroadcast(string host) => await StartLogcatBroadcast(host, DefaultAppiumPort); | ||
|
|
||
| /// <summary> | ||
| /// Start logcat messages broadcast via web socket. | ||
| /// </summary> | ||
| /// <param name="host">The name of the host where Appium server is running.</param> | ||
| /// <param name="port">The port of the host where Appium server is running.</param> | ||
| /// <remarks> | ||
| /// This implementation uses a custom WebSocket endpoint and is temporary. | ||
| /// In the future, this functionality will be replaced with WebDriver BiDi log events | ||
| /// when BiDi support for Android device logs becomes available. | ||
| /// </remarks> | ||
| public async Task StartLogcatBroadcast(string host, int port) | ||
| { | ||
| ExecuteScript("mobile: startLogsBroadcast", new Dictionary<string, object>()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tried BiDi instead of this extension command? https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#mobile-startlogsbroadcast
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @KazuCocoa, I attempted several different methods using BiDI, but unfortunately, none of them worked for me.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you share the appium server-side log as well?
When i ran appium was 3.1.1, uia2 driver was 6.1.0
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @KazuCocoa, does BiDi require a specific minimum version of Appium/UIAutomator2 to function? EDIT: Looks like I'm a bit behind with the versions: https://gist.github.com/Dor-bl/95d17ca78974d6883da682fba6e49d10
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minimal could be appium 2 and uia2 3.7.10. Old ones might have issues though.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll take a look at the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've opened an issue on the Appium server: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned in appium/appium-android-driver#1031, the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nvborisenko, can we apply a fix on the Selenium side, following the above comment?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I can accept it. But why:
We subscribe like: await bidi.Log.OnEntryAddedAsync(Console.WriteLine, new() { Contexts = ["NATIVE_APP"] });According spec |
||
| var endpointUri = new Uri($"ws://{host}:{port}/ws/session/{SessionId}/appium/device/logcat"); | ||
| await _logcatClient.ConnectAsync(endpointUri); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log messages broadcasting handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which accepts a single argument, which is the actual log message.</param> | ||
| public void AddLogcatMessagesListener(Action<string> handler) => | ||
| _logcatClient.AddMessageHandler(handler); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log broadcasting errors handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which accepts a single argument, which is the actual exception instance.</param> | ||
| public void AddLogcatErrorsListener(Action<Exception> handler) => | ||
| _logcatClient.AddErrorHandler(handler); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log broadcasting connection handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which is executed as soon as the client is successfully connected to the web socket.</param> | ||
| public void AddLogcatConnectionListener(Action handler) => | ||
| _logcatClient.AddConnectionHandler(handler); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log broadcasting disconnection handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which is executed as soon as the client is successfully disconnected from the web socket.</param> | ||
| public void AddLogcatDisconnectionListener(Action handler) => | ||
| _logcatClient.AddDisconnectionHandler(handler); | ||
|
|
||
| /// <summary> | ||
| /// Removes all existing logcat handlers. | ||
| /// </summary> | ||
| public void RemoveAllLogcatListeners() => _logcatClient.RemoveAllHandlers(); | ||
|
|
||
| /// <summary> | ||
| /// Stops logcat messages broadcast via web socket. | ||
| /// </summary> | ||
| public async Task StopLogcatBroadcast() | ||
| { | ||
| ExecuteScript("mobile: stopLogsBroadcast", new Dictionary<string, object>()); | ||
| await _logcatClient.DisconnectAsync(); | ||
| } | ||
|
|
||
| #endregion | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| //Licensed under the Apache License, Version 2.0 (the "License"); | ||
| //you may not use this file except in compliance with the License. | ||
| //See the NOTICE file distributed with this work for additional | ||
| //information regarding copyright ownership. | ||
| //You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| //Unless required by applicable law or agreed to in writing, software | ||
| //distributed under the License is distributed on an "AS IS" BASIS, | ||
| //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| //See the License for the specific language governing permissions and | ||
| //limitations under the License. | ||
|
|
||
| using System; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace OpenQA.Selenium.Appium.Android.Interfaces | ||
| { | ||
| /// <summary> | ||
| /// Interface for handling Android logcat message broadcasts via WebSocket. | ||
| /// </summary> | ||
| public interface IListensToLogcatMessages | ||
| { | ||
| /// <summary> | ||
| /// Start logcat messages broadcast via web socket. | ||
| /// This method assumes that Appium server is running on localhost and | ||
| /// is assigned to the default port (4723). | ||
| /// </summary> | ||
| Task StartLogcatBroadcast(); | ||
|
|
||
| /// <summary> | ||
| /// Start logcat messages broadcast via web socket. | ||
| /// This method assumes that Appium server is assigned to the default port (4723). | ||
| /// </summary> | ||
| /// <param name="host">The name of the host where Appium server is running.</param> | ||
| Task StartLogcatBroadcast(string host); | ||
|
|
||
| /// <summary> | ||
| /// Start logcat messages broadcast via web socket. | ||
| /// </summary> | ||
| /// <param name="host">The name of the host where Appium server is running.</param> | ||
| /// <param name="port">The port of the host where Appium server is running.</param> | ||
| Task StartLogcatBroadcast(string host, int port); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log messages broadcasting handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which accepts a single argument, which is the actual log message.</param> | ||
| void AddLogcatMessagesListener(Action<string> handler); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log broadcasting errors handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which accepts a single argument, which is the actual exception instance.</param> | ||
| void AddLogcatErrorsListener(Action<Exception> handler); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log broadcasting connection handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which is executed as soon as the client is successfully connected to the web socket.</param> | ||
| void AddLogcatConnectionListener(Action handler); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new log broadcasting disconnection handler. | ||
| /// Several handlers might be assigned to a single server. | ||
| /// Multiple calls to this method will cause such handler | ||
| /// to be called multiple times. | ||
| /// </summary> | ||
| /// <param name="handler">A function, which is executed as soon as the client is successfully disconnected from the web socket.</param> | ||
| void AddLogcatDisconnectionListener(Action handler); | ||
|
|
||
| /// <summary> | ||
| /// Removes all existing logcat handlers. | ||
| /// </summary> | ||
| void RemoveAllLogcatListeners(); | ||
|
|
||
| /// <summary> | ||
| /// Stops logcat messages broadcast via web socket. | ||
| /// </summary> | ||
| Task StopLogcatBroadcast(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //Licensed under the Apache License, Version 2.0 (the "License"); | ||
| //you may not use this file except in compliance with the License. | ||
| //See the NOTICE file distributed with this work for additional | ||
| //information regarding copyright ownership. | ||
| //You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| //Unless required by applicable law or agreed to in writing, software | ||
| //distributed under the License is distributed on an "AS IS" BASIS, | ||
| //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| //See the License for the specific language governing permissions and | ||
| //limitations under the License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace OpenQA.Selenium.Appium.WebSocket | ||
| { | ||
| /// <summary> | ||
| /// Interface for handling WebSocket connections. | ||
| /// </summary> | ||
| public interface ICanHandleConnects | ||
| { | ||
| /// <summary> | ||
| /// Gets the list of web socket connection handlers. | ||
| /// </summary> | ||
| List<Action> ConnectionHandlers { get; } | ||
|
|
||
| /// <summary> | ||
| /// Register a new connection handler. | ||
| /// </summary> | ||
| /// <param name="handler">A callback function, which is going to be executed when web socket connection event arrives.</param> | ||
| void AddConnectionHandler(Action handler); | ||
|
|
||
| /// <summary> | ||
| /// Removes existing web socket connection handlers. | ||
| /// </summary> | ||
| void RemoveConnectionHandlers(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //Licensed under the Apache License, Version 2.0 (the "License"); | ||
| //you may not use this file except in compliance with the License. | ||
| //See the NOTICE file distributed with this work for additional | ||
| //information regarding copyright ownership. | ||
| //You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| //Unless required by applicable law or agreed to in writing, software | ||
| //distributed under the License is distributed on an "AS IS" BASIS, | ||
| //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| //See the License for the specific language governing permissions and | ||
| //limitations under the License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace OpenQA.Selenium.Appium.WebSocket | ||
| { | ||
| /// <summary> | ||
| /// Interface for handling WebSocket disconnections. | ||
| /// </summary> | ||
| public interface ICanHandleDisconnects | ||
| { | ||
| /// <summary> | ||
| /// Gets the list of web socket disconnection handlers. | ||
| /// </summary> | ||
| List<Action> DisconnectionHandlers { get; } | ||
|
|
||
| /// <summary> | ||
| /// Register a new web socket disconnect handler. | ||
| /// </summary> | ||
| /// <param name="handler">A callback function, which is going to be executed when web socket disconnect event arrives.</param> | ||
| void AddDisconnectionHandler(Action handler); | ||
|
|
||
| /// <summary> | ||
| /// Removes existing disconnection handlers. | ||
| /// </summary> | ||
| void RemoveDisconnectionHandlers(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //Licensed under the Apache License, Version 2.0 (the "License"); | ||
| //you may not use this file except in compliance with the License. | ||
| //See the NOTICE file distributed with this work for additional | ||
| //information regarding copyright ownership. | ||
| //You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| //Unless required by applicable law or agreed to in writing, software | ||
| //distributed under the License is distributed on an "AS IS" BASIS, | ||
| //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| //See the License for the specific language governing permissions and | ||
| //limitations under the License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace OpenQA.Selenium.Appium.WebSocket | ||
| { | ||
| /// <summary> | ||
| /// Interface for handling WebSocket errors. | ||
| /// </summary> | ||
| public interface ICanHandleErrors | ||
| { | ||
| /// <summary> | ||
| /// Gets the list of web socket error handlers. | ||
| /// </summary> | ||
| List<Action<Exception>> ErrorHandlers { get; } | ||
|
|
||
| /// <summary> | ||
| /// Register a new error handler. | ||
| /// </summary> | ||
| /// <param name="handler">A callback function, which accepts the received exception instance as a parameter.</param> | ||
| void AddErrorHandler(Action<Exception> handler); | ||
|
|
||
| /// <summary> | ||
| /// Removes existing error handlers. | ||
| /// </summary> | ||
| void RemoveErrorHandlers(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| //Licensed under the Apache License, Version 2.0 (the "License"); | ||
| //you may not use this file except in compliance with the License. | ||
| //See the NOTICE file distributed with this work for additional | ||
| //information regarding copyright ownership. | ||
| //You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| //Unless required by applicable law or agreed to in writing, software | ||
| //distributed under the License is distributed on an "AS IS" BASIS, | ||
| //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| //See the License for the specific language governing permissions and | ||
| //limitations under the License. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace OpenQA.Selenium.Appium.WebSocket | ||
| { | ||
| /// <summary> | ||
| /// Interface for handling WebSocket messages. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of message to handle.</typeparam> | ||
| public interface ICanHandleMessages<T> | ||
| { | ||
| /// <summary> | ||
| /// Gets the list of web socket message handlers. | ||
| /// </summary> | ||
| List<Action<T>> MessageHandlers { get; } | ||
|
|
||
| /// <summary> | ||
| /// Register a new message handler. | ||
| /// </summary> | ||
| /// <param name="handler">A callback function, which accepts the received message as a parameter.</param> | ||
| void AddMessageHandler(Action<T> handler); | ||
|
|
||
| /// <summary> | ||
| /// Removes existing message handlers. | ||
| /// </summary> | ||
| void RemoveMessageHandlers(); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.