This commit is contained in:
redth 2022-09-07 16:26:45 -04:00
Родитель 047c5a539e
Коммит a1e29e4c5a
28 изменённых файлов: 744 добавлений и 449 удалений

Просмотреть файл

@ -35,7 +35,7 @@ namespace Microsoft.Maui.Automation
var multiApp = new MultiPlatformApplication(Platform.Maui, new[]
{
( Platform.Maui, new MauiApplication(app)),
( Platform.Maui, new MauiApplication(platformApp, app)),
( platform, platformApp )
});
@ -51,36 +51,8 @@ namespace Microsoft.Maui.Automation
#endif
);
#if ANDROID
client = new GrpcRemoteAppAgent(multiApp, address, new MyAndroidHandler());
#else
client = new GrpcRemoteAppAgent(multiApp, address);
#endif
}
}
#if ANDROID
public class MyAndroidHandler : Xamarin.Android.Net.AndroidMessageHandler
{
MyHostnameVerifier verifier = new MyHostnameVerifier();
protected override Javax.Net.Ssl.IHostnameVerifier GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection connection)
{
return verifier;
}
protected override Javax.Net.Ssl.SSLSocketFactory ConfigureCustomSSLSocketFactory(Javax.Net.Ssl.HttpsURLConnection connection)
{
return Android.Net.SSLCertificateSocketFactory.GetInsecure(1000, null);
}
class MyHostnameVerifier : Java.Lang.Object, Javax.Net.Ssl.IHostnameVerifier
{
public bool Verify(string hostname, Javax.Net.Ssl.ISSLSession session)
{
return true;
}
}
}
#endif
}

Просмотреть файл

@ -1,28 +1,28 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
//using System;
//using System.Collections.Generic;
//using System.Threading.Tasks;
namespace Microsoft.Maui.Automation
{
public class AppHostApplication : MultiPlatformApplication
{
public AppHostApplication
(
Platform defaultPlatform
#if ANDROID
, Android.App.Application application
#endif
)
: base(defaultPlatform, new[] {
( Platform.Maui, new MauiApplication() ),
( App.GetCurrentPlatform(), App.CreateForCurrentPlatform(
#if ANDROID
application
#endif
))
})
{
}
}
}
//namespace Microsoft.Maui.Automation
//{
// public class AppHostApplication : MultiPlatformApplication
// {
// public AppHostApplication
// (
// Platform defaultPlatform
//#if ANDROID
// , Android.App.Application application
//#endif
// )
// : base(defaultPlatform, new[] {
// ( Platform.Maui, new MauiApplication() ),
// ( App.GetCurrentPlatform(), App.CreateForCurrentPlatform(
//#if ANDROID
// application
//#endif
// ))
// })
// {
// }
// }
//}

Просмотреть файл

@ -13,12 +13,12 @@ namespace Microsoft.Maui.Automation
{
public override Platform DefaultPlatform => Platform.Ios;
public override async Task<string> GetProperty(Platform platform, string elementId, string propertyName)
public override async Task<string> GetProperty(string elementId, string propertyName)
{
var selector = new ObjCRuntime.Selector(propertyName);
var getSelector = new ObjCRuntime.Selector("get" + System.Globalization.CultureInfo.InvariantCulture.TextInfo.ToTitleCase(propertyName));
var element = (await FindElements(platform, e => e.Id?.Equals(elementId) ?? false))?.FirstOrDefault();
var element = (await FindElements(e => e.Id?.Equals(elementId) ?? false))?.FirstOrDefault();
if (element is not null && element.PlatformElement is NSObject nsobj)
{
@ -40,7 +40,7 @@ namespace Microsoft.Maui.Automation
return string.Empty;
}
public override Task<IEnumerable<Element>> GetElements(Platform platform)
public override Task<IEnumerable<Element>> GetElements()
{
var root = GetRootElements(-1);
@ -86,20 +86,20 @@ namespace Microsoft.Maui.Automation
return children;
}
public override Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher)
public override Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher)
{
var windows = GetRootElements(-1);
var matches = new List<Element>();
Traverse(platform, windows, matches, matcher);
Traverse(windows, matches, matcher);
return Task.FromResult<IEnumerable<Element>>(matches);
}
public override Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments)
public override Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> Task.FromResult(new PerformActionResult { Result = String.Empty, Status = -1 });
void Traverse(Platform platform, IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
void Traverse(IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
{
foreach (var e in elements)
{
@ -110,7 +110,7 @@ namespace Microsoft.Maui.Automation
{
var children = uiView.Subviews?.Select(s => s.GetElement(this, e.Id, 1, 1))
?.ToList() ?? new List<Element>();
Traverse(platform, children, matches, matcher);
Traverse(children, matches, matcher);
}
}
}

Просмотреть файл

@ -1,6 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Automation.RemoteGrpc;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Platform;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -12,8 +14,10 @@ namespace Microsoft.Maui.Automation
{
public class MauiApplication : Application
{
public MauiApplication(Maui.IApplication? mauiApp = default) : base()
public MauiApplication(IApplication platformApp, Maui.IApplication mauiApp = default) : base()
{
PlatformApplication = platformApp;
MauiPlatformApplication = mauiApp
?? App.GetCurrentMauiApplication() ?? throw new PlatformNotSupportedException();
}
@ -29,7 +33,9 @@ namespace Microsoft.Maui.Automation
public readonly Maui.IApplication MauiPlatformApplication;
public override Task<IEnumerable<Element>> GetElements(Platform platform)
public readonly IApplication PlatformApplication;
public override Task<IEnumerable<Element>> GetElements()
=> Dispatch<IEnumerable<Element>>(() =>
{
var windows = new List<Element>();
@ -43,7 +49,7 @@ namespace Microsoft.Maui.Automation
return Task.FromResult<IEnumerable<Element>>(windows);
});
public override Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher)
public override Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher)
=> Dispatch<IEnumerable<Element>>(() =>
{
var windows = new List<Element>();
@ -55,12 +61,12 @@ namespace Microsoft.Maui.Automation
}
var matches = new List<Element>();
Traverse(platform, windows, matches, matcher);
Traverse(windows, matches, matcher);
return Task.FromResult<IEnumerable<Element>>(matches);
});
void Traverse(Platform platform, IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
void Traverse(IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
{
foreach (var e in elements)
{
@ -70,25 +76,50 @@ namespace Microsoft.Maui.Automation
if (e.PlatformElement is IView view)
{
var children = view.GetChildren(this, e.Id, 1, 1);
Traverse(platform, children, matches, matcher);
Traverse(children, matches, matcher);
}
else if (e.PlatformElement is IWindow window)
{
var children = window.GetChildren(this, e.Id, 1, 1);
Traverse(platform, children, matches, matcher);
Traverse(children, matches, matcher);
}
}
}
public override Task<string> GetProperty(Platform platform, string elementId, string propertyName)
public override async Task<string> GetProperty(string elementId, string propertyName)
{
throw new NotImplementedException();
var element = await this.FirstById(elementId);
return element.PlatformElement
.GetType()
.GetProperty(propertyName, System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)
?.GetValue(element.Platform)?.ToString() ?? String.Empty;
}
public override Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments)
{
throw new NotImplementedException();
}
}
public override Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> action switch
{
Actions.Tap => PerformTap(elementId, arguments),
_ => throw new NotImplementedException()
};
async Task<PerformActionResult> PerformTap(string elementId, params string[] arguments)
{
if (!string.IsNullOrEmpty(elementId))
{
var element = await this.FirstById(elementId);
if (element.PlatformElement is IElement mauiElement)
{
#if ANDROID
if (mauiElement.Handler?.PlatformView is Android.Views.View androidView)
return await androidView.PerformAction(Actions.Tap, elementId, arguments);
#endif
}
}
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -40,52 +40,36 @@ namespace Microsoft.Maui.Automation
public bool IsActivityCurrent(Activity activity)
=> LifecycleListener.Activity == activity;
public override Task<string> GetProperty(Platform platform, string elementId, string propertyName)
public override Task<string> GetProperty(string elementId, string propertyName)
{
throw new NotImplementedException();
}
public override Task<IEnumerable<Element>> GetElements(Platform platform)
public override Task<IEnumerable<Element>> GetElements()
{
return Task.FromResult(LifecycleListener.Activities.Select(a => a.GetElement(this, 1, -1)));
}
public override Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher)
public override Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher)
{
var windows = LifecycleListener.Activities.Select(a => a.GetElement(this, 1, 1));
var matches = new List<Element>();
Traverse(platform, windows, matches, matcher);
Traverse(windows, matches, matcher);
return Task.FromResult<IEnumerable<Element>>(matches);
}
public override async Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments)
{
if (action == Actions.Tap)
{
if (!string.IsNullOrEmpty(elementId))
{
var element = await this.FirstById(elementId);
if (element.PlatformElement is View androidView)
{
var r = androidView.PerformClick();
return PerformActionResult.Ok();
}
}
else if (arguments is not null
&& arguments.Length == 2
&& ulong.TryParse(arguments[0], out var x)
&& ulong.TryParse(arguments[1], out var y))
{
// tap x and y
}
}
public override async Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
{
var element = await this.FirstById(elementId);
if (element?.PlatformElement is Android.Views.View androidView)
return await androidView.PerformAction(action, elementId, arguments);
return PerformActionResult.Error($"Unrecognized action: {action}");
}
}
void Traverse(Platform platform, IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
void Traverse(IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
{
foreach (var e in elements)
{
@ -95,7 +79,7 @@ namespace Microsoft.Maui.Automation
if (e.PlatformElement is View view)
{
var children = view.GetChildren(this, e.Id, 1, 1);
Traverse(platform, children, matches, matcher);
Traverse(children, matches, matcher);
}
}
}

Просмотреть файл

@ -3,10 +3,12 @@ using Android.Content;
using Android.Hardware.Lights;
using Android.Views;
using AndroidX.Core.View.Accessibility;
using Java.Lang;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
namespace Microsoft.Maui.Automation
@ -143,5 +145,46 @@ namespace Microsoft.Maui.Automation
e.Children.AddRange(activity.GetChildren(e.Application, e.Id, currentDepth + 1, maxDepth));
return e;
}
public static async Task<PerformActionResult> PerformAction(this Android.Views.View view, string action, string elementId, params string[] arguments)
{
if (action == Actions.Tap)
{
await view.PostAsync(() => view.PerformClick());
return PerformActionResult.Ok();
}
throw new NotSupportedException($"PerformAction {action} is not supported.");
}
public static Task PostAsync(this Android.Views.View view, Action action)
{
var r = new AsyncThreadRunner(() => view.PerformClick());
view.Post(r);
return r.WaitAsync();
}
}
class AsyncThreadRunner : Java.Lang.Object, IRunnable
{
public AsyncThreadRunner(Action action)
{
runner = action;
tcsRunner = new();
}
readonly Action runner;
readonly TaskCompletionSource<bool> tcsRunner;
public Task WaitAsync()
=> tcsRunner.Task;
public void Run()
{
runner?.Invoke();
tcsRunner.TrySetResult(true);
}
}
}

Просмотреть файл

@ -13,12 +13,12 @@ namespace Microsoft.Maui.Automation
public override Platform DefaultPlatform => Platform.Winappsdk;
public override Task<IEnumerable<Element>> GetElements(Platform platform)
public override Task<IEnumerable<Element>> GetElements()
=> Task.FromResult<IEnumerable<Element>>(new[] { UI.Xaml.Window.Current.GetElement(this, 1, -1) });
public override async Task<string> GetProperty(Platform platform, string elementId, string propertyName)
public override async Task<string> GetProperty(string elementId, string propertyName)
{
var matches = await FindElements(platform, e => e.Id?.Equals(elementId) ?? false);
var matches = await FindElements(e => e.Id?.Equals(elementId) ?? false);
var match = matches?.FirstOrDefault();
@ -29,23 +29,23 @@ namespace Microsoft.Maui.Automation
}
public override async Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher)
public override async Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher)
{
var windows = new[] { UI.Xaml.Window.Current.GetElement(this, 1, 1) };
var matches = new List<Element>();
await Traverse(platform, windows, matches, matcher);
await Traverse(windows, matches, matcher);
return matches;
}
public override Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments)
public override Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
{
return Task.FromResult(PerformActionResult.Error());
}
async Task Traverse(Platform platform, IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
async Task Traverse(IEnumerable<Element> elements, IList<Element> matches, Func<Element, bool> matcher)
{
foreach (var e in elements)
{
@ -62,7 +62,7 @@ namespace Microsoft.Maui.Automation
children.Add(c);
}
await Traverse(platform, children, matches, matcher);
await Traverse(children, matches, matcher);
}
}
}

Просмотреть файл

@ -56,17 +56,17 @@ namespace Microsoft.Maui.Automation
public abstract Platform DefaultPlatform { get; }
public abstract Task<string> GetProperty(Platform platform, string elementId, string propertyName);
public abstract Task<string> GetProperty(string elementId, string propertyName);
public abstract Task<IEnumerable<Element>> GetElements(Platform platform);
public abstract Task<IEnumerable<Element>> GetElements();
public abstract Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher);
public abstract Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher);
public Task<PerformActionResult> PerformAction(Platform platform, string action, params string[] arguments)
=> PerformAction(platform, action, string.Empty, arguments);
public Task<PerformActionResult> PerformAction(string action, params string[] arguments)
=> PerformAction(action, string.Empty, arguments);
public abstract Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments);
public abstract Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments);
}
}
}

Просмотреть файл

@ -6,51 +6,40 @@ namespace Microsoft.Maui.Automation;
public static class ElementExtensions
{
public static Task<Element?> FirstBy(this IApplication application, string propertyName, string pattern, bool isRegularExpression)
=> application.FirstBy(application.DefaultPlatform, propertyName, pattern, isRegularExpression);
public static Task<Element?> FirstById(this IApplication application, string id)
=> application.FirstById(application.DefaultPlatform, id);
public static Task<Element?> FirstByAutomationId(this IApplication application, string automationId)
=> application.FirstByAutomationId(application.DefaultPlatform, automationId);
public static async Task<Element?> FirstBy(this IApplication application, Platform platform, string propertyName, string pattern, bool isRegularExpression)
=> (await application.FindElements(platform, e => e.Matches(propertyName, pattern, isRegularExpression))).FirstOrDefault();
public static async Task<Element?> FirstById(this IApplication application, Platform platform, string id)
=> (await application.FindElements(platform, e => e.Id.Equals(id))).FirstOrDefault();
public static async Task<Element?> FirstByAutomationId(this IApplication application, Platform platform, string automationId)
=> (await application.FindElements(platform, e => e.AutomationId.Equals(automationId))).FirstOrDefault();
public static async Task<Element?> FirstBy(this IApplication application,string propertyName, string pattern, bool isRegularExpression)
=> (await application.FindElements(e => e.PropertyMatches(propertyName, pattern, isRegularExpression))).FirstOrDefault();
public static async Task<Element?> FirstById(this IApplication application, string id)
=> (await application.FindElements(e => e.Id.Equals(id))).FirstOrDefault();
public static async Task<Element?> FirstByAutomationId(this IApplication application, string automationId)
=> (await application.FindElements(e => e.AutomationId.Equals(automationId))).FirstOrDefault();
public static Element? FirstBy(this IEnumerable<Element> elements, string propertyName, string pattern, bool isRegularExpression)
=> elements.Traverse(e => e.PropertyMatches(propertyName, pattern, isRegularExpression)).FirstOrDefault();
public static Element? FirstById(this IEnumerable<Element> elements, string id)
=> elements.Traverse(e => e.Id.Equals(id)).FirstOrDefault();
public static Element? FirstByAutomationId(this IEnumerable<Element> elements, string automationId)
=> elements.Traverse(e => e.AutomationId.Equals(automationId)).FirstOrDefault();
public static Task<IEnumerable<Element>> By(this IApplication application, string propertyName, string pattern, bool isRegularExpression)
=> application.By(application.DefaultPlatform, propertyName, pattern, isRegularExpression);
=> application.FindElements(e => e.PropertyMatches(propertyName, pattern, isRegularExpression));
public static Task<IEnumerable<Element>> ById(this IApplication application, string id)
=> application.ById(application.DefaultPlatform, id);
=> application.FindElements(e => e.Id.Equals(id));
public static Task<IEnumerable<Element>> ByAutomationId(this IApplication application, string automationId)
=> application.ByAutomationId(application.DefaultPlatform, automationId);
public static Task<IEnumerable<Element>> By(this IApplication application, Platform platform, string propertyName, string pattern, bool isRegularExpression)
=> application.FindElements(platform, e => e.Matches(propertyName, pattern, isRegularExpression));
public static Task<IEnumerable<Element>> ById(this IApplication application, Platform platform, string id)
=> application.FindElements(platform, e => e.Id.Equals(id));
public static Task<IEnumerable<Element>> ByAutomationId(this IApplication application, Platform platform, string automationId)
=> application.FindElements(platform, e => e.AutomationId.Equals(automationId));
=> application.FindElements(e => e.AutomationId.Equals(automationId));
public static IEnumerable<Element> By(this IEnumerable<Element> elements, string propertyName, string pattern, bool isRegularExpression)
=> elements.Traverse(e => e.Matches(propertyName, pattern, isRegularExpression));
=> elements.Traverse(e => e.PropertyMatches(propertyName, pattern, isRegularExpression));
public static IEnumerable<Element> ById(this IEnumerable<Element> elements, string id)
=> elements.Traverse(e => e.Id.Equals(id));
@ -59,30 +48,30 @@ public static class ElementExtensions
=> elements.Traverse(e => e.AutomationId.Equals(automationId));
public static IEnumerable<Element> Find(this IEnumerable<Element> elements, Func<Element, bool> matcher)
=> elements.Traverse(matcher);
public static IEnumerable<Element> Find(this IEnumerable<Element> elements, Predicate<Element> predicate)
=> elements.Traverse(predicate);
static IEnumerable<Element> Traverse(this IEnumerable<Element> elements, Func<Element, bool> matcher)
static IEnumerable<Element> Traverse(this IEnumerable<Element> elements, Predicate<Element> predicate)
{
var matches = new List<Element>();
elements.Traverse(matches, matcher);
elements.Traverse(matches, predicate);
return matches;
}
static void Traverse(this IEnumerable<Element> source, IList<Element> matches, Func<Element, bool> matcher)
static void Traverse(this IEnumerable<Element> source, IList<Element> matches, Predicate<Element> predicate)
{
foreach (var s in source)
{
if (matcher(s))
if (predicate(s))
matches.Add(s);
Traverse(s.Children, matches, matcher);
Traverse(s.Children, matches, predicate);
}
}
internal static bool Matches(this Element e, string propertyName, string pattern, bool isRegularExpression = false)
public static bool PropertyMatches(this Element e, string propertyName, string pattern, bool isRegularExpression = false)
{
var value =
propertyName.ToLowerInvariant() switch

Просмотреть файл

@ -1,6 +1,7 @@
using Grpc.Core;
using Grpc.Net.Client;
using Grpc.Net.Client.Configuration;
using Grpc.Net.Client.Web;
using Microsoft.Maui.Automation.RemoteGrpc;
using System;
using System.Collections.Generic;
@ -29,39 +30,11 @@ namespace Microsoft.Maui.Automation
private bool disposedValue;
public GrpcRemoteAppAgent(IApplication application, string address, HttpMessageHandler? httpMessageHandler = null)
public GrpcRemoteAppAgent(IApplication application, string address)
{
Application = application;
var grpc = GrpcChannel.ForAddress(address, new GrpcChannelOptions
{
//ServiceConfig = new ServiceConfig
//{
// MethodConfigs =
// {
// new MethodConfig
// {
// Names = { MethodName.Default },
// RetryPolicy = new RetryPolicy
// {
// MaxAttempts = 60,
// InitialBackoff = TimeSpan.FromSeconds(1),
// MaxBackoff = TimeSpan.FromSeconds(5),
// BackoffMultiplier = 1.1,
// RetryableStatusCodes = { StatusCode.NotFound, StatusCode.Unavailable }
// }
// }
// }
//},
//HttpHandler = new Grpc.Net.Client.Web.GrpcWebHandler(Grpc.Net.Client.Web.GrpcWebMode.GrpcWeb)
//,
//httpMessageHandler ?? new HttpClientHandler())
//HttpHandler = new HttpClientHandler
//{
// ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
//}
}); ;
var grpc = GrpcChannel.ForAddress(address);
client = new RemoteApp.RemoteAppClient(grpc);
elementsCall = client.GetElementsRoute();
@ -87,7 +60,8 @@ namespace Microsoft.Maui.Automation
var response = await HandleFindElementsRequest(findElementsCall.ResponseStream.Current);
await findElementsCall.RequestStream.WriteAsync(response);
}
} catch (Exception ex)
}
catch (Exception ex)
{
throw ex;
}
@ -114,56 +88,104 @@ namespace Microsoft.Maui.Automation
async Task<ElementsResponse> HandleElementsRequest(ElementsRequest request)
{
// Get the elements from the running app host
var elements = await Application.GetElements(request.Platform);
var response = new ElementsResponse();
response.RequestId = request.RequestId;
response.Elements.AddRange(elements);
try
{
// Get the elements from the running app host
var elements = await GetApp(request.Platform).GetElements();
response.Elements.AddRange(elements);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return response;
}
async Task<ElementsResponse> HandleFindElementsRequest(FindElementsRequest request)
{
// Get the elements from the running app host
var elements = await Application.FindElements(request.Platform, e =>
e.Matches(request.PropertyName, request.Pattern, request.IsExpression));
var response = new ElementsResponse();
response.RequestId = request.RequestId;
response.Elements.AddRange(elements);
try
{
// Get the elements from the running app host
var elements = await GetApp(request.Platform).FindElements(e =>
e.PropertyMatches(request.PropertyName, request.Pattern, request.IsExpression));
response.Elements.AddRange(elements);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return response;
}
async Task<PropertyResponse> HandleGetPropertyRequest(PropertyRequest request)
{
// Get the elements from the running app host
var v = await Application.GetProperty(request.Platform, request.ElementId, request.PropertyName);
var response = new PropertyResponse();
response.Platform = request.Platform;
response.RequestId = request.RequestId;
response.Value = v;
try
{
// Get the elements from the running app host
var v = await GetApp(request.Platform).GetProperty(request.ElementId, request.PropertyName);
response.Value = v;
}
catch (Exception ex)
{
Console.WriteLine(ex);
response.Value = string.Empty;
}
return response;
}
async Task<PerformActionResponse> HandlePerformActionRequest(PerformActionRequest request)
{
// Get the elements from the running app host
var result = await Application.PerformAction(request.Platform, request.Action, request.ElementId, request.Arguments.ToArray());
var response = new PerformActionResponse();
response.Platform = request.Platform;
response.RequestId = request.RequestId;
response.Status = result.Status;
response.Result = result.Result;
try
{
// Get the elements from the running app host
var result = await GetApp(request.Platform).PerformAction(request.Action, request.ElementId, request.Arguments.ToArray());
response.Status = result.Status;
response.Result = result.Result;
}
catch (Exception ex)
{
response.Status = PerformActionResult.ErrorStatus;
response.Result = ex.Message;
}
return response;
}
IApplication GetApp(Platform platform)
{
var unsupportedText = $"Platform {platform} is not supported on this app agent";
if (Application is MultiPlatformApplication multiApp)
{
if (!multiApp.PlatformApps.ContainsKey(platform))
throw new NotSupportedException(unsupportedText);
return multiApp.PlatformApps[platform];
}
if (Application.DefaultPlatform != platform)
throw new NotSupportedException(unsupportedText);
return Application;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)

Просмотреть файл

@ -7,13 +7,14 @@ namespace Microsoft.Maui.Automation
{
public Platform DefaultPlatform { get; }
public Task<IEnumerable<Element>> GetElements(Platform platform);
public Task<IEnumerable<Element>> GetElements();
public Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher);
public Task<string> GetProperty(Platform platform, string elementId, string propertyName);
public Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher);
public Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments);
public Task<PerformActionResult> PerformAction(Platform platform, string action, params string[] arguments);
public Task<string> GetProperty(string elementId, string propertyName);
public Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments);
public Task<PerformActionResult> PerformAction(string action, params string[] arguments);
}
}

Просмотреть файл

@ -29,18 +29,18 @@ namespace Microsoft.Maui.Automation
return PlatformApps[platform];
}
public override Task<IEnumerable<Element>> GetElements(Platform platform)
=> GetApp(platform).GetElements(platform);
public override Task<IEnumerable<Element>> GetElements()
=> GetApp(DefaultPlatform).GetElements();
public override Task<string> GetProperty(Platform platform, string elementId, string propertyName)
=> GetApp(platform).GetProperty(platform, elementId, propertyName);
public override Task<string> GetProperty(string elementId, string propertyName)
=> GetApp(DefaultPlatform).GetProperty(elementId, propertyName);
public override Task<IEnumerable<Element>> FindElements(Platform platform, Func<Element, bool> matcher)
=> GetApp(platform).FindElements(platform, matcher);
public override Task<IEnumerable<Element>> FindElements(Func<Element, bool> matcher)
=> GetApp(DefaultPlatform).FindElements(matcher);
public override Task<PerformActionResult> PerformAction(Platform platform, string action, string elementId, params string[] arguments)
=> GetApp(platform).PerformAction(platform, action, elementId, arguments);
public override Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> GetApp(DefaultPlatform).PerformAction(action, elementId, arguments);
}
}

Просмотреть файл

@ -5,15 +5,19 @@
public const string Tap = "TAP";
}
public class PerformActionResult
public partial class PerformActionResult
{
public static PerformActionResult Ok(string result = "")
=> new PerformActionResult { Result = result, Status = 0 };
public const int SuccessStatus = 0;
public const int ErrorStatus = -1;
public static PerformActionResult Error(string result = "", int status = -1)
public static PerformActionResult Ok(string result = "")
=> new PerformActionResult { Result = result, Status = SuccessStatus };
public static PerformActionResult Error(string result = "", int status = ErrorStatus)
=> new PerformActionResult { Result = result, Status = status };
public int Status { get; set; }
public int Status { get; set; } = ErrorStatus;
public string Result { get; set; } = string.Empty;
}
}

Просмотреть файл

@ -19,8 +19,8 @@ public class AndroidDriver : IDriver
{
Configuration = configuration;
int port = 10882;
var address = IPAddress.Any.ToString();
int port = 5000;
//var address = IPAddress.Any.ToString();
var adbDeviceSerial = configuration.Device;
androidSdkManager = new AndroidSdk.AndroidSdkManager();
@ -40,8 +40,8 @@ public class AndroidDriver : IDriver
Name = $"Android ({Adb.GetDeviceName(Device)})";
//var forwardResult = Adb.RunCommand("-s", $"\"{Device}\"", "reverse", $"tcp:{port}", $"tcp:{port}")?.GetAllOutput();
//System.Diagnostics.Debug.WriteLine(forwardResult);
var forwardResult = Adb.RunCommand("-s", $"\"{Device}\"", "reverse", $"tcp:{port}", $"tcp:{port}")?.GetAllOutput();
System.Diagnostics.Debug.WriteLine(forwardResult);
grpc = new GrpcHost();
}
@ -52,8 +52,9 @@ public class AndroidDriver : IDriver
protected readonly AndroidSdk.PackageManager Pm;
readonly AndroidSdk.AndroidSdkManager androidSdkManager;
protected readonly string Device;
protected readonly string Device;
private bool disposedValue;
public IAutomationConfiguration Configuration { get; }
public string Name { get; }
@ -177,19 +178,31 @@ public class AndroidDriver : IDriver
}
public Task Tap(int x, int y)
=> grpc.Client.PerformAction(Platform.Android, Actions.Tap, string.Empty, x.ToString(), y.ToString());
=> grpc.Client.PerformAction(Configuration.AutomationPlatform, Actions.Tap, string.Empty, x.ToString(), y.ToString());
public Task Tap(Element element)
=> grpc.Client.PerformAction(Configuration.AutomationPlatform, Actions.Tap, element.Id);
bool IsAppInstalled(string appId)
=> androidSdkManager.PackageManager.ListPackages()
.Any(p => p.PackageName?.Equals(appId, StringComparison.OrdinalIgnoreCase) ?? false);
public Task<string> GetProperty(Platform platform, string elementId, string propertyName)
=> grpc.Client.GetProperty(platform, elementId, propertyName);
public Task<string> GetProperty(string elementId, string propertyName)
=> grpc.Client.GetProperty(Configuration.AutomationPlatform, elementId, propertyName);
public Task<IEnumerable<Element>> GetElements(Platform platform)
=> grpc.Client.GetElements(platform);
public Task<IEnumerable<Element>> GetElements()
=> grpc.Client.GetElements(Configuration.AutomationPlatform);
public Task<IEnumerable<Element>> FindElements(Platform platform, string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> grpc.Client.FindElements(platform, propertyName, pattern, isExpression, ancestorId);
public Task<IEnumerable<Element>> FindElements(string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> grpc.Client.FindElements(Configuration.AutomationPlatform, propertyName, pattern, isExpression, ancestorId);
public Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> grpc.Client.PerformAction(Configuration.AutomationPlatform, action, elementId, arguments);
public async void Dispose()
{
if (grpc is not null)
await grpc.Stop();
}
}

Просмотреть файл

@ -2,9 +2,10 @@
namespace Microsoft.Maui.Automation.Driver;
public class AppDriver : IDriver
public class AppDriver : Driver
{
public AppDriver(IAutomationConfiguration configuration)
: base(configuration)
{
Driver = configuration.DevicePlatform switch
{
@ -19,61 +20,70 @@ public class AppDriver : IDriver
public readonly IDriver Driver;
public IAutomationConfiguration Configuration => Driver!.Configuration;
public override string Name => Driver.Name;
public string Name => Driver.Name;
public Task Back()
public override Task Back()
=> Driver.Back();
public Task ClearAppState(string appId)
public override Task ClearAppState(string appId)
=> Driver.ClearAppState(appId);
public Task<IEnumerable<Element>> FindElements(Platform platform, string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> Driver.FindElements(platform, propertyName, pattern, isExpression, propertyName);
public override Task<IEnumerable<Element>> FindElements(string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> Driver.FindElements(propertyName, pattern, isExpression, propertyName);
public Task<IDeviceInfo> GetDeviceInfo()
public override Task<IDeviceInfo> GetDeviceInfo()
=> Driver.GetDeviceInfo();
public Task<IEnumerable<Element>> GetElements(Platform platform)
=> Driver.GetElements(platform);
public override Task<IEnumerable<Element>> GetElements()
=> Driver.GetElements();
public Task<string> GetProperty(Platform platform, string elementId, string propertyName)
=> Driver.GetProperty(platform, elementId, propertyName);
public override Task<string> GetProperty(string elementId, string propertyName)
=> Driver.GetProperty(elementId, propertyName);
public Task InputText(string text)
public override Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> Driver.PerformAction(action, elementId, arguments);
public override Task InputText(string text)
=> Driver.InputText(text);
public Task InstallApp(string file, string appId)
public override Task InstallApp(string file, string appId)
=> Driver.InstallApp(file, appId);
public Task KeyPress(char keyCode)
public override Task KeyPress(char keyCode)
=> Driver.KeyPress(keyCode);
public Task LaunchApp(string appId)
public override Task LaunchApp(string appId)
=> Driver.LaunchApp(appId);
public Task LongPress(int x, int y)
public override Task LongPress(int x, int y)
=> Driver.LongPress(x, y);
public Task OpenUri(string uri)
public override Task OpenUri(string uri)
=> Driver.OpenUri(uri);
public Task PullFile(string appId, string remoteFile, string localDirectory)
public override Task PullFile(string appId, string remoteFile, string localDirectory)
=> Driver.PullFile(appId, remoteFile, localDirectory);
public Task PushFile(string appId, string localFile, string destinationDirectory)
public override Task PushFile(string appId, string localFile, string destinationDirectory)
=> Driver.PushFile(appId, localFile, destinationDirectory);
public Task RemoveApp(string appId)
public override Task RemoveApp(string appId)
=> Driver.RemoveApp(appId);
public Task StopApp(string appId)
public override Task StopApp(string appId)
=> Driver.StopApp(appId);
public Task Swipe((int x, int y) start, (int x, int y) end)
public override Task Swipe((int x, int y) start, (int x, int y) end)
=> Driver.Swipe(start, end);
public Task Tap(int x, int y)
public override Task Tap(int x, int y)
=> Driver.Tap(x, y);
public override Task Tap(Element element)
=> Driver.PerformAction(Actions.Tap, element.Id);
public override void Dispose()
{
Driver.Dispose();
}
}

Просмотреть файл

@ -42,8 +42,10 @@ namespace Microsoft.Maui.Automation.Driver
{
}
public AutomationConfiguration(Platform devicePlatform, string? device = null, Platform? automationPlatform = null)
public AutomationConfiguration(string appId, string appFilename, Platform devicePlatform, string? device = null, Platform? automationPlatform = null)
{
AppId = appId;
AppFilename = appFilename;
DevicePlatform = devicePlatform;
if (!string.IsNullOrEmpty(device))
Device = device;
@ -58,7 +60,7 @@ namespace Microsoft.Maui.Automation.Driver
public int AppAgentPort
{
get => int.Parse(GetOrDefault(nameof(AppAgentPort), 10882).ToString());
get => int.Parse(GetOrDefault(nameof(AppAgentPort), 5000).ToString());
set => this[nameof(AppAgentPort)] = value;
}
@ -80,7 +82,19 @@ namespace Microsoft.Maui.Automation.Driver
set => this[nameof(AutomationPlatform)] = Enum.GetName<Platform>(value) ?? nameof(Platform.Maui);
}
private object GetOrDefault(string key, object defaultValue)
public string AppId
{
get => GetOrDefault(nameof(AppId), "").ToString();
set => this[nameof(AppId)] = value;
}
public string AppFilename
{
get => GetOrDefault(nameof(AppFilename), "").ToString();
set => this[nameof(AppFilename)] = value;
}
private object GetOrDefault(string key, object defaultValue)
{
if (this.ContainsKey(key))
return this[key];

Просмотреть файл

@ -0,0 +1,77 @@
namespace Microsoft.Maui.Automation.Driver
{
public abstract class Driver : IDriver
{
public Driver(IAutomationConfiguration configuration)
{
Configuration = configuration;
}
public abstract string Name { get; }
public IAutomationConfiguration Configuration { get; }
public abstract Task Back();
public abstract Task ClearAppState(string appId);
public Task ClearAppState()
=> ClearAppState(Configuration.AppId);
public abstract void Dispose();
public abstract Task<IDeviceInfo> GetDeviceInfo();
public abstract Task InputText(string text);
public abstract Task InstallApp(string file, string appId);
public Task InstallApp()
=> InstallApp(Configuration.AppFilename, Configuration.AppId);
public abstract Task KeyPress(char keyCode);
public abstract Task LaunchApp(string appId);
public Task LaunchApp()
=> LaunchApp(Configuration.AppId);
public abstract Task LongPress(int x, int y);
public abstract Task OpenUri(string uri);
public abstract Task<IEnumerable<Element>> FindElements(string propertyName, string pattern, bool isExpression = false, string ancestorId = "");
public abstract Task<IEnumerable<Element>> GetElements();
public abstract Task<string> GetProperty(string elementId, string propertyName);
public abstract Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments);
public abstract Task PullFile(string appId, string remoteFile, string localDirectory);
public Task PullFile(string remoteFile, string localDirectory)
=> PullFile(Configuration.AppId, remoteFile, localDirectory);
public abstract Task PushFile(string appId, string localFile, string destinationDirectory);
public Task PushFile(string localFile, string destinationDirectory)
=> PushFile(Configuration.AppId, localFile, destinationDirectory);
public abstract Task RemoveApp(string appId);
public Task RemoveApp()
=> RemoveApp(Configuration.AppId);
public abstract Task StopApp(string appId);
public Task StopApp()
=> StopApp(Configuration.AppId);
public abstract Task Swipe((int x, int y) start, (int x, int y) end);
public abstract Task Tap(int x, int y);
public abstract Task Tap(Element element);
}
}

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Maui.Automation.Driver;
public static class DriverExtensions
{
public static async Task<Element?> FirstById(this IDriver driver, string id)
{
var elements = await driver.GetElements();
return elements.FirstById(id);
}
public static async Task<Element?> FirstByAutomationId(this IDriver driver, string automationId)
{
var elements = await driver.GetElements();
var e = elements.FirstByAutomationId(automationId);
return e;
}
public static async Task<IEnumerable<Element>> By(this IDriver driver, Predicate<Element> matching)
{
var elements = await driver.GetElements();
return elements.Find(matching);
}
public static Task<IEnumerable<Element>> ByType(this IDriver driver, string elementType)
=> driver.FindElements("Type", elementType);
}

Просмотреть файл

@ -1,44 +1,68 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Automation.Remote;
namespace Microsoft.Maui.Automation.Remote;
public class GrpcHost
{
public GrpcHost()
{
var builder = CreateHostBuilder(this, Array.Empty<string>());
public GrpcHost()
{
var builder = CreateHostBuilder(this, Array.Empty<string>());
host = builder.Build();
host.StartAsync();
host = builder.Build();
host.StartAsync();
Client = host.Services.GetRequiredService<GrpcRemoteAppClient>();
}
Services = host.Services;
Client = host.Services.GetRequiredService<GrpcRemoteAppClient>();
}
public readonly GrpcRemoteAppClient Client;
public readonly GrpcRemoteAppClient Client;
readonly IHost host;
readonly IWebHost host;
public static IHostBuilder CreateHostBuilder(GrpcHost grpcHost, string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder
.ConfigureServices(services => {
services.AddGrpc();
services.AddSingleton<GrpcHost>(grpcHost);
services.AddSingleton<GrpcRemoteAppClient>();
public readonly IServiceProvider Services;
})
.Configure(app =>
app.UseRouting()
.UseGrpcWeb()
.UseEndpoints(endpoints =>
endpoints.MapGrpcService<GrpcRemoteAppClient>().EnableGrpcWeb())));
public ILogger<GrpcHost> Logger => Services.GetRequiredService<ILogger<GrpcHost>>();
public static IWebHostBuilder CreateHostBuilder(GrpcHost grpcHost, string[] args)
{
var builder = new WebHostBuilder()
.ConfigureLogging(log =>
{
log.AddConsole();
})
.UseKestrel(kestrel =>
{
kestrel.ListenAnyIP(5000, listen =>
{
listen.Protocols = HttpProtocols.Http2;
});
})
.ConfigureServices(services =>
{
services.AddGrpc();
services.AddSingleton<GrpcHost>(grpcHost);
services.AddSingleton<GrpcRemoteAppClient>();
})
.Configure(app =>
{
app
.UseRouting()
.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true })
.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GrpcRemoteAppClient>();
});
});
public Task Stop()
=> host?.StopAsync() ?? Task.CompletedTask;
return builder;
}
public Task Stop()
=> host?.StopAsync() ?? Task.CompletedTask;
}

Просмотреть файл

@ -1,19 +1,15 @@
using Grpc.Core;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Automation.RemoteGrpc;
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Microsoft.Maui.Automation.Remote
{
public class GrpcRemoteAppClient : RemoteGrpc.RemoteApp.RemoteAppBase
public class GrpcRemoteAppClient : RemoteApp.RemoteAppBase
{
public GrpcRemoteAppClient()
public GrpcRemoteAppClient(ILogger<GrpcRemoteAppClient> logger)
: base()
{
Console.WriteLine("New grpc Service");
logger.LogInformation("GRPC Service Created");
}
Dictionary<string, TaskCompletionSource<IResponseMessage>> pendingResponses = new();

Просмотреть файл

@ -10,5 +10,8 @@
Platform DevicePlatform { get; set; }
Platform AutomationPlatform { get; set; }
string AppId { get; set; }
string AppFilename { get; set; }
}
}

Просмотреть файл

@ -1,6 +1,6 @@
namespace Microsoft.Maui.Automation.Driver
{
public interface IDriver
public interface IDriver : IDisposable
{
string Name { get; }
@ -9,6 +9,7 @@
Task<IDeviceInfo> GetDeviceInfo();
Task InstallApp(string file, string appId);
Task RemoveApp(string appId);
Task LaunchApp(string appId);
@ -17,12 +18,14 @@
Task ClearAppState(string appId);
Task PushFile(string appId, string localFile, string destinationDirectory);
Task PullFile(string appId, string remoteFile, string localDirectory);
Task PullFile(string appId, string remoteFile, string localDirectory);
Task Tap(int x, int y);
Task Tap(int x, int y);
Task LongPress(int x, int y);
Task Tap(Element element);
Task KeyPress(char keyCode);
Task Swipe((int x, int y) start, (int x, int y) end);
@ -33,10 +36,12 @@
Task OpenUri(string uri);
Task<string> GetProperty(Platform platform, string elementId, string propertyName);
Task<string> GetProperty(string elementId, string propertyName);
Task<IEnumerable<Element>> GetElements(Platform platform);
Task<IEnumerable<Element>> GetElements();
Task<IEnumerable<Element>> FindElements(Platform platform, string propertyName, string pattern, bool isExpression = false, string ancestorId = "");
Task<IEnumerable<Element>> FindElements(string propertyName, string pattern, bool isExpression = false, string ancestorId = "");
Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments);
}
}

Просмотреть файл

@ -6,104 +6,115 @@ using System.Net;
namespace Microsoft.Maui.Automation.Driver
{
public class WindowsDriver : IDriver
{
public class WindowsDriver : IDriver
{
public WindowsDriver(IAutomationConfiguration configuration)
{
Configuration = configuration;
Configuration = configuration;
grpc = new GrpcHost();
}
grpc = new GrpcHost();
}
readonly GrpcHost grpc;
readonly GrpcHost grpc;
public string Name => "Windows";
public string Name => "Windows";
public IAutomationConfiguration Configuration { get; }
public IAutomationConfiguration Configuration { get; }
public Task Back()
=> Task.CompletedTask;
public Task Back()
=> Task.CompletedTask;
public Task ClearAppState(string appId)
{
throw new NotImplementedException();
}
public Task ClearAppState(string appId)
{
throw new NotImplementedException();
}
public Task<IDeviceInfo> GetDeviceInfo()
{
throw new NotImplementedException();
}
public Task<IDeviceInfo> GetDeviceInfo()
{
throw new NotImplementedException();
}
public Task InputText(string text)
{
throw new NotImplementedException();
}
public Task InputText(string text)
{
throw new NotImplementedException();
}
public Task InstallApp(string file, string appId)
{
throw new NotImplementedException();
}
public Task InstallApp(string file, string appId)
{
throw new NotImplementedException();
}
public Task KeyPress(char keyCode)
{
throw new NotImplementedException();
}
public Task KeyPress(char keyCode)
{
throw new NotImplementedException();
}
public Task LaunchApp(string appId)
{
throw new NotImplementedException();
}
public Task LaunchApp(string appId)
{
throw new NotImplementedException();
}
public Task LongPress(int x, int y)
{
throw new NotImplementedException();
}
public Task LongPress(int x, int y)
{
throw new NotImplementedException();
}
public Task OpenUri(string uri)
{
throw new NotImplementedException();
}
public Task OpenUri(string uri)
{
throw new NotImplementedException();
}
public Task PullFile(string appId, string remoteFile, string localDirectory)
{
throw new NotImplementedException();
}
public Task PullFile(string appId, string remoteFile, string localDirectory)
{
throw new NotImplementedException();
}
public Task PushFile(string appId, string localFile, string destinationDirectory)
{
throw new NotImplementedException();
}
public Task PushFile(string appId, string localFile, string destinationDirectory)
{
throw new NotImplementedException();
}
public Task RemoveApp(string appId)
{
throw new NotImplementedException();
}
public Task RemoveApp(string appId)
{
throw new NotImplementedException();
}
public Task StopApp(string appId)
{
throw new NotImplementedException();
}
public Task StopApp(string appId)
{
throw new NotImplementedException();
}
public Task Swipe((int x, int y) start, (int x, int y) end)
{
throw new NotImplementedException();
}
public Task Swipe((int x, int y) start, (int x, int y) end)
{
throw new NotImplementedException();
}
public Task Tap(int x, int y)
{
throw new NotImplementedException();
}
public Task Tap(int x, int y)
{
throw new NotImplementedException();
}
public Task<string> GetProperty(Platform platform, string elementId, string propertyName)
=> grpc.Client.GetProperty(platform, elementId, propertyName);
public Task Tap(Element element)
=> grpc.Client.PerformAction(Configuration.AutomationPlatform, Actions.Tap, element.Id);
public Task<IEnumerable<Element>> GetElements(Platform platform)
=> grpc.Client.GetElements(platform);
public Task<string> GetProperty(string elementId, string propertyName)
=> grpc.Client.GetProperty(Configuration.AutomationPlatform, elementId, propertyName);
public Task<IEnumerable<Element>> FindElements(Platform platform, string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> grpc.Client.FindElements(platform, propertyName, pattern, isExpression, ancestorId);
public Task<IEnumerable<Element>> GetElements()
=> grpc.Client.GetElements(Configuration.AutomationPlatform);
}
public Task<IEnumerable<Element>> FindElements(string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> grpc.Client.FindElements(Configuration.AutomationPlatform, propertyName, pattern, isExpression, ancestorId);
public Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> grpc.Client.PerformAction(Configuration.AutomationPlatform, action, elementId, arguments);
public async void Dispose()
{
if (grpc is not null)
await grpc.Stop();
}
}
}

Просмотреть файл

@ -187,7 +187,11 @@ public class iOSDriver : IDriver
=> idb.hid().SendStream<HIDEvent, HIDResponse>(keyCode.AsHidEvents().ToArray());
public Task Tap(int x, int y)
=> press(x, y, TimeSpan.FromMilliseconds(50));
=> press(x, y, TimeSpan.FromMilliseconds(50));
public Task Tap(Element element)
=> grpc.Client.PerformAction(Platform.Ios, Actions.Tap, element.Id);
public Task LongPress(int x, int y)
=> press(x, y, TimeSpan.FromSeconds(3));
@ -247,15 +251,17 @@ public class iOSDriver : IDriver
}
});
public Task<string> GetProperty(Platform platform, string elementId, string propertyName)
=> grpc.Client.GetProperty(platform, elementId, propertyName);
public Task<string> GetProperty(string elementId, string propertyName)
=> grpc.Client.GetProperty(Configuration.AutomationPlatform, elementId, propertyName);
public Task<IEnumerable<Element>> GetElements(Platform platform)
=> grpc.Client.GetElements(platform);
public Task<IEnumerable<Element>> GetElements()
=> grpc.Client.GetElements(Configuration.AutomationPlatform);
public Task<IEnumerable<Element>> FindElements(Platform platform, string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> grpc.Client.FindElements(platform, propertyName, pattern, isExpression, ancestorId);
public Task<IEnumerable<Element>> FindElements(string propertyName, string pattern, bool isExpression = false, string ancestorId = "")
=> grpc.Client.FindElements(Configuration.AutomationPlatform, propertyName, pattern, isExpression, ancestorId);
public Task<PerformActionResult> PerformAction(string action, string elementId, params string[] arguments)
=> grpc.Client.PerformAction(Configuration.AutomationPlatform, action, elementId, arguments);
string UnpackIdb()
{
@ -272,4 +278,10 @@ public class iOSDriver : IDriver
return exePath;
}
public async void Dispose()
{
if (grpc is not null)
await grpc.Stop();
}
}

Просмотреть файл

@ -1,5 +1,8 @@
using Grpc.Net.Client;
using Grpc.Core.Logging;
using Grpc.Net.Client;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Automation;
using Microsoft.Maui.Automation.Driver;
using Microsoft.Maui.Automation.Remote;
using Spectre.Console;
using System.Net;
@ -8,52 +11,35 @@ using System.Net;
var platform = Platform.Maui;
var grpc = new GrpcHost();
Console.WriteLine("Started GRPC Host.");
var client = grpc.Client;
while (true)
var config = new AutomationConfiguration
{
var input = Console.ReadLine() ?? string.Empty;
AppAgentPort = 5000,
DevicePlatform = Platform.Android,
AutomationPlatform = platform,
Device = "emulator-5554"
};
var driver = new AppDriver(config);
try
{
if (input.StartsWith("tree"))
var mappings = new Dictionary<string, Func<Task>>
{
{ "tree", Tree },
{ "windows", Windows },
{ "test2", async () =>
{
var children = await client.GetElements(platform);
var button = await driver.FirstByAutomationId("buttonOne");
foreach (var w in children)
{
var tree = new Tree(w.ToTable(ConfigureTable));
await driver.Tap(button!);
var label = await driver.FirstByAutomationId("labelCount");
foreach (var d in w.Children)
{
PrintTree(tree, d, 1);
}
AnsiConsole.Write(tree);
}
Console.WriteLine(label.Text);
}
else if (input.StartsWith("windows"))
},
{ "test", async () =>
{
var children = await client.GetElements(platform);
var elements = await driver.FindElements("AutomationId", "buttonOne");
foreach (var w in children)
{
var tree = new Tree(w.ToTable(ConfigureTable));
AnsiConsole.Write(tree);
}
}
else if (input.StartsWith("test"))
{
var elements = await client.FindElements(platform, "AutomationId", "buttonOne");
foreach (var w in elements)
{
var tree = new Tree(w.ToTable(ConfigureTable));
@ -62,9 +48,37 @@ while (true)
}
}
}
};
while (true)
{
var input = Console.ReadLine() ?? string.Empty;
try
{
foreach (var kvp in mappings)
{
if (input.StartsWith(kvp.Key))
{
Task.Run(async () =>
{
try
{
await kvp.Value();
}
catch (Exception ex)
{
AnsiConsole.WriteException(ex);
}
});
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
AnsiConsole.WriteException(ex);
}
if (input != null && (input.Equals("quit", StringComparison.OrdinalIgnoreCase)
@ -73,8 +87,37 @@ while (true)
break;
}
await grpc.Stop();
driver.Dispose();
async Task Tree()
{
var children = await driver.GetElements();
foreach (var w in children)
{
var tree = new Tree(w.ToTable(ConfigureTable));
foreach (var d in w.Children)
{
PrintTree(tree, d, 1);
}
AnsiConsole.Write(tree);
}
}
async Task Windows()
{
var children = await driver.GetElements();
foreach (var w in children)
{
var tree = new Tree(w.ToTable(ConfigureTable));
AnsiConsole.Write(tree);
}
}
void PrintTree(IHasTreeNodes node, Element element, int depth)
{

Просмотреть файл

@ -10,37 +10,39 @@ namespace Microsoft.Maui.Automation.Test
{
public class Tests
{
readonly AppDriver driver;
public Tests()
{
configuration = new AutomationConfiguration(
Platform.Android,
automationPlatform: Platform.Maui,
device: "emulator-5554");
configuration.AppAgentPort = 5000;
driver = new Driver.AppDriver(configuration);
driver = new AppDriver(
new AutomationConfiguration(
"com.companyname.samplemauiapp",
"C:\\code\\Maui.UITesting\\samples\\SampleMauiApp\\bin\\Debug\\net6.0-android\\com.companyname.samplemauiapp-Signed.apk",
Platform.Android,
automationPlatform: Platform.Maui,
device: "emulator-5554"));
}
readonly IAutomationConfiguration configuration;
readonly AppDriver driver;
[Fact]
public async Task RunApp()
{
var appId = "com.companyname.samplemauiapp";
var file = "C:\\code\\Maui.UITesting\\samples\\SampleMauiApp\\bin\\Debug\\net6.0-android\\com.companyname.samplemauiapp-Signed.apk";
// Install and launch the app
await driver.InstallApp();
await driver.LaunchApp();
// Find the button by its MAUI AutomationId property
var button = await driver.FirstByAutomationId("buttonOne");
Assert.NotNull(button);
//await driver.InstallApp(file, appId);
// Tap the button to increment the counter
await driver.Tap(button);
//await driver.InstallApp(@"C:\code\Maui.UITesting\samples\SampleMauiApp\bin\Debug\net6.0-android\com.companyname.samplemauiapp-Signed.apk", appId);
//await driver.LaunchApp(appId);
var elements = await driver.FindElements(Platform.Maui, "AutomationId", "buttonOne");
var e = elements.FirstOrDefault();
// Find the label we expect to have changed
var label = await driver.By(e =>
e.Type == "Label"
&& e.Text.Contains("1"));
Assert.NotEmpty(label);
}
}
}

Просмотреть файл

@ -12,7 +12,7 @@ namespace SampleMauiApp
MainPage = new MainPage();
//#if ANDROID
this.StartAutomationServiceListener("http://127.0.0.1:5000");
this.StartAutomationServiceListener("http://localhost:5000");
//#else
//this.StartAutomationServiceListener("https://localhost:5001");
//#endif

Просмотреть файл

@ -24,6 +24,7 @@
<Label
Text="Current count: 0"
AutomationId="labelCount"
Grid.Row="2"
FontSize="18"
FontAttributes="Bold"