Switch things over to grpc
This commit is contained in:
Родитель
a4440c1022
Коммит
5987467cf5
|
@ -8,13 +8,13 @@ using Microsoft.Maui.Hosting;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public static class AutomationAppBuilderExtensions
|
||||
{
|
||||
static IRemoteAutomationService RemoteAutomationService;
|
||||
static IApplication App;
|
||||
static GrpcRemoteAppHost host;
|
||||
|
||||
static IApplication CreateApp(
|
||||
Maui.IApplication app
|
||||
|
@ -33,33 +33,16 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
var platform = Automation.App.GetCurrentPlatform();
|
||||
|
||||
var multiApp = new MultiPlatformApplication(Platform.MAUI, new[]
|
||||
var multiApp = new MultiPlatformApplication(Platform.Maui, new[]
|
||||
{
|
||||
( Platform.MAUI, new MauiApplication(app)),
|
||||
( Platform.Maui, new MauiApplication(app)),
|
||||
( platform, platformApp )
|
||||
});
|
||||
|
||||
return multiApp;
|
||||
}
|
||||
|
||||
public static void StartAutomationServiceConnection(this Maui.IApplication mauiApplication, string host = null, int port = TcpRemoteApplication.DefaultPort)
|
||||
{
|
||||
IPAddress address = IPAddress.Any;
|
||||
if (!string.IsNullOrEmpty(host) && IPAddress.TryParse(host, out var ip))
|
||||
address = ip;
|
||||
|
||||
var multiApp = CreateApp(mauiApplication
|
||||
#if ANDROID
|
||||
, (Android.App.Application.Context as Android.App.Application)
|
||||
?? Microsoft.Maui.MauiApplication.Current
|
||||
#endif
|
||||
);
|
||||
|
||||
RemoteAutomationService = new RemoteAutomationService(multiApp);
|
||||
App = new TcpRemoteApplication(Platform.MAUI, address, port, false, RemoteAutomationService);
|
||||
}
|
||||
|
||||
public static void StartAutomationServiceListener(this Maui.IApplication mauiApplication, int port = TcpRemoteApplication.DefaultPort)
|
||||
public static void StartAutomationServiceListener(this Maui.IApplication mauiApplication, int port = 10882)
|
||||
{
|
||||
var address = IPAddress.Any;
|
||||
|
||||
|
@ -70,8 +53,15 @@ namespace Microsoft.Maui.Automation
|
|||
#endif
|
||||
);
|
||||
|
||||
RemoteAutomationService = new RemoteAutomationService(multiApp);
|
||||
App = new TcpRemoteApplication(Platform.MAUI, address, port, true, RemoteAutomationService);
|
||||
host = new GrpcRemoteAppHost(multiApp);
|
||||
|
||||
var server = new Server
|
||||
{
|
||||
Services = { RemoteGrpc.RemoteApp.BindService(host) },
|
||||
Ports = { new ServerPort(address.ToString(), port, ServerCredentials.Insecure) }
|
||||
};
|
||||
|
||||
server.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.Maui.Automation
|
|||
#endif
|
||||
)
|
||||
: base(defaultPlatform, new[] {
|
||||
( Platform.MAUI, new MauiApplication() ),
|
||||
( Platform.Maui, new MauiApplication() ),
|
||||
( App.GetCurrentPlatform(), App.CreateForCurrentPlatform(
|
||||
#if ANDROID
|
||||
application
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#if IOS || MACCATALYST
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
@ -10,48 +11,51 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
public class iOSApplication : Application
|
||||
{
|
||||
public override Platform DefaultPlatform => Platform.iOS;
|
||||
public override Platform DefaultPlatform => Platform.Ios;
|
||||
|
||||
public override async Task<object> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
public override Task<string> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
var p = await base.GetProperty(platform, elementId, propertyName);
|
||||
|
||||
if (p != null)
|
||||
return p;
|
||||
|
||||
var selector = new ObjCRuntime.Selector(propertyName);
|
||||
var getSelector = new ObjCRuntime.Selector("get" + System.Globalization.CultureInfo.InvariantCulture.TextInfo.ToTitleCase(propertyName));
|
||||
|
||||
var element = await Element(platform, elementId);
|
||||
var roots = GetRootElements();
|
||||
|
||||
if (element is iOSView view)
|
||||
var element = roots.FindDepthFirst(new IdSelector(elementId))?.FirstOrDefault();
|
||||
|
||||
if (element is not null && element.PlatformElement is NSObject nsobj)
|
||||
{
|
||||
if (view.PlatformView.RespondsToSelector(selector))
|
||||
if (nsobj.RespondsToSelector(selector))
|
||||
{
|
||||
var v = view.PlatformView.PerformSelector(selector)?.ToString();
|
||||
var v = nsobj.PerformSelector(selector)?.ToString();
|
||||
if (v != null)
|
||||
return v;
|
||||
return Task.FromResult(v);
|
||||
}
|
||||
|
||||
if (view.PlatformView.RespondsToSelector(getSelector))
|
||||
if (nsobj.RespondsToSelector(getSelector))
|
||||
{
|
||||
var v = view.PlatformView.PerformSelector(getSelector)?.ToString();
|
||||
var v = nsobj.PerformSelector(getSelector)?.ToString();
|
||||
if (v != null)
|
||||
return v;
|
||||
return Task.FromResult(v);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
return Task.FromResult<string>(string.Empty);
|
||||
}
|
||||
|
||||
public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
public override Task<IEnumerable<Element>> GetElements(Platform platform, string elementId = null, int depth = 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var root = GetRootElements();
|
||||
|
||||
if (string.IsNullOrEmpty(elementId))
|
||||
return Task.FromResult(root);
|
||||
|
||||
return Task.FromResult(root.FindDepthFirst(new IdSelector(elementId)));
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
|
||||
IEnumerable<Element> GetRootElements()
|
||||
{
|
||||
var children = new List<IElement>();
|
||||
var children = new List<Element>();
|
||||
|
||||
var scenes = UIApplication.SharedApplication.ConnectedScenes?.ToArray();
|
||||
|
||||
|
@ -65,7 +69,7 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
foreach (var window in windowScene.Windows)
|
||||
{
|
||||
children.Add(new iOSWindow(this, window));
|
||||
children.Add(window.GetElement(this));
|
||||
hadScenes = true;
|
||||
}
|
||||
}
|
||||
|
@ -79,12 +83,12 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
foreach (var window in UIApplication.SharedApplication.Windows)
|
||||
{
|
||||
children.Add(new iOSWindow(this, window));
|
||||
children.Add(window.GetElement(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<IElement>>(children);
|
||||
return children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,51 +1,91 @@
|
|||
#if IOS || MACCATALYST
|
||||
using System;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
|
||||
using Microsoft.Maui.Controls.PlatformConfiguration.GTKSpecific;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
namespace Microsoft.Maui.Automation;
|
||||
|
||||
internal static class iOSExtensions
|
||||
{
|
||||
internal static class iOSExtensions
|
||||
static string[] possibleTextPropertyNames = new string[]
|
||||
{
|
||||
static string[] possibleTextPropertyNames = new string[]
|
||||
"Title", "Text",
|
||||
};
|
||||
|
||||
internal static string GetText(this UIView view)
|
||||
=> view switch
|
||||
{
|
||||
"Title", "Text",
|
||||
IUITextInput ti => TextFromUIInput(ti),
|
||||
UIButton b => b.CurrentTitle,
|
||||
_ => TextViaReflection(view, possibleTextPropertyNames)
|
||||
};
|
||||
|
||||
internal static string GetText(this UIView view)
|
||||
=> view switch
|
||||
{
|
||||
IUITextInput ti => TextFromUIInput(ti),
|
||||
UIButton b => b.CurrentTitle,
|
||||
_ => TextViaReflection(view, possibleTextPropertyNames)
|
||||
};
|
||||
|
||||
static string TextViaReflection(UIView view, string[] propertyNames)
|
||||
static string TextViaReflection(UIView view, string[] propertyNames)
|
||||
{
|
||||
foreach (var name in propertyNames)
|
||||
{
|
||||
foreach (var name in propertyNames)
|
||||
{
|
||||
var prop = view.GetType().GetProperty("Text", typeof(string));
|
||||
if (prop is null)
|
||||
continue;
|
||||
if (!prop.CanRead)
|
||||
continue;
|
||||
if (prop.PropertyType != typeof(string))
|
||||
continue;
|
||||
return prop.GetValue(view) as string ?? "";
|
||||
}
|
||||
return "";
|
||||
var prop = view.GetType().GetProperty("Text", typeof(string));
|
||||
if (prop is null)
|
||||
continue;
|
||||
if (!prop.CanRead)
|
||||
continue;
|
||||
if (prop.PropertyType != typeof(string))
|
||||
continue;
|
||||
return prop.GetValue(view) as string ?? "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static string TextFromUIInput(IUITextInput ti)
|
||||
static string TextFromUIInput(IUITextInput ti)
|
||||
{
|
||||
var start = ti.BeginningOfDocument;
|
||||
var end = ti.EndOfDocument;
|
||||
var range = ti.GetTextRange(start, end);
|
||||
return ti.TextInRange(range);
|
||||
}
|
||||
|
||||
public static Element GetElement(this UIKit.UIView uiView, IApplication application, string parentId = "")
|
||||
{
|
||||
var e = new Element(application, Platform.Ios, uiView.Handle.ToString(), uiView, parentId)
|
||||
{
|
||||
var start = ti.BeginningOfDocument;
|
||||
var end = ti.EndOfDocument;
|
||||
var range = ti.GetTextRange(start, end);
|
||||
return ti.TextInRange(range);
|
||||
}
|
||||
AutomationId = uiView.AccessibilityIdentifier,
|
||||
Visible = !uiView.Hidden,
|
||||
Enabled = uiView.UserInteractionEnabled,
|
||||
Focused = uiView.Focused,
|
||||
|
||||
X = (int)uiView.Frame.X,
|
||||
Y = (int)uiView.Frame.Y,
|
||||
|
||||
Width = (int)uiView.Frame.Width,
|
||||
Height = (int)uiView.Frame.Height,
|
||||
Text = uiView.GetText()
|
||||
};
|
||||
|
||||
var children = uiView.Subviews?.Select(s => s.GetElement(application, e.Id))?.ToList<Element>() ?? new List<Element>();
|
||||
|
||||
e.Children.AddRange(children);
|
||||
return e;
|
||||
}
|
||||
|
||||
public static Element GetElement(this UIWindow window, IApplication application)
|
||||
{
|
||||
var e = new Element(application, Platform.Ios, window.Handle.ToString(), window)
|
||||
{
|
||||
AutomationId = window.AccessibilityIdentifier ?? window.Handle.ToString(),
|
||||
Width = (int)window.Frame.Width,
|
||||
Height = (int)window.Frame.Height,
|
||||
Text = string.Empty
|
||||
};
|
||||
|
||||
var children = window.Subviews?.Select(s => s.GetElement(application, e.Id))?.ToList<Element>() ?? new List<Element>();
|
||||
|
||||
e.Children.AddRange(children);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#if IOS || MACCATALYST
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using UIKit;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class iOSView : Element
|
||||
{
|
||||
public iOSView(IApplication application, UIKit.UIView platformView, string? parentId = null)
|
||||
: base(application, Platform.iOS, platformView.Handle.ToString(), parentId)
|
||||
{
|
||||
PlatformView = platformView;
|
||||
PlatformElement = platformView;
|
||||
|
||||
AutomationId = platformView.AccessibilityIdentifier;
|
||||
|
||||
|
||||
var children = platformView.Subviews?.Select(s => new iOSView(application, s, Id))?.ToList<IElement>() ?? new List<IElement>();
|
||||
Children = new ReadOnlyCollection<IElement>(children);
|
||||
|
||||
Visible = !platformView.Hidden;
|
||||
Enabled = platformView.UserInteractionEnabled;
|
||||
Focused = platformView.Focused;
|
||||
|
||||
X = (int)platformView.Frame.X;
|
||||
Y = (int)platformView.Frame.Y;
|
||||
|
||||
Width = (int)platformView.Frame.Width;
|
||||
Height = (int)platformView.Frame.Height;
|
||||
|
||||
Text = platformView.GetText();
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
public UIKit.UIView PlatformView { get; set; }
|
||||
|
||||
//public void Clear()
|
||||
//{
|
||||
// if (PlatformElement is UIKit.IUITextInput ti)
|
||||
// {
|
||||
// var start = ti.BeginningOfDocument;
|
||||
// var end = ti.EndOfDocument;
|
||||
// var range = ti.GetTextRange(start, end);
|
||||
// ti.ReplaceText(range, string.Empty);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public void SendKeys(string text)
|
||||
//{
|
||||
// if (PlatformElement is UIKit.IUITextInput ti)
|
||||
// {
|
||||
// var start = ti.BeginningOfDocument;
|
||||
// var end = ti.EndOfDocument;
|
||||
// var range = ti.GetTextRange(start, end);
|
||||
// ti.ReplaceText(range, text);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public bool Focus()
|
||||
//{
|
||||
// if (!PlatformElement.CanBecomeFirstResponder)
|
||||
// return false;
|
||||
|
||||
// return PlatformElement.BecomeFirstResponder();
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,32 +0,0 @@
|
|||
#if IOS || MACCATALYST
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using UIKit;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class iOSWindow : Element
|
||||
{
|
||||
public iOSWindow(IApplication application, UIWindow window)
|
||||
: base(application, Platform.iOS, window.Handle.ToString())
|
||||
{
|
||||
PlatformWindow = window;
|
||||
PlatformElement = window;
|
||||
AutomationId = window.AccessibilityIdentifier ?? Id;
|
||||
|
||||
var children = window.Subviews?.Select(s => new iOSView(application, s, Id))?.ToList<IElement>() ?? new List<IElement>();
|
||||
Children = new ReadOnlyCollection<IElement>(children);
|
||||
Width = (int)PlatformWindow.Frame.Width;
|
||||
Height = (int)PlatformWindow.Frame.Height;
|
||||
Text = string.Empty;
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
public readonly UIWindow PlatformWindow;
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,33 @@
|
|||
using Grpc.Core;
|
||||
using Microsoft.Maui.Automation.RemoteGrpc;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Maui.Automation.Remote
|
||||
{
|
||||
public class GrpcRemoteAppHost : RemoteGrpc.RemoteApp.RemoteAppBase
|
||||
{
|
||||
public GrpcRemoteAppHost(IApplication application)
|
||||
{
|
||||
this.Application = application;
|
||||
}
|
||||
|
||||
protected readonly IApplication Application;
|
||||
|
||||
public async override Task<ElementsResponse> GetElements(ElementsRequest request, ServerCallContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var elements = await Application.GetElements(request.Platform, request.ElementId, request.ChildDepth);
|
||||
|
||||
var resp = new ElementsResponse();
|
||||
resp.Elements.AddRange(elements);
|
||||
return resp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,13 +22,13 @@ namespace Microsoft.Maui.Automation
|
|||
public static Platform GetCurrentPlatform()
|
||||
=>
|
||||
#if IOS || MACCATALYST
|
||||
Platform.iOS;
|
||||
Platform.Ios;
|
||||
#elif ANDROID
|
||||
Platform.Android;
|
||||
#elif WINDOWS
|
||||
Platform.WinAppSdk;
|
||||
Platform.Winappsdk;
|
||||
#else
|
||||
Platform.MAUI;
|
||||
Platform.Maui;
|
||||
#endif
|
||||
|
||||
public static IApplication CreateForCurrentPlatform
|
||||
|
@ -56,19 +56,19 @@ namespace Microsoft.Maui.Automation
|
|||
st.Push(elem);
|
||||
}
|
||||
|
||||
internal static async IAsyncEnumerable<IElement> FindDepthFirst(this IAsyncEnumerable<IElement> elements, IElementSelector? selector)
|
||||
{
|
||||
var list = new List<IElement>();
|
||||
await foreach (var e in elements)
|
||||
list.Add(e);
|
||||
//internal static IEnumerable<Element> FindDepthFirst(this IEnumerable<Element> elements, IElementSelector? selector)
|
||||
//{
|
||||
// var list = new List<Element>();
|
||||
// foreach (var e in elements)
|
||||
// list.Add(e);
|
||||
|
||||
await foreach (var e in FindDepthFirst(list, selector))
|
||||
yield return e;
|
||||
}
|
||||
// foreach (var e in FindDepthFirst(list, selector))
|
||||
// yield return e;
|
||||
//}
|
||||
|
||||
internal static async IAsyncEnumerable<IElement> FindDepthFirst(this IEnumerable<IElement> elements, IElementSelector? selector)
|
||||
internal static IEnumerable<Element> FindDepthFirst(this IEnumerable<Element> elements, IElementSelector? selector)
|
||||
{
|
||||
var st = new Stack<IElement>();
|
||||
var st = new Stack<Element>();
|
||||
st.PushAllReverse(elements);
|
||||
|
||||
while (st.Count > 0)
|
||||
|
@ -83,19 +83,19 @@ namespace Microsoft.Maui.Automation
|
|||
}
|
||||
}
|
||||
|
||||
internal static async IAsyncEnumerable<IElement> FindBreadthFirst(this IAsyncEnumerable<IElement> elements, IElementSelector? selector)
|
||||
{
|
||||
var list = new List<IElement>();
|
||||
await foreach (var e in elements)
|
||||
list.Add(e);
|
||||
//internal static IEnumerable<Element> FindBreadthFirst(this IEnumerable<Element> elements, IElementSelector? selector)
|
||||
//{
|
||||
// var list = new List<Element>();
|
||||
// foreach (var e in elements)
|
||||
// list.Add(e);
|
||||
|
||||
await foreach (var e in FindBreadthFirst(list, selector))
|
||||
yield return e;
|
||||
}
|
||||
// foreach (var e in FindBreadthFirst(list, selector))
|
||||
// yield return e;
|
||||
//}
|
||||
|
||||
internal static async IAsyncEnumerable<IElement> FindBreadthFirst(this IEnumerable<IElement> elements, IElementSelector? selector)
|
||||
internal static IEnumerable<Element> FindBreadthFirst(this IEnumerable<Element> elements, IElementSelector? selector)
|
||||
{
|
||||
var q = new Queue<IElement>();
|
||||
var q = new Queue<Element>();
|
||||
|
||||
foreach (var e in elements)
|
||||
q.Enqueue(e);
|
||||
|
@ -117,9 +117,9 @@ namespace Microsoft.Maui.Automation
|
|||
return new ReadOnlyCollection<T>(elems.ToList());
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<IElement> AsReadOnlyCollection(this IElement element)
|
||||
public static IReadOnlyCollection<Element> AsReadOnlyCollection(this Element element)
|
||||
{
|
||||
var list = new List<IElement> { element };
|
||||
var list = new List<Element> { element };
|
||||
return list.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class MauiApplication : Application
|
||||
public class MauiApplication : Application
|
||||
{
|
||||
public MauiApplication(Maui.IApplication? mauiApp = default) : base()
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ namespace Microsoft.Maui.Automation
|
|||
}
|
||||
|
||||
Task<TResult> Dispatch<TResult>(Func<TResult> action)
|
||||
{
|
||||
{
|
||||
var tcs = new TaskCompletionSource<TResult>();
|
||||
|
||||
var dispatcher = MauiPlatformApplication.Handler.MauiContext.Services.GetService<Dispatching.IDispatcher>() ?? throw new Exception("Unable to locate Dispatcher");
|
||||
|
@ -30,27 +30,27 @@ namespace Microsoft.Maui.Automation
|
|||
tcs.TrySetResult(r);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public override Platform DefaultPlatform => Platform.MAUI;
|
||||
public override Platform DefaultPlatform => Platform.Maui;
|
||||
|
||||
public readonly Maui.IApplication MauiPlatformApplication;
|
||||
public readonly Maui.IApplication MauiPlatformApplication;
|
||||
|
||||
public override async Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
public override async Task<IEnumerable<Element>> GetElements(Platform platform, string elementId = null, int depth = 0)
|
||||
{
|
||||
var windows = await Dispatch(() =>
|
||||
{
|
||||
var result = new List<MauiWindow>();
|
||||
var result = new List<Element>();
|
||||
|
||||
foreach (var window in MauiPlatformApplication.Windows)
|
||||
{
|
||||
var w = new MauiWindow(this, window);
|
||||
var w = window.GetMauiElement(this);
|
||||
result.Add(w);
|
||||
}
|
||||
|
||||
|
@ -60,10 +60,10 @@ namespace Microsoft.Maui.Automation
|
|||
return windows;
|
||||
}
|
||||
|
||||
public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
public override Task<string> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,69 +1,128 @@
|
|||
using System;
|
||||
using Microsoft.Maui.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
internal static class MauiExtensions
|
||||
{
|
||||
internal static IElement[] GetChildren(this Maui.IWindow window, IApplication application, string? parentId = null)
|
||||
internal static class MauiExtensions
|
||||
{
|
||||
internal static Element[] GetChildren(this Maui.IWindow window, IApplication application, string parentId = "")
|
||||
{
|
||||
if (window.Content == null)
|
||||
return Array.Empty<IElement>();
|
||||
return Array.Empty<Element>();
|
||||
|
||||
return new[] { new MauiElement(application, window.Content, parentId) };
|
||||
return new[] { window.Content.ToMauiAutomationView(application, parentId) };
|
||||
}
|
||||
|
||||
internal static IElement[] GetChildren(this Maui.IView view, IApplication application, string? parentId = null)
|
||||
internal static Element[] GetChildren(this Maui.IView view, IApplication application, string parentId = "")
|
||||
{
|
||||
if (view is ILayout layout)
|
||||
{
|
||||
var children = new List<IElement>();
|
||||
{
|
||||
var children = new List<Element>();
|
||||
|
||||
foreach (var v in layout)
|
||||
{
|
||||
children.Add(new MauiElement(application, v, parentId));
|
||||
}
|
||||
{
|
||||
children.Add(v.ToMauiAutomationView(application, parentId));
|
||||
}
|
||||
|
||||
return children.ToArray();
|
||||
}
|
||||
}
|
||||
else if (view is IContentView content && content?.Content is Maui.IView contentView)
|
||||
{
|
||||
return new[] { new MauiElement(application, contentView, parentId) };
|
||||
}
|
||||
{
|
||||
return new[] { contentView.ToMauiAutomationView(application, parentId) };
|
||||
}
|
||||
|
||||
return Array.Empty<IElement>();
|
||||
return Array.Empty<Element>();
|
||||
}
|
||||
|
||||
|
||||
internal static IElement ToAutomationWindow(this Maui.IWindow window, IApplication application)
|
||||
internal static Element ToPlatformAutomationWindow(this Maui.IWindow window, IApplication application)
|
||||
{
|
||||
#if ANDROID
|
||||
if (window.Handler.PlatformView is Android.App.Activity activity)
|
||||
return new AndroidWindow(application, activity);
|
||||
return activity.GetElement(application);
|
||||
#elif IOS || MACCATALYST
|
||||
if (window.Handler.PlatformView is UIKit.UIWindow uiwindow)
|
||||
return new iOSWindow(application, uiwindow);
|
||||
return uiwindow.GetElement(application);
|
||||
#elif WINDOWS
|
||||
if (window.Handler.PlatformView is Microsoft.UI.Xaml.Window xamlwindow)
|
||||
return new WindowsAppSdkWindow(application, xamlwindow);
|
||||
return xamlwindow.GetElement(application);
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static IElement ToAutomationView(this Maui.IView view, IApplication application, string? parentId = null)
|
||||
{
|
||||
internal static Element GetPlatformElement(this Maui.IView view, IApplication application, string parentId = "")
|
||||
{
|
||||
#if ANDROID
|
||||
if (view.Handler.PlatformView is Android.Views.View androidview)
|
||||
return new AndroidView(application, androidview, parentId);
|
||||
return androidview.GetElement(application, parentId);
|
||||
#elif IOS || MACCATALYST
|
||||
if (view.Handler.PlatformView is UIKit.UIView uiview)
|
||||
return new iOSView(application, uiview, parentId);
|
||||
return uiview.GetElement(application, parentId);
|
||||
#elif WINDOWS
|
||||
if (view.Handler.PlatformView is Microsoft.UI.Xaml.UIElement uielement)
|
||||
return new WindowsAppSdkView(application, uielement, parentId);
|
||||
return uielement.GetElement(application, parentId);
|
||||
#endif
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
internal static Element GetMauiElement(this Maui.IWindow window, IApplication application)
|
||||
{
|
||||
var platformElement = window.ToPlatformAutomationWindow(application);
|
||||
|
||||
var e = new Element(application, Platform.Maui, platformElement.Id, platformElement)
|
||||
{
|
||||
Id = platformElement.Id,
|
||||
AutomationId = platformElement.AutomationId ?? platformElement.Id,
|
||||
Type = window.GetType().Name,
|
||||
|
||||
Width = platformElement.Width,
|
||||
Height = platformElement.Height,
|
||||
Text = platformElement.Text ?? ""
|
||||
};
|
||||
|
||||
e.Children.AddRange(window.GetChildren(application, e.Id));
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
internal static Element ToMauiAutomationView(this Maui.IView view, IApplication application, string parentId = "")
|
||||
{
|
||||
var platformElement = view.GetPlatformElement(application, parentId);
|
||||
|
||||
var e = new Element(application, Platform.Maui, platformElement.Id, parentId)
|
||||
{
|
||||
PlatformElement = platformElement,
|
||||
ParentId = parentId,
|
||||
AutomationId = view.AutomationId ?? platformElement.Id,
|
||||
Type = view.GetType().Name,
|
||||
FullType = view.GetType().FullName,
|
||||
Visible = view.Visibility == Visibility.Visible,
|
||||
Enabled = view.IsEnabled,
|
||||
Focused = view.IsFocused,
|
||||
|
||||
X = (int)view.Frame.X,
|
||||
Y = (int)view.Frame.Y,
|
||||
Width = (int)view.Frame.Width,
|
||||
Height = (int)view.Frame.Height,
|
||||
};
|
||||
|
||||
if (view is Microsoft.Maui.IText text && !string.IsNullOrEmpty(text.Text))
|
||||
e.Text = text.Text;
|
||||
|
||||
if (view is Microsoft.Maui.ITextInput input && !string.IsNullOrEmpty(input.Text))
|
||||
e.Text = input.Text;
|
||||
|
||||
if (view is IImage image && image.Source is not null)
|
||||
e.Text = image.Source?.ToString() ?? "";
|
||||
|
||||
|
||||
e.Children.AddRange(view.GetChildren(application, parentId));
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Maui;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class MauiElement : Element
|
||||
{
|
||||
public MauiElement(IApplication application, Maui.IView view, string? parentId = null)
|
||||
: base(application, Platform.MAUI, string.Empty, parentId)
|
||||
{
|
||||
PlatformElement = view;
|
||||
PlatformView = view.ToAutomationView(application, parentId) ?? throw new PlatformNotSupportedException();
|
||||
|
||||
ParentId = parentId;
|
||||
Id = PlatformView.Id;
|
||||
AutomationId = PlatformView.AutomationId;
|
||||
Type = view.GetType().Name;
|
||||
|
||||
Visible = PlatformView.Visible;
|
||||
Enabled = PlatformView.Enabled;
|
||||
Focused = PlatformView.Focused;
|
||||
|
||||
X = PlatformView.X;
|
||||
X = PlatformView.Y;
|
||||
Width = PlatformView.Width;
|
||||
Height = PlatformView.Height;
|
||||
|
||||
if (view is Microsoft.Maui.IText text)
|
||||
Text = text.Text;
|
||||
|
||||
if (view is Microsoft.Maui.ITextInput input)
|
||||
Text = input.Text;
|
||||
|
||||
if (view is IImage image)
|
||||
Text = image.Source?.ToString();
|
||||
|
||||
Children = view.GetChildren(application, parentId);
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
protected IElement PlatformView { get; set; }
|
||||
|
||||
// public void Clear()
|
||||
//{
|
||||
// if (NativeView is ITextInput ti)
|
||||
// ti.Text = string.Empty;
|
||||
//}
|
||||
|
||||
//public void SendKeys(string text)
|
||||
//{
|
||||
// if (NativeView is ITextInput ti)
|
||||
// ti.Text = text;
|
||||
//}
|
||||
|
||||
//public bool Focus()
|
||||
// => PlatformView.Focus();
|
||||
|
||||
//public void Click()
|
||||
//{
|
||||
// if (NativeView is IButton button)
|
||||
// button.Clicked();
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class MauiWindow : Element
|
||||
{
|
||||
internal MauiWindow(IApplication application, Maui.IWindow window)
|
||||
: base(application, Platform.MAUI, "", parentId: null)
|
||||
{
|
||||
PlatformWindow = window.ToAutomationWindow(application) ?? throw new PlatformNotSupportedException();
|
||||
PlatformElement = window;
|
||||
|
||||
Id = PlatformWindow.Id;
|
||||
AutomationId = PlatformWindow.AutomationId;
|
||||
Type = window.GetType().Name;
|
||||
|
||||
Width = PlatformWindow.Width;
|
||||
Height = PlatformWindow.Height;
|
||||
Text = PlatformWindow.Text;
|
||||
|
||||
Children = window.GetChildren(application, Id);
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
protected IElement PlatformWindow { get; set; }
|
||||
}
|
||||
}
|
|
@ -14,13 +14,22 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Remote\Microsoft.Maui.Automation.Remote.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Platforms\iOS\" />
|
||||
<Folder Include="Platforms\MacCatalyst\" />
|
||||
<Folder Include="Platforms\iOS\" />
|
||||
<Folder Include="Platforms\MacCatalyst\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Core.Xamarin" Version="2.44.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.21.5" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.48.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -26,11 +26,6 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
AutomationActivityLifecycleContextListener LifecycleListener { get; }
|
||||
|
||||
public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
//public override Task<IWindow> CurrentWindow()
|
||||
//{
|
||||
// var activity = LifecycleListener.Activity ?? LifecycleListener.Activities.FirstOrDefault();
|
||||
|
@ -41,12 +36,19 @@ namespace Microsoft.Maui.Automation
|
|||
// return Task.FromResult<IWindow>(new AndroidWindow(this, activity));
|
||||
//}
|
||||
|
||||
public override Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
=> Task.FromResult<IEnumerable<IElement>>(LifecycleListener.Activities.Select(a => new AndroidWindow(this, a)));
|
||||
|
||||
public bool IsActivityCurrent(Activity activity)
|
||||
=> LifecycleListener.Activity == activity;
|
||||
|
||||
public override Task<string> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<Element>> GetElements(Platform platform, string elementId = null, int depth = 0)
|
||||
{
|
||||
return Task.FromResult(LifecycleListener.Activities.Select(a => a.GetElement(this)));
|
||||
}
|
||||
|
||||
internal class AutomationActivityLifecycleContextListener : Java.Lang.Object, Android.App.Application.IActivityLifecycleCallbacks
|
||||
{
|
||||
public readonly List<Activity> Activities = new List<Activity>();
|
||||
|
|
|
@ -1,37 +1,41 @@
|
|||
using Android.Views;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Hardware.Lights;
|
||||
using Android.Views;
|
||||
using AndroidX.Core.View.Accessibility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public static class AndroidExtensions
|
||||
{
|
||||
public static IReadOnlyCollection<IElement> GetChildren(this Android.Views.View nativeView, IApplication application, string parentId)
|
||||
public static IReadOnlyCollection<Element> GetChildren(this Android.Views.View nativeView, IApplication application, string parentId)
|
||||
{
|
||||
var c = new List<IElement>();
|
||||
var c = new List<Element>();
|
||||
|
||||
if (nativeView is ViewGroup vg)
|
||||
{
|
||||
for (int i = 0; i < vg.ChildCount; i++)
|
||||
c.Add(new AndroidView(application, vg.GetChildAt(i), parentId));
|
||||
c.Add(vg.GetChildAt(i).GetElement(application, parentId));
|
||||
}
|
||||
|
||||
return new ReadOnlyCollection<IElement>(c.ToList());
|
||||
return new ReadOnlyCollection<Element>(c.ToList());
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<IElement> GetChildren(this Android.App.Activity activity, IApplication application, string parentId)
|
||||
public static IReadOnlyCollection<Element> GetChildren(this Android.App.Activity activity, IApplication application, string parentId)
|
||||
{
|
||||
var rootView = activity.Window?.DecorView?.RootView ??
|
||||
activity.FindViewById(Android.Resource.Id.Content)?.RootView ??
|
||||
activity.Window?.DecorView?.FindViewById(Android.Resource.Id.Content);
|
||||
|
||||
if (rootView is not null)
|
||||
return new ReadOnlyCollection<IElement>(new List<IElement> { new AndroidView(application, rootView, parentId) });
|
||||
return new ReadOnlyCollection<Element>(new List<Element> { rootView.GetElement(application, parentId) });
|
||||
|
||||
return new ReadOnlyCollection<IElement>(new List<IElement>());
|
||||
return new ReadOnlyCollection<Element>(new List<Element>());
|
||||
}
|
||||
|
||||
public static string GetText(this Android.Views.View view)
|
||||
|
@ -85,5 +89,56 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
return rootView.EnsureUniqueId();
|
||||
}
|
||||
|
||||
|
||||
public static Element GetElement(this Android.Views.View androidview, IApplication application, string parentId = "")
|
||||
{
|
||||
var e = new Element(application, Platform.Android, androidview.EnsureUniqueId(), androidview, parentId)
|
||||
{
|
||||
AutomationId = androidview.GetAutomationId(),
|
||||
Enabled = androidview.Enabled,
|
||||
Visible = androidview.Visibility == ViewStates.Visible,
|
||||
Focused = androidview.Selected,
|
||||
Width = androidview.MeasuredWidth,
|
||||
Height = androidview.MeasuredHeight,
|
||||
Text = androidview.GetText(),
|
||||
};
|
||||
|
||||
var loc = new int[2];
|
||||
androidview?.GetLocationInWindow(loc);
|
||||
|
||||
if (loc != null && loc.Length >= 2)
|
||||
{
|
||||
e.X = loc[0];
|
||||
e.Y = loc[1];
|
||||
}
|
||||
|
||||
e.Children.AddRange(androidview.GetChildren(e.Application, e.Id));
|
||||
return e;
|
||||
}
|
||||
|
||||
public static Element GetElement(this Activity activity, IApplication application)
|
||||
{
|
||||
var e = new Element(application, Platform.Android, activity.GetWindowId(), activity)
|
||||
{
|
||||
AutomationId = activity.GetAutomationId(),
|
||||
X = (int)(activity.Window?.DecorView?.GetX() ?? -1f),
|
||||
Y = (int)(activity.Window?.DecorView?.GetY() ?? -1f),
|
||||
Width = activity.Window?.DecorView?.Width ?? -1,
|
||||
Height = activity.Window?.DecorView?.Height ?? -1,
|
||||
Text = activity.Title
|
||||
};
|
||||
|
||||
if (application is AndroidApplication androidApp)
|
||||
{
|
||||
var isCurrent = androidApp.IsActivityCurrent(activity);
|
||||
e.Visible = isCurrent;
|
||||
e.Enabled = isCurrent;
|
||||
e.Focused = isCurrent;
|
||||
}
|
||||
|
||||
e.Children.AddRange(activity.GetChildren(e.Application, e.Id));
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
using Android.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class AndroidView : Element
|
||||
{
|
||||
public AndroidView(IApplication application, Android.Views.View platformView, string? parentId = null)
|
||||
: base(application, Platform.Android, platformView.EnsureUniqueId(), parentId)
|
||||
{
|
||||
AutomationId = platformView.GetAutomationId();
|
||||
Children = platformView.GetChildren(Application, parentId);
|
||||
|
||||
Visible = platformView.Visibility == ViewStates.Visible;
|
||||
Enabled = platformView.Enabled;
|
||||
Focused = platformView.Selected;
|
||||
|
||||
var loc = new int[2];
|
||||
platformView?.GetLocationInWindow(loc);
|
||||
|
||||
if (loc != null && loc.Length >= 2)
|
||||
{
|
||||
X = loc[0];
|
||||
Y = loc[1];
|
||||
}
|
||||
|
||||
Width = platformView.MeasuredWidth;
|
||||
Height = platformView.MeasuredHeight;
|
||||
|
||||
Text = platformView.GetText();
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
protected Android.Views.View PlatformView { get; set; }
|
||||
|
||||
Point Location
|
||||
{
|
||||
get
|
||||
{
|
||||
var loc = new int[2];
|
||||
PlatformView.GetLocationInWindow(loc);
|
||||
|
||||
if (loc != null && loc.Length >= 2)
|
||||
return new Point(loc[0], loc[1]);
|
||||
|
||||
return Point.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//public void Clear()
|
||||
//{
|
||||
// if (NativeView is Android.Widget.TextView tv)
|
||||
// tv.Text = String.Empty;
|
||||
//}
|
||||
|
||||
//public void SendKeys(string text)
|
||||
//{
|
||||
// if (NativeView is Android.Widget.TextView tv)
|
||||
// tv.Text = text;
|
||||
//}
|
||||
|
||||
//public void Return()
|
||||
//{
|
||||
// throw new NotImplementedException();
|
||||
//}
|
||||
|
||||
//public bool Focus()
|
||||
//{
|
||||
// return NativeView.RequestFocus();
|
||||
//}
|
||||
|
||||
//public void Click()
|
||||
//{
|
||||
// NativeView.CallOnClick();
|
||||
//}
|
||||
|
||||
//public string GetProperty(string propertyName)
|
||||
//{
|
||||
// // TODO:
|
||||
// return null;
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
using Android.App;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class AndroidWindow : Element
|
||||
{
|
||||
public AndroidWindow(IApplication application, Activity activity)
|
||||
: base(application, Platform.Android, activity.GetWindowId())
|
||||
{
|
||||
PlatformWindow = activity;
|
||||
AutomationId = activity.GetAutomationId();
|
||||
|
||||
Children = activity.GetChildren(application, Id);
|
||||
X = (int)(activity.Window?.DecorView?.GetX() ?? -1f);
|
||||
Y = (int)(activity.Window?.DecorView?.GetY() ?? -1f);
|
||||
Width = activity.Window?.DecorView?.Width ?? -1;
|
||||
Height = activity.Window?.DecorView?.Height ?? -1;
|
||||
Text = PlatformWindow.Title;
|
||||
|
||||
if (application is AndroidApplication androidApp)
|
||||
{
|
||||
var isCurrent = androidApp.IsActivityCurrent(activity);
|
||||
Visible = isCurrent;
|
||||
Enabled = isCurrent;
|
||||
Focused = isCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
protected Activity PlatformWindow { get; set; }
|
||||
}
|
||||
}
|
|
@ -7,27 +7,7 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
public class WindowsAppSdkApplication : Application
|
||||
{
|
||||
public override Platform DefaultPlatform => Platform.WinAppSdk;
|
||||
|
||||
public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
=> RunOnMainThreadAsync(() =>
|
||||
{
|
||||
// TODO: Handle platform specific actions
|
||||
return Task.FromResult<IActionResult>(new ActionResult(ActionResultStatus.Unknown));
|
||||
});
|
||||
|
||||
public override async Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
=> new List<IElement>() { await RunOnMainThreadAsync(() => Task.FromResult(new WindowsAppSdkWindow(this, UI.Xaml.Window.Current))) };
|
||||
|
||||
public override Task<IEnumerable<IElement>> Descendants(Platform platform, string ofElementId = null, IElementSelector selector = null)
|
||||
=> RunOnMainThreadAsync(() => base.Descendants(platform, ofElementId, selector));
|
||||
|
||||
|
||||
public override Task<object> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
=> RunOnMainThreadAsync(() => base.GetProperty(platform, elementId, propertyName));
|
||||
|
||||
public override Task<IElement> Element(Platform platform, string elementId)
|
||||
=> RunOnMainThreadAsync(() => base.Element(platform, elementId));
|
||||
public override Platform DefaultPlatform => Platform.Winappsdk;
|
||||
|
||||
async Task<T> RunOnMainThreadAsync<T>(Func<Task<T>> action)
|
||||
{
|
||||
|
@ -50,7 +30,29 @@ namespace Microsoft.Maui.Automation
|
|||
return await tcs.Task;
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<Element>> GetElements(Platform platform, string elementId = null, int depth = 0)
|
||||
{
|
||||
var root = await GetRootElements();
|
||||
|
||||
if (string.IsNullOrEmpty(elementId))
|
||||
return root;
|
||||
|
||||
return root.FindDepthFirst(new IdSelector(elementId));
|
||||
}
|
||||
|
||||
public override async Task<string> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
var roots = await GetRootElements();
|
||||
|
||||
var element = roots.FindDepthFirst(new IdSelector(elementId))?.FirstOrDefault();
|
||||
|
||||
return element.GetType().GetProperty(propertyName)?.GetValue(element)?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
async Task<IEnumerable<Element>> GetRootElements()
|
||||
{
|
||||
var e = await RunOnMainThreadAsync(() => Task.FromResult(UI.Xaml.Window.Current.GetElement(this)));
|
||||
return new[] { e };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class WindowsAppSdkView : Element
|
||||
{
|
||||
public WindowsAppSdkView(IApplication application, UIElement platformView, string? parentId = null)
|
||||
: base(application, Platform.WinAppSdk, platformView.GetHashCode().ToString(), parentId)
|
||||
{
|
||||
PlatformView = platformView;
|
||||
PlatformElement = platformView;
|
||||
|
||||
AutomationId = platformView.GetType().Name;
|
||||
|
||||
var children = (platformView as Panel)?.Children?.Select(c => new WindowsAppSdkView(application, c, Id))?.ToList<IElement>() ?? new List<IElement>();
|
||||
Children = new ReadOnlyCollection<IElement>(children);
|
||||
|
||||
Visible = PlatformView.Visibility == UI.Xaml.Visibility.Visible;
|
||||
Enabled = PlatformView.IsTapEnabled;
|
||||
Focused = PlatformView.FocusState != FocusState.Unfocused;
|
||||
X = (int)PlatformView.ActualOffset.X;
|
||||
Y = (int)PlatformView.ActualOffset.Y;
|
||||
Width = (int)PlatformView.ActualSize.X;
|
||||
Height = (int)PlatformView.ActualSize.Y;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
protected UIElement PlatformView { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
// All the code in this file is only included on Windows.
|
||||
public class WindowsAppSdkWindow : Element
|
||||
{
|
||||
public WindowsAppSdkWindow(IApplication application, Microsoft.UI.Xaml.Window window)
|
||||
: base(application, Platform.WinAppSdk, window.GetHashCode().ToString())
|
||||
{
|
||||
PlatformWindow = window;
|
||||
|
||||
AutomationId = Id;
|
||||
|
||||
var children = new List<IElement> { new WindowsAppSdkView(application, PlatformWindow.Content, Id) };
|
||||
|
||||
Children = new ReadOnlyCollection<IElement>(children);
|
||||
|
||||
Width = (int)window.Bounds.Width;
|
||||
Height = (int)window.Bounds.Height;
|
||||
Text = window.Title;
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
public readonly UI.Xaml.Window PlatformWindow;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace Microsoft.Maui.Automation;
|
||||
|
||||
internal static class WindowsExtensions
|
||||
{
|
||||
public static Element GetElement(this UIElement uiElement, IApplication application, string parentId = "")
|
||||
{
|
||||
var e = new Element(application, Platform.Winappsdk, uiElement.GetHashCode().ToString(), uiElement, parentId)
|
||||
{
|
||||
AutomationId = uiElement.GetType().Name,
|
||||
Visible = uiElement.Visibility == UI.Xaml.Visibility.Visible,
|
||||
Enabled = uiElement.IsTapEnabled,
|
||||
Focused = uiElement.FocusState != FocusState.Unfocused,
|
||||
X = (int)uiElement.ActualOffset.X,
|
||||
Y = (int)uiElement.ActualOffset.Y,
|
||||
Width = (int)uiElement.ActualSize.X,
|
||||
Height = (int)uiElement.ActualSize.Y
|
||||
};
|
||||
|
||||
var children = (uiElement as Panel)?.Children?.Select(c => c.GetElement(application, e.Id))?.ToList() ?? new List<Element>();
|
||||
e.Children.AddRange(children);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public static Element GetElement(this Microsoft.UI.Xaml.Window window, IApplication application, string parentId = "")
|
||||
{
|
||||
var e = new Element(application, Platform.Winappsdk, window.GetHashCode().ToString(), window, parentId)
|
||||
{
|
||||
PlatformElement = window,
|
||||
AutomationId = window.GetType().Name,
|
||||
X = (int)window.Bounds.X,
|
||||
Y = (int)window.Bounds.Y,
|
||||
Width = (int)window.Bounds.Width,
|
||||
Height = (int)window.Bounds.Height,
|
||||
Text = window.Title
|
||||
|
||||
};
|
||||
|
||||
e.Children.Add(window.Content.GetElement(application, e.Id));
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
|
@ -56,55 +56,8 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
public abstract Platform DefaultPlatform { get; }
|
||||
|
||||
public abstract Task<IEnumerable<IElement>> Children(Platform platform);
|
||||
public abstract Task<string> GetProperty(Platform platform, string elementId, string propertyName);
|
||||
|
||||
public abstract Task<IActionResult> Perform(Platform platform, string elementId, IAction action);
|
||||
|
||||
public virtual async Task<object?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
var element = await Element(platform, elementId);
|
||||
|
||||
var t = element?.PlatformElement?.GetType();
|
||||
|
||||
if (t != null)
|
||||
{
|
||||
var prop = t.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
if (prop != null)
|
||||
{
|
||||
return Task.FromResult(prop.GetValue(element?.PlatformElement));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<object?>(null);
|
||||
}
|
||||
|
||||
public virtual async Task<IElement?> Element(Platform platform, string elementId)
|
||||
{
|
||||
return (await Children(platform)).FindDepthFirst(new IdSelector(elementId))?.FirstOrDefault();
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<IElement>> Descendants(Platform platform, string? ofElementId = null, IElementSelector? selector = null)
|
||||
{
|
||||
var descendants = new List<IElement>();
|
||||
|
||||
if (string.IsNullOrEmpty(ofElementId))
|
||||
{
|
||||
var children = (await Children(platform))?.FindBreadthFirst(selector);
|
||||
|
||||
if (children is not null && children.Any())
|
||||
descendants.AddRange(children);
|
||||
}
|
||||
else
|
||||
{
|
||||
var element = await Element(platform, ofElementId);
|
||||
|
||||
var children = element?.Children?.FindBreadthFirst(selector);
|
||||
if (children is not null && children.Any())
|
||||
descendants.AddRange(children);
|
||||
}
|
||||
|
||||
return descendants;
|
||||
}
|
||||
public abstract Task<IEnumerable<Element>> GetElements(Platform platform, string? elementId = null, int depth = 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,37 +8,37 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
public static class ApiExtensions
|
||||
{
|
||||
public static Task<IEnumerable<IElement>> By(this IElement element, params IElementSelector[] selectors)
|
||||
=> All(element.Application, element.Platform, selectors);
|
||||
//public static Task<IEnumerable<Element>> By(this Element element, params IElementSelector[] selectors)
|
||||
// => All(element.Application, element.Platform, selectors);
|
||||
|
||||
public static async Task<IElement?> FirstBy(this IElement element, params IElementSelector[] selectors)
|
||||
=> (await By(element.Application, element.Platform, selectors)).FirstOrDefault();
|
||||
//public static async Task<Element?> FirstBy(this Element element, params IElementSelector[] selectors)
|
||||
// => (await By(element.Application, element.Platform, selectors)).FirstOrDefault();
|
||||
|
||||
public static Task<IEnumerable<IElement>> By(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
=> All(app, platform, selectors);
|
||||
//public static Task<IEnumerable<Element>> By(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
// => All(app, platform, selectors);
|
||||
|
||||
public static async Task<IElement?> FirstBy(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
=> (await By(app, platform, selectors))?.FirstOrDefault();
|
||||
//public static async Task<Element?> FirstBy(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
// => (await By(app, platform, selectors))?.FirstOrDefault();
|
||||
|
||||
public static Task<IEnumerable<IElement>> ByAutomationId(this IApplication app, Platform platform, string automationId, StringComparison comparison = StringComparison.Ordinal)
|
||||
=> By(app, platform, new AutomationIdSelector(automationId, comparison));
|
||||
//public static Task<IEnumerable<Element>> ByAutomationId(this IApplication app, Platform platform, string automationId, StringComparison comparison = StringComparison.Ordinal)
|
||||
// => By(app, platform, new AutomationIdSelector(automationId, comparison));
|
||||
|
||||
public static Task<IElement?> ById(this IApplication app, Platform platform, string id, StringComparison comparison = StringComparison.Ordinal)
|
||||
=> FirstBy(app, platform, new IdSelector(id, comparison));
|
||||
//public static Task<Element?> ById(this IApplication app, Platform platform, string id, StringComparison comparison = StringComparison.Ordinal)
|
||||
// => FirstBy(app, platform, new IdSelector(id, comparison));
|
||||
|
||||
|
||||
|
||||
public static Task<IEnumerable<IElement>> All(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
=> app.Descendants(platform, selector: new CompoundSelector(any: false, selectors));
|
||||
//public static Task<IEnumerable<Element>> All(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
// => app.GetElements(platform, selector: new CompoundSelector(any: false, selectors));
|
||||
|
||||
public static Task<IEnumerable<IElement>> Any(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
=> app.Descendants(platform, selector: new CompoundSelector(any: true, selectors));
|
||||
//public static Task<IEnumerable<Element>> Any(this IApplication app, Platform platform, params IElementSelector[] selectors)
|
||||
// => app.GetElements(platform, selector: new CompoundSelector(any: true, selectors));
|
||||
|
||||
public static Task<IEnumerable<IElement>> All(this IElement element, Platform platform, params IElementSelector[] selectors)
|
||||
=> element.Application.Descendants(platform, element.Id, new CompoundSelector(any: false, selectors));
|
||||
//public static Task<IEnumerable<Element>> All(this Element element, Platform platform, params IElementSelector[] selectors)
|
||||
// => element.Application.GetElements(platform, element.Id, new CompoundSelector(any: false, selectors));
|
||||
|
||||
public static Task<IEnumerable<IElement>> Any(this IElement element, Platform platform, params IElementSelector[] selectors)
|
||||
=> element.Application.Descendants(platform, element.Id, new CompoundSelector(any: true, selectors));
|
||||
//public static Task<IEnumerable<Element>> Any(this Element element, Platform platform, params IElementSelector[] selectors)
|
||||
// => element.Application.GetElements(platform, element.Id, new CompoundSelector(any: true, selectors));
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,104 +1,35 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public abstract class Element : IElement
|
||||
{
|
||||
protected Element()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
Platform = Platform.MAUI;
|
||||
Application = new NullApplication(Platform);
|
||||
ParentId = string.Empty;
|
||||
namespace Microsoft.Maui.Automation;
|
||||
public partial class Element
|
||||
{
|
||||
public Element(IApplication application, Platform platform, string id, object platformElement, string parentId = "")
|
||||
: base()
|
||||
{
|
||||
Application = application;
|
||||
ParentId = parentId;
|
||||
Id = id;
|
||||
AutomationId = Id;
|
||||
Type = platformElement.GetType().Name;
|
||||
FullType = platformElement.GetType().FullName ?? Type;
|
||||
|
||||
AutomationId = Id;
|
||||
Type = GetType().Name;
|
||||
FullType = GetType().FullName ?? Type;
|
||||
Visible = false;
|
||||
Enabled = false;
|
||||
Focused = false;
|
||||
X = -1;
|
||||
Y = -1;
|
||||
Platform = platform;
|
||||
Width = -1;
|
||||
Height = -1;
|
||||
PlatformElement = platformElement;
|
||||
}
|
||||
|
||||
Visible = false;
|
||||
Enabled = false;
|
||||
Focused = false;
|
||||
X = -1;
|
||||
Y = -1;
|
||||
Platform = Platform.MAUI;
|
||||
Children = new ReadOnlyCollection<IElement>(new List<IElement>());
|
||||
Width = -1;
|
||||
Height = -1;
|
||||
}
|
||||
|
||||
public Element(IApplication application, Platform platform, string id, string? parentId = null)
|
||||
{
|
||||
Application = application;
|
||||
ParentId = parentId;
|
||||
Id = id;
|
||||
AutomationId = Id;
|
||||
Type = GetType().Name;
|
||||
FullType = GetType().FullName ?? Type;
|
||||
|
||||
Visible = false;
|
||||
Enabled = false;
|
||||
Focused = false;
|
||||
X = -1;
|
||||
Y = -1;
|
||||
Platform = platform;
|
||||
Children = new ReadOnlyCollection<IElement>(new List<IElement>());
|
||||
Width = -1;
|
||||
Height = -1;
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
public virtual IApplication Application { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual string? ParentId { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual bool Visible { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual bool Enabled { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual bool Focused { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual int X { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual int Y { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual Platform Platform { get; protected set; }
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
public virtual object? PlatformElement { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
[Newtonsoft.Json.JsonProperty]
|
||||
public virtual IReadOnlyCollection<IElement> Children { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual string Id { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual string AutomationId { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual string Type { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual string FullType { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual string? Text { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual int Width { get; protected set; }
|
||||
|
||||
[JsonInclude]
|
||||
public virtual int Height { get; protected set; }
|
||||
}
|
||||
}
|
||||
public IApplication? Application { get; set; }
|
||||
public object? PlatformElement { get; set; }
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ namespace Microsoft.Maui.Automation
|
|||
st.Push(elem);
|
||||
}
|
||||
|
||||
internal static IEnumerable<IElement> FindDepthFirst(this IEnumerable<IElement> elements, IElementSelector? selector)
|
||||
internal static IEnumerable<Element> FindDepthFirst(this IEnumerable<Element> elements, IElementSelector? selector)
|
||||
{
|
||||
var st = new Stack<IElement>();
|
||||
var st = new Stack<Element>();
|
||||
st.PushAllReverse(elements);
|
||||
|
||||
while (st.Count > 0)
|
||||
|
@ -31,9 +31,9 @@ namespace Microsoft.Maui.Automation
|
|||
}
|
||||
}
|
||||
|
||||
internal static IEnumerable<IElement> FindBreadthFirst(this IEnumerable<IElement> elements, IElementSelector? selector)
|
||||
internal static IEnumerable<Element> FindBreadthFirst(this IEnumerable<Element> elements, IElementSelector? selector)
|
||||
{
|
||||
var q = new Queue<IElement>();
|
||||
var q = new Queue<Element>();
|
||||
|
||||
foreach (var e in elements)
|
||||
q.Enqueue(e);
|
||||
|
@ -55,9 +55,9 @@ namespace Microsoft.Maui.Automation
|
|||
return new ReadOnlyCollection<T>(elems.ToList());
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<IElement> AsReadOnlyCollection(this IElement element)
|
||||
public static IReadOnlyCollection<Element> AsReadOnlyCollection(this Element element)
|
||||
{
|
||||
var list = new List<IElement> { element };
|
||||
var list = new List<Element> { element };
|
||||
return list.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
public interface IAction
|
||||
{
|
||||
public Task<IActionResult> Invoke(IElement element);
|
||||
public Task<IActionResult> Invoke(Element element);
|
||||
|
||||
//public void Clear();
|
||||
|
||||
|
|
|
@ -4,14 +4,8 @@
|
|||
{
|
||||
public Platform DefaultPlatform { get; }
|
||||
|
||||
public Task<IEnumerable<IElement>> Children(Platform platform);
|
||||
public Task<IEnumerable<Element>> GetElements(Platform platform, string? elementId = null, int childDepth = 0);
|
||||
|
||||
public Task<IElement?> Element(Platform platform, string elementId);
|
||||
|
||||
public Task<IEnumerable<IElement>> Descendants(Platform platform, string? ofElementId = null, IElementSelector? selector = null);
|
||||
|
||||
public Task<IActionResult> Perform(Platform platform, string elementId, IAction action);
|
||||
|
||||
public Task<object?> GetProperty(Platform platform, string elementId, string propertyName);
|
||||
public Task<string> GetProperty(Platform platform, string elementId, string propertyName);
|
||||
}
|
||||
}
|
|
@ -1,41 +1,36 @@
|
|||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
[JsonConverter(typeof(ElementConverter))]
|
||||
public interface IElement
|
||||
{
|
||||
public IApplication Application { get; }
|
||||
//using Google.Protobuf.Collections;
|
||||
|
||||
public Platform Platform { get; }
|
||||
//namespace Microsoft.Maui.Automation;
|
||||
|
||||
public string? ParentId { get; }
|
||||
//public interface IElement
|
||||
//{
|
||||
// public IApplication Application { get; }
|
||||
|
||||
public bool Visible { get; }
|
||||
public bool Enabled { get; }
|
||||
public bool Focused { get; }
|
||||
// public Platform Platform { get; }
|
||||
|
||||
// public string? ParentId { get; }
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[JsonIgnore]
|
||||
public object? PlatformElement { get; }
|
||||
// public bool Visible { get; }
|
||||
// public bool Enabled { get; }
|
||||
// public bool Focused { get; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IReadOnlyCollection<IElement> Children { get; }
|
||||
// public object? PlatformElement { get; }
|
||||
|
||||
public string Id { get; }
|
||||
// public RepeatedField<Element> Children { get; }
|
||||
|
||||
public string AutomationId { get; }
|
||||
// public string Id { get; }
|
||||
|
||||
public string Type { get; }
|
||||
public string FullType { get; }
|
||||
// public string AutomationId { get; }
|
||||
|
||||
public string? Text { get; }
|
||||
// public string Type { get; }
|
||||
// public string FullType { get; }
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
}
|
||||
}
|
||||
// public string? Text { get; }
|
||||
|
||||
// public int Width { get; }
|
||||
// public int Height { get; }
|
||||
// public int X { get; }
|
||||
// public int Y { get; }
|
||||
//}
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.21.5" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.46.3" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.48.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="..\proto\types.proto" />
|
||||
<Protobuf Include="..\proto\remoteapp.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -4,42 +4,36 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class MultiPlatformApplication : Application
|
||||
{
|
||||
public class MultiPlatformApplication : Application
|
||||
{
|
||||
public MultiPlatformApplication(Platform defaultPlatform, params (Platform platform, IApplication app)[] apps)
|
||||
{
|
||||
{
|
||||
DefaultPlatform = defaultPlatform;
|
||||
|
||||
PlatformApps = new Dictionary<Platform, IApplication>();
|
||||
|
||||
foreach (var app in apps)
|
||||
PlatformApps[app.platform] = app.app;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly IDictionary<Platform, IApplication> PlatformApps;
|
||||
|
||||
|
||||
public override Platform DefaultPlatform { get; }
|
||||
public override Platform DefaultPlatform { get; }
|
||||
|
||||
IApplication GetApp(Platform platform)
|
||||
{
|
||||
IApplication GetApp(Platform platform)
|
||||
{
|
||||
if (!PlatformApps.ContainsKey(platform))
|
||||
throw new PlatformNotSupportedException();
|
||||
|
||||
return PlatformApps[platform];
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
=> GetApp(platform).Children(platform);
|
||||
public override Task<IEnumerable<Element>> GetElements(Platform platform, string? elementId = null, int depth = 0)
|
||||
=> GetApp(platform).GetElements(platform, elementId, depth);
|
||||
|
||||
public override Task<IElement?> Element(Platform platform, string elementId)
|
||||
=> GetApp(platform).Element(platform, elementId);
|
||||
|
||||
public override Task<object?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
public override Task<string?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
=> GetApp(platform).GetProperty(platform, elementId, propertyName);
|
||||
|
||||
public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
=> GetApp(platform).Perform(platform, elementId, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class NullApplication : IApplication
|
||||
{
|
||||
public NullApplication(Platform platform = Platform.MAUI)
|
||||
=> DefaultPlatform = platform;
|
||||
|
||||
public Platform DefaultPlatform { get; }
|
||||
|
||||
public Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
=> Task.FromResult(Enumerable.Empty<IElement>());
|
||||
|
||||
public Task<IEnumerable<IElement>> Descendants(Platform platform, string? ofElementId = null, IElementSelector? selector = null)
|
||||
=> Task.FromResult(Enumerable.Empty<IElement>());
|
||||
|
||||
public Task<IElement?> Element(Platform platform, string elementId)
|
||||
{
|
||||
return Task.FromResult<IElement?>(null);
|
||||
}
|
||||
|
||||
public Task<object?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
return Task.FromResult<object?>(null);
|
||||
}
|
||||
|
||||
public Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
{
|
||||
return Task.FromResult<IActionResult>(new ActionResult(ActionResultStatus.Unknown));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,30 +3,29 @@ using System.Text.Json.Serialization;
|
|||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum Platform
|
||||
{
|
||||
[EnumMember(Value = "MAUI")]
|
||||
MAUI = 0,
|
||||
// public enum Platform
|
||||
// {
|
||||
// [EnumMember(Value = "MAUI")]
|
||||
//MAUI = 0,
|
||||
|
||||
[EnumMember(Value = "iOS")]
|
||||
iOS = 1,
|
||||
// [EnumMember(Value = "iOS")]
|
||||
// iOS = 100,
|
||||
|
||||
[EnumMember(Value = "MacCatalyst")]
|
||||
MacCatalyst = 2,
|
||||
// [EnumMember(Value = "MacCatalyst")]
|
||||
// MacCatalyst = 200,
|
||||
|
||||
[EnumMember(Value = "macOS")]
|
||||
MacOS = 3,
|
||||
// [EnumMember(Value = "macOS")]
|
||||
// MacOS = 210,
|
||||
|
||||
[EnumMember(Value = "tvOS")]
|
||||
tvOS = 4,
|
||||
// [EnumMember(Value = "tvOS")]
|
||||
// tvOS = 300,
|
||||
|
||||
[EnumMember(Value = "Android")]
|
||||
Android = 10,
|
||||
// [EnumMember(Value = "Android")]
|
||||
// Android = 400,
|
||||
|
||||
[EnumMember(Value = "WindowsAppSdk")]
|
||||
WinAppSdk = 20
|
||||
}
|
||||
// [EnumMember(Value = "WindowsAppSdk")]
|
||||
// WinAppSdk = 500
|
||||
// }
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum ActionResultStatus
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
// https://gist.github.com/Ilchert/4854a20f790ca963d1ab17d99433c7da
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)]
|
||||
public class JsonKnownTypeAttribute : Attribute
|
||||
{
|
||||
public Type NestedType { get; }
|
||||
public string Discriminator { get; }
|
||||
|
||||
public JsonKnownTypeAttribute(Type nestedType, string discriminator)
|
||||
{
|
||||
NestedType = nestedType;
|
||||
Discriminator = discriminator;
|
||||
}
|
||||
}
|
||||
|
||||
public class PolyJsonConverter : JsonConverterFactory
|
||||
{
|
||||
private static readonly Type converterType = typeof(PolyJsonConverter<>);
|
||||
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
var attr = typeToConvert.GetCustomAttributes<JsonKnownTypeAttribute>(false);
|
||||
return attr.Any();
|
||||
}
|
||||
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var attr = typeToConvert.GetCustomAttributes<JsonKnownTypeAttribute>();
|
||||
var concreteConverterType = converterType.MakeGenericType(typeToConvert);
|
||||
return (JsonConverter)Activator.CreateInstance(concreteConverterType, attr);
|
||||
}
|
||||
}
|
||||
|
||||
public class ElementConverter : JsonConverter<IElement>
|
||||
{
|
||||
private static readonly JsonEncodedText TypeProperty = JsonEncodedText.Encode("$type");
|
||||
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
=> typeToConvert.IsAssignableTo(typeof(IElement));
|
||||
|
||||
public override IElement? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
using var doc = JsonDocument.ParseValue(ref reader);
|
||||
|
||||
return (IElement)doc.Deserialize(typeToConvert);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, IElement value, JsonSerializerOptions options)
|
||||
{
|
||||
var type = value.GetType();
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName(TypeProperty.EncodedUtf8Bytes);
|
||||
writer.WriteStringValue(type.FullName);
|
||||
using var doc = JsonSerializer.SerializeToDocument(value, type, options);
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
prop.WriteTo(writer);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
internal class PolyJsonConverter<T> : JsonConverter<T>
|
||||
{
|
||||
private readonly Dictionary<string, Type> _discriminatorCache;
|
||||
private readonly Dictionary<Type, string> _typeCache;
|
||||
|
||||
private static readonly JsonEncodedText TypeProperty = JsonEncodedText.Encode("$type");
|
||||
|
||||
public PolyJsonConverter(IEnumerable<JsonKnownTypeAttribute> resolvers)
|
||||
{
|
||||
_discriminatorCache = resolvers.ToDictionary(p => p.Discriminator, p => p.NestedType);
|
||||
_typeCache = resolvers.ToDictionary(p => p.NestedType, p => p.Discriminator);
|
||||
}
|
||||
|
||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
using var doc = JsonDocument.ParseValue(ref reader);
|
||||
if (!doc.RootElement.TryGetProperty(TypeProperty.EncodedUtf8Bytes, out var typeElement))
|
||||
throw new JsonException();
|
||||
|
||||
var discriminator = typeElement.GetString();
|
||||
if (discriminator is null || !_discriminatorCache.TryGetValue(discriminator, out var type))
|
||||
throw new JsonException();
|
||||
|
||||
|
||||
return (T)doc.Deserialize(type, options);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||
{
|
||||
var type = value.GetType();
|
||||
if (!_typeCache.TryGetValue(type, out var discriminator))
|
||||
throw new JsonException();
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName(TypeProperty.EncodedUtf8Bytes);
|
||||
writer.WriteStringValue(discriminator);
|
||||
using var doc = JsonSerializer.SerializeToDocument(value, type, options);
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
prop.WriteTo(writer);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonProtectedResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
|
||||
{
|
||||
protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization)
|
||||
{
|
||||
var prop = base.CreateProperty(member, memberSerialization);
|
||||
if (!prop.Writable)
|
||||
{
|
||||
var property = member as PropertyInfo;
|
||||
var hasPrivateSetter = property?.GetSetMethod(true) != null;
|
||||
prop.Writable = hasPrivateSetter;
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
public StringComparison Comparison { get; protected set; }
|
||||
|
||||
public bool Matches(IElement view)
|
||||
public bool Matches(Element view)
|
||||
=> view?.AutomationId?.Equals(AutomationId, Comparison) ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
public bool Any { get; protected set; }
|
||||
|
||||
public bool Matches(IElement element)
|
||||
public bool Matches(Element element)
|
||||
{
|
||||
foreach (var s in Selectors)
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{
|
||||
}
|
||||
|
||||
public bool Matches(IElement view)
|
||||
public bool Matches(Element view)
|
||||
=> true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
public interface IElementSelector
|
||||
{
|
||||
public bool Matches(IElement element);
|
||||
public bool Matches(Element element);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
public StringComparison Comparison { get; protected set; }
|
||||
|
||||
public bool Matches(IElement view)
|
||||
public bool Matches(Element view)
|
||||
=> view?.AutomationId?.Equals(Id, Comparison) ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
public Regex Rx { get; protected set; }
|
||||
|
||||
public bool Matches(IElement view)
|
||||
public bool Matches(Element view)
|
||||
=> view.Text != null && Rx.IsMatch(view.Text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Microsoft.Maui.Automation
|
|||
|
||||
public string Text { get; protected set; }
|
||||
|
||||
public bool Matches(IElement view)
|
||||
public bool Matches(Element view)
|
||||
=> Rule switch
|
||||
{
|
||||
TextMatchRule.Contains => view.Text?.Contains(Text, Comparison) ?? false,
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
public bool FullName { get; protected set; }
|
||||
|
||||
public bool Matches(IElement element)
|
||||
public bool Matches(Element element)
|
||||
=> element.Type.Equals(TypeName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
{
|
||||
public static class ViewExtensions
|
||||
{
|
||||
public static bool IsTopLevel(this IElement element)
|
||||
public static bool IsTopLevel(this Element element)
|
||||
=> element?.ParentId == element?.Id;
|
||||
|
||||
|
||||
public static string ToString(this IElement element, int depth, int indentSpaces = 2)
|
||||
public static string ToString(this Element element, int depth, int indentSpaces = 2)
|
||||
{
|
||||
var v = element;
|
||||
var t = element.IsTopLevel() ? "window" : "view";
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
namespace Microsoft.Maui.Automation.Remote
|
||||
{
|
||||
public interface IRemoteAutomationService
|
||||
{
|
||||
public Task<RemoteElement[]> Children(Platform platform);
|
||||
public Task<RemoteElement?> Element(Platform platform, string elementId);
|
||||
public Task<RemoteElement[]> Descendants(Platform platform, string? elementId = null, IElementSelector? selector = null);
|
||||
public Task<IActionResult> Perform(Platform platform, string elementId, IAction action);
|
||||
public Task<object?> GetProperty(Platform platform, string elementId, string propertyName);
|
||||
}
|
||||
}
|
|
@ -9,12 +9,18 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Streamer\" />
|
||||
<Protobuf Include="..\proto\remoteapp.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.21.5" />
|
||||
<PackageReference Include="Grpc.Core" Version="2.46.3" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.48.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
using Streamer;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.Maui.Automation.Remote
|
||||
{
|
||||
public class RemoteApplication : IApplication
|
||||
{
|
||||
public RemoteApplication(Platform defaultPlatform, Stream stream, IRemoteAutomationService? remoteAutomationService = null)
|
||||
{
|
||||
DefaultPlatform = defaultPlatform;
|
||||
Stream = stream;
|
||||
|
||||
if (remoteAutomationService != null)
|
||||
{
|
||||
RemoteAutomationService = remoteAutomationService;
|
||||
Server = Streamer.Channel.CreateServer();
|
||||
Server.Bind(
|
||||
new MethodHandler<ChildrenResponse, ChildrenRequest>(
|
||||
nameof(IRemoteAutomationService.Children),
|
||||
async req => new ChildrenResponse(await RemoteAutomationService.Children(req.Platform))),
|
||||
new MethodHandler<ElementResponse, ElementRequest>(
|
||||
nameof(IRemoteAutomationService.Element),
|
||||
async req => new ElementResponse(await RemoteAutomationService.Element(req.Platform, req.ElementId))),
|
||||
new MethodHandler<DescendantsResponse, DescendantsRequest>(
|
||||
nameof(IRemoteAutomationService.Descendants),
|
||||
async req => new DescendantsResponse(await RemoteAutomationService.Descendants(req.Platform, req.ElementId, req.Selector))),
|
||||
new MethodHandler<PerformResponse, PerformRequest>(
|
||||
nameof(IRemoteAutomationService.Perform),
|
||||
async req => new PerformResponse(await RemoteAutomationService.Perform(req.Platform, req.ElementId, req.Action))),
|
||||
new MethodHandler<GetPropertyResponse, GetPropertyRequest>(
|
||||
nameof(IRemoteAutomationService.GetProperty),
|
||||
async req => new GetPropertyResponse(await RemoteAutomationService.GetProperty(req.Platform, req.ElementId, req.PropertyName)))
|
||||
);
|
||||
|
||||
_ = Task.Run(() => { _ = Server.StartAsync(Stream); });
|
||||
}
|
||||
else
|
||||
{
|
||||
client = new ClientChannel(Stream);
|
||||
}
|
||||
}
|
||||
|
||||
public Platform DefaultPlatform { get; }
|
||||
|
||||
protected readonly Streamer.ServerChannel? Server;
|
||||
readonly Streamer.ClientChannel? client;
|
||||
protected Streamer.ClientChannel Client => client ?? throw new NullReferenceException();
|
||||
|
||||
|
||||
|
||||
protected readonly IRemoteAutomationService? RemoteAutomationService;
|
||||
public readonly Stream Stream;
|
||||
|
||||
public async Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await Client.InvokeAsync<ChildrenRequest, ChildrenResponse>(new ChildrenRequest(platform));
|
||||
return response?.Result ?? Array.Empty<RemoteElement>();
|
||||
} catch (Exception ex)
|
||||
{
|
||||
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IElement?> Element(Platform platform, string elementId)
|
||||
{
|
||||
var response = await Client.InvokeAsync<ElementRequest, ElementResponse>(new ElementRequest(platform, elementId));
|
||||
return response?.Element;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IElement>> Descendants(Platform platform, string? elementId = null, IElementSelector? selector = null)
|
||||
{
|
||||
var response = await Client.InvokeAsync<DescendantsRequest, DescendantsResponse>(new DescendantsRequest(platform, elementId, selector));
|
||||
|
||||
return response?.Result ?? Enumerable.Empty<IElement>();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
{
|
||||
var response = await Client.InvokeAsync<PerformRequest, PerformResponse>(new PerformRequest(platform, elementId, action));
|
||||
|
||||
return response?.Result ?? new ActionResult(ActionResultStatus.Error, "Unknown");
|
||||
}
|
||||
|
||||
public async Task<object?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
var response = await Client.InvokeAsync<GetPropertyRequest, GetPropertyResponse>(new GetPropertyRequest(platform, elementId, propertyName));
|
||||
|
||||
return response?.Result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
using Microsoft.Maui.Automation.Remote;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Maui.Automation
|
||||
{
|
||||
public class RemoteAutomationService : IRemoteAutomationService
|
||||
{
|
||||
public RemoteAutomationService(IApplication app)
|
||||
{
|
||||
PlatformApp = app;
|
||||
}
|
||||
|
||||
readonly IApplication PlatformApp;
|
||||
|
||||
|
||||
|
||||
public async Task<RemoteElement?> Element(Platform platform, string elementId)
|
||||
{
|
||||
var platformElement = await PlatformApp.Element(platform, elementId);
|
||||
|
||||
if (platformElement is null)
|
||||
return null;
|
||||
|
||||
return new RemoteElement(PlatformApp, platformElement, platformElement.ParentId);
|
||||
}
|
||||
|
||||
public async Task<RemoteElement[]> Children(Platform platform)
|
||||
{
|
||||
var results = new List<RemoteElement>();
|
||||
var children = await PlatformApp.Children(platform);
|
||||
|
||||
foreach (var c in children)
|
||||
{
|
||||
var e = new RemoteElement(PlatformApp, c, c.ParentId);
|
||||
results.Add(e);
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
=> PlatformApp.Perform(platform, elementId, action);
|
||||
|
||||
public Task<object?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
=> PlatformApp.GetProperty(platform, elementId, propertyName);
|
||||
|
||||
void ConvertChildren(RemoteElement parent, IEnumerable<IElement> toConvert, IElementSelector? selector, string? parentId = null)
|
||||
{
|
||||
selector ??= new DefaultViewSelector();
|
||||
|
||||
var converted = toConvert
|
||||
.Where(c => selector.Matches(c))
|
||||
.Select(c => new RemoteElement(PlatformApp, c, parentId)!);
|
||||
|
||||
parent.SetChildren(converted);
|
||||
|
||||
foreach (var v in converted)
|
||||
ConvertChildren(v, v.Children, selector);
|
||||
}
|
||||
|
||||
public async Task<RemoteElement[]> Descendants(Platform platform, string? elementId = null, IElementSelector? selector = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(elementId))
|
||||
{
|
||||
var view = await PlatformApp.Element(platform, elementId);
|
||||
|
||||
if (view == null)
|
||||
return Array.Empty<RemoteElement>();
|
||||
|
||||
var remoteView = new RemoteElement(PlatformApp, view, view.ParentId);
|
||||
|
||||
ConvertChildren(remoteView, remoteView.Children, selector);
|
||||
|
||||
var res= remoteView.Children.Cast<RemoteElement>().ToArray();
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
var children = new List<RemoteElement>();
|
||||
|
||||
foreach (var c in await Children(platform))
|
||||
{
|
||||
var remoteView = new RemoteElement(PlatformApp, c, c.ParentId);
|
||||
|
||||
ConvertChildren(remoteView, remoteView.Children, selector);
|
||||
|
||||
children.Add(c);
|
||||
}
|
||||
|
||||
return children.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Maui.Automation.Remote
|
||||
{
|
||||
public class RemoteElement : Element
|
||||
{
|
||||
//[JsonConstructor]
|
||||
protected RemoteElement() : base() { }
|
||||
|
||||
[JsonConstructor]
|
||||
public RemoteElement(Platform platform = Platform.MAUI)
|
||||
: base(new NullApplication(platform), platform, "")
|
||||
{ }
|
||||
|
||||
public RemoteElement(IApplication application, IElement from, string? parentId = null)
|
||||
: base(application, from.Platform, from.Id, parentId)
|
||||
{
|
||||
Id = from.Id;
|
||||
AutomationId = from.AutomationId;
|
||||
|
||||
Type = from.Type;
|
||||
FullType = from.FullType;
|
||||
|
||||
Visible = from.Visible;
|
||||
Enabled = from.Enabled;
|
||||
Focused = from.Focused;
|
||||
|
||||
Text = from.Text;
|
||||
|
||||
X = from.X;
|
||||
Y = from.Y;
|
||||
Width = from.Width;
|
||||
Height = from.Height;
|
||||
|
||||
var children = from.Children?.Select(c => new RemoteElement(application, c, Id))
|
||||
?? Enumerable.Empty<RemoteElement>();
|
||||
|
||||
Children = new ReadOnlyCollection<IElement>(children.ToList<IElement>());
|
||||
|
||||
}
|
||||
|
||||
internal void SetChildren(IEnumerable<RemoteElement> children)
|
||||
{
|
||||
Children = new ReadOnlyCollection<IElement>(children.ToList<IElement>());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class ChildrenRequest : Request
|
||||
{
|
||||
public ChildrenRequest(Platform platform)
|
||||
{
|
||||
Method = nameof(IRemoteAutomationService.Children);
|
||||
Platform = platform;
|
||||
}
|
||||
|
||||
public Platform Platform { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class DescendantsRequest : Request
|
||||
{
|
||||
public DescendantsRequest(Platform platform, string? elementId = null, IElementSelector? selector = null)
|
||||
{
|
||||
Method = nameof(IRemoteAutomationService.Descendants);
|
||||
Platform = platform;
|
||||
ElementId = elementId;
|
||||
Selector = selector;
|
||||
}
|
||||
|
||||
public Platform Platform { get; set; }
|
||||
|
||||
public string? ElementId { get; set; } = null;
|
||||
|
||||
public IElementSelector? Selector { get;}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class ElementRequest : Request
|
||||
{
|
||||
public ElementRequest(Platform platform, string elementId)
|
||||
{
|
||||
Method = nameof(IRemoteAutomationService.Element);
|
||||
Platform = platform;
|
||||
ElementId = elementId;
|
||||
}
|
||||
|
||||
public Platform Platform { get; set; }
|
||||
|
||||
public string ElementId { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class GetPropertyRequest : Request
|
||||
{
|
||||
public GetPropertyRequest(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
Method = nameof(IRemoteAutomationService.GetProperty);
|
||||
Platform = platform;
|
||||
ElementId = elementId;
|
||||
PropertyName = propertyName;
|
||||
}
|
||||
|
||||
public Platform Platform { get; set; }
|
||||
|
||||
public string ElementId { get; set; }
|
||||
|
||||
public string PropertyName { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class PerformRequest : Request
|
||||
{
|
||||
public PerformRequest(Platform platform, string elementId, IAction action)
|
||||
{
|
||||
Method = nameof(IRemoteAutomationService.Perform);
|
||||
Platform = platform;
|
||||
ElementId = elementId;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public Platform Platform { get; set; }
|
||||
|
||||
public string ElementId { get; set; }
|
||||
|
||||
public IAction Action { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class ChildrenResponse : Response
|
||||
{
|
||||
public ChildrenResponse(RemoteElement[] result)
|
||||
=> Result = result;
|
||||
|
||||
public RemoteElement[] Result { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class DescendantsResponse : Response
|
||||
{
|
||||
public DescendantsResponse(RemoteElement[] result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public RemoteElement[] Result { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using Microsoft.Maui.Automation.Remote;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class ElementResponse : Response
|
||||
{
|
||||
public ElementResponse(RemoteElement? result)
|
||||
{
|
||||
Element = result;
|
||||
}
|
||||
|
||||
public RemoteElement? Element { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
namespace Streamer
|
||||
{
|
||||
public class GetPropertyResponse : Response
|
||||
{
|
||||
public GetPropertyResponse(object? result)
|
||||
=> Result = result;
|
||||
|
||||
public object? Result { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class PerformResponse : Response
|
||||
{
|
||||
public PerformResponse(IActionResult result)
|
||||
=> Result = result;
|
||||
|
||||
public IActionResult Result { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class Channel
|
||||
{
|
||||
public static ClientChannel CreateClient(Stream stream)
|
||||
=> new ClientChannel(stream);
|
||||
|
||||
public static ServerChannel CreateServer()
|
||||
=> new ServerChannel();
|
||||
|
||||
public static JsonSerializerOptions GetJsonSerializerOptions()
|
||||
=> new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Converters = { new PolyJsonConverter(), new ElementConverter() }
|
||||
};
|
||||
|
||||
static Newtonsoft.Json.JsonSerializerSettings GetJsonSerializerSettings()
|
||||
=> new Newtonsoft.Json.JsonSerializerSettings
|
||||
{
|
||||
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All,
|
||||
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore,
|
||||
ContractResolver = new JsonProtectedResolver()
|
||||
};
|
||||
|
||||
public static Response ReadResponse(Stream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read length
|
||||
var msgLenBuffer = new byte[4];
|
||||
if (stream.Read(msgLenBuffer, 0, 4) != 4)
|
||||
throw new Exception();
|
||||
var messageLength = BitConverter.ToInt32(msgLenBuffer);
|
||||
|
||||
// Read contents
|
||||
var messageBuffer = new byte[messageLength];
|
||||
if (stream.Read(messageBuffer, 0, messageLength) != messageLength)
|
||||
throw new Exception();
|
||||
|
||||
var json = System.Text.Encoding.UTF8.GetString(messageBuffer);
|
||||
|
||||
var resp = Newtonsoft.Json.JsonConvert.DeserializeObject<Response>(json, GetJsonSerializerSettings());
|
||||
return resp;
|
||||
//return JsonSerializer.Deserialize<Response>(json, GetJsonSerializerOptions());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static Request ReadRequest(Stream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
// Read length
|
||||
var msgLenBuffer = new byte[4];
|
||||
if (stream.Read(msgLenBuffer, 0, 4) != 4)
|
||||
throw new Exception();
|
||||
var messageLength = BitConverter.ToInt32(msgLenBuffer);
|
||||
|
||||
// Read contents
|
||||
var messageBuffer = new byte[messageLength];
|
||||
if (stream.Read(messageBuffer, 0, messageLength) != messageLength)
|
||||
throw new Exception();
|
||||
|
||||
var json = System.Text.Encoding.UTF8.GetString(messageBuffer);
|
||||
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<Request>(json, GetJsonSerializerSettings());
|
||||
//return JsonSerializer.Deserialize<Request>(json, GetJsonSerializerOptions());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteRequest(Stream stream, Request message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(message, GetJsonSerializerSettings());
|
||||
|
||||
//var json = JsonSerializer.Serialize<Request>(message, GetJsonSerializerOptions());
|
||||
var data = System.Text.Encoding.UTF8.GetBytes(json);
|
||||
var msgLen = BitConverter.GetBytes(data.Length);
|
||||
stream.Write(msgLen, 0, msgLen.Length);
|
||||
stream.Write(data, 0, data.Length);
|
||||
stream.Flush();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteResponse(Stream stream, Response message)
|
||||
{
|
||||
try
|
||||
{
|
||||
//var json = JsonSerializer.Serialize<Response>(message, GetJsonSerializerOptions());
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(message, GetJsonSerializerSettings());
|
||||
var data = System.Text.Encoding.UTF8.GetBytes(json);
|
||||
var msgLen = BitConverter.GetBytes(data.Length);
|
||||
stream.Write(msgLen, 0, msgLen.Length);
|
||||
stream.Write(data, 0, data.Length);
|
||||
stream.Flush();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class ClientChannel : IDisposable
|
||||
{
|
||||
private int _id;
|
||||
private readonly Dictionary<long, Action<Response?>> _invocations = new ();
|
||||
private readonly Stream _stream;
|
||||
|
||||
public ClientChannel(Stream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
|
||||
new Thread(() => ReadLoop()).Start();
|
||||
}
|
||||
|
||||
public Task<TResponse?> InvokeAsync<TRequest, TResponse>(TRequest request)
|
||||
where TRequest : Request
|
||||
where TResponse : Response
|
||||
{
|
||||
int id = Interlocked.Increment(ref _id);
|
||||
request.Id = id;
|
||||
|
||||
var tcs = new TaskCompletionSource<TResponse?>();
|
||||
|
||||
lock (_invocations)
|
||||
{
|
||||
_invocations[id] = response =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// If there's no response then cancel the call
|
||||
if (response == null)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
else if (response.Error != null)
|
||||
{
|
||||
tcs.TrySetException(new InvalidOperationException(response.Error));
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.TrySetResult(response as TResponse);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Channel.WriteRequest(_stream, request);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void ReadLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var response = Channel.ReadResponse(_stream);
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
lock (_invocations)
|
||||
{
|
||||
if (_invocations.TryGetValue(response.Id, out var invocation))
|
||||
{
|
||||
invocation?.Invoke(response);
|
||||
|
||||
_invocations.Remove(response.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Any pending callbacks need to be cleaned up
|
||||
lock (_invocations)
|
||||
{
|
||||
foreach (var invocation in _invocations)
|
||||
{
|
||||
invocation.Value(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
namespace Streamer
|
||||
{
|
||||
public abstract class MethodHandler
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public abstract Task<Response> HandleAsync(Request request);
|
||||
}
|
||||
|
||||
|
||||
public class MethodHandler<TResponse, TRequest> : MethodHandler
|
||||
where TResponse : Response
|
||||
where TRequest : Request
|
||||
{
|
||||
public MethodHandler(string name, Func<TRequest, Task<TResponse>> handler)
|
||||
{
|
||||
Name = name;
|
||||
Handler = handler;
|
||||
}
|
||||
|
||||
public override string Name { get; }
|
||||
|
||||
protected readonly Func<TRequest, Task<TResponse>> Handler;
|
||||
|
||||
public override async Task<Response> HandleAsync(Request request)
|
||||
{
|
||||
if (request is TRequest typedRequest)
|
||||
{
|
||||
return await Handler(typedRequest);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Invalid request type, expected: {typeof(TRequest).FullName}.", nameof(request));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(PolyJsonConverter))]
|
||||
[JsonKnownType(typeof(ChildrenRequest), nameof(ChildrenRequest))]
|
||||
[JsonKnownType(typeof(DescendantsRequest), nameof(DescendantsRequest))]
|
||||
[JsonKnownType(typeof(ElementRequest), nameof(ElementRequest))]
|
||||
[JsonKnownType(typeof(GetPropertyRequest), nameof(GetPropertyRequest))]
|
||||
[JsonKnownType(typeof(PerformRequest), nameof(PerformRequest))]
|
||||
public abstract class Request
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string? Method { get; set; }
|
||||
|
||||
public object?[] Args { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
using Microsoft.Maui.Automation;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(PolyJsonConverter))]
|
||||
[JsonKnownType(typeof(ChildrenResponse), nameof(ChildrenResponse))]
|
||||
[JsonKnownType(typeof(DescendantsResponse), nameof(DescendantsResponse))]
|
||||
[JsonKnownType(typeof(ElementResponse), nameof(ElementResponse))]
|
||||
[JsonKnownType(typeof(GetPropertyResponse), nameof(GetPropertyResponse))]
|
||||
[JsonKnownType(typeof(PerformResponse), nameof(PerformResponse))]
|
||||
public class Response
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string? Error { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Streamer
|
||||
{
|
||||
public class ServerChannel
|
||||
{
|
||||
private readonly Dictionary<string, Func<Request, Task<Response>>> _callbacks = new Dictionary<string, Func<Request, Task<Response>>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private bool _isBound;
|
||||
|
||||
public ServerChannel()
|
||||
{
|
||||
}
|
||||
|
||||
public IDisposable Bind(params MethodHandler[] methods)
|
||||
{
|
||||
if (_isBound)
|
||||
{
|
||||
throw new NotSupportedException("Can't bind to different objects");
|
||||
}
|
||||
|
||||
_isBound = true;
|
||||
|
||||
foreach (var m in methods)
|
||||
{
|
||||
var methodName = m.Name;
|
||||
|
||||
|
||||
if (_callbacks.ContainsKey(methodName))
|
||||
{
|
||||
throw new NotSupportedException(String.Format("Duplicate definitions of {0}. Overloading is not supported.", m.Name));
|
||||
}
|
||||
|
||||
_callbacks[methodName] = async request =>
|
||||
{
|
||||
Response response = new();
|
||||
response.Id = request.Id;
|
||||
|
||||
try
|
||||
{
|
||||
response = await m.HandleAsync(request);
|
||||
response.Id = request.Id;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
response.Error = ex?.InnerException?.Message ?? ex?.Message;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Error = ex?.Message;
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
}
|
||||
|
||||
return new DisposableAction(() =>
|
||||
{
|
||||
foreach (var m in methods)
|
||||
{
|
||||
lock (_callbacks)
|
||||
{
|
||||
_callbacks.Remove(m.Name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task StartAsync(Stream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var request = Channel.ReadRequest(stream);
|
||||
|
||||
if (request != null)
|
||||
{
|
||||
Response? response = null;
|
||||
|
||||
if (request.Method != null && _callbacks.TryGetValue(request.Method, out var callback))
|
||||
{
|
||||
response = await callback(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's no method then return a failed response for this request
|
||||
response = new Response
|
||||
{
|
||||
Id = request.Id,
|
||||
Error = string.Format("Unknown method '{0}'", request.Method)
|
||||
};
|
||||
}
|
||||
|
||||
Channel.WriteResponse(stream, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private class DisposableAction : IDisposable
|
||||
{
|
||||
private Action _action;
|
||||
|
||||
public DisposableAction(Action action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Interlocked.Exchange(ref _action, () => { }).Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Microsoft.Maui.Automation.Remote
|
||||
{
|
||||
public class TcpRemoteApplication : IApplication
|
||||
{
|
||||
public const int DefaultPort = 4327;
|
||||
|
||||
public TcpRemoteApplication(Platform defaultPlatform, IPAddress address, int port = DefaultPort, bool listen = true, IRemoteAutomationService? remoteAutomationService = null)
|
||||
{
|
||||
DefaultPlatform = defaultPlatform;
|
||||
if (listen)
|
||||
{
|
||||
tcpListener = new TcpListener(address, port);
|
||||
tcpListener.Start();
|
||||
client = tcpListener.AcceptTcpClient();
|
||||
stream = client.GetStream();
|
||||
remoteApplication = new RemoteApplication(DefaultPlatform, stream, remoteAutomationService);
|
||||
tcpListener.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
tcpListener = null;
|
||||
client = new TcpClient();
|
||||
client.Connect(address, port);
|
||||
stream = client.GetStream();
|
||||
remoteApplication = new RemoteApplication(DefaultPlatform, stream, remoteAutomationService);
|
||||
}
|
||||
}
|
||||
|
||||
readonly Stream stream;
|
||||
readonly TcpListener? tcpListener;
|
||||
readonly RemoteApplication remoteApplication;
|
||||
readonly TcpClient client;
|
||||
|
||||
public Platform DefaultPlatform { get; }
|
||||
|
||||
public Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
=> remoteApplication.Children(platform);
|
||||
|
||||
public Task<IElement?> Element(Platform platform, string elementId)
|
||||
=> remoteApplication.Element(platform, elementId);
|
||||
|
||||
public Task<IEnumerable<IElement>> Descendants(Platform platform, string? elementId = null, IElementSelector? selector = null)
|
||||
=> remoteApplication.Descendants(platform, elementId, selector);
|
||||
|
||||
public Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
=> remoteApplication.Perform(platform, elementId, action);
|
||||
|
||||
public Task<object?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
=> remoteApplication.GetProperty(platform, elementId, propertyName);
|
||||
}
|
||||
}
|
|
@ -1,19 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Spectre.Console" Version="0.44.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Core" Version="2.46.3" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.48.0" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.44.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.21.5" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.48.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Remote\Microsoft.Maui.Automation.Remote.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,72 +1,92 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Microsoft.Maui.Automation.Remote;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.Maui.Automation;
|
||||
using Spectre.Console;
|
||||
using System.Net;
|
||||
|
||||
var port = TcpRemoteApplication.DefaultPort;
|
||||
var address = "http://localhost:10882";
|
||||
|
||||
Console.WriteLine($"REPL> Connecting to {address}...");
|
||||
var platform = Platform.Maui;
|
||||
|
||||
|
||||
|
||||
var grpc = GrpcChannel.ForAddress(address);
|
||||
|
||||
var client = new Microsoft.Maui.Automation.RemoteGrpc.RemoteApp.RemoteAppClient(grpc);
|
||||
|
||||
Console.WriteLine($"REPL> Waiting for remote automation connection on port {port}...");
|
||||
var platform = Platform.MAUI;
|
||||
var remote = new TcpRemoteApplication(platform, IPAddress.Any, port);
|
||||
|
||||
Console.WriteLine("Connected.");
|
||||
|
||||
|
||||
while(true)
|
||||
while (true)
|
||||
{
|
||||
var input = Console.ReadLine() ?? string.Empty;
|
||||
var input = Console.ReadLine() ?? string.Empty;
|
||||
|
||||
try {
|
||||
if (input.StartsWith("tree"))
|
||||
{
|
||||
var children = await remote.Children(platform);
|
||||
try
|
||||
{
|
||||
if (input.StartsWith("tree"))
|
||||
{
|
||||
var children = await client.GetElementsAsync(new Microsoft.Maui.Automation.RemoteGrpc.ElementsRequest());
|
||||
|
||||
foreach (var w in children)
|
||||
{
|
||||
var tree = new Tree(w.ToTable(ConfigureTable));
|
||||
foreach (var w in children.Elements)
|
||||
{
|
||||
var tree = new Tree(w.ToTable(ConfigureTable));
|
||||
|
||||
var descendants = await remote.Descendants(platform, w.Id);
|
||||
foreach (var d in descendants)
|
||||
{
|
||||
//var node = tree.AddNode(d.ToMarkupString(0, 0));
|
||||
|
||||
PrintTree(tree, d, 1);
|
||||
}
|
||||
foreach (var d in w.Children)
|
||||
{
|
||||
PrintTree(tree, d, 1);
|
||||
}
|
||||
|
||||
AnsiConsole.Write(tree);
|
||||
}
|
||||
AnsiConsole.Write(tree);
|
||||
}
|
||||
|
||||
}
|
||||
else if (input.StartsWith("windows"))
|
||||
{
|
||||
foreach (var w in await remote.Children(platform))
|
||||
{
|
||||
var tree = new Tree(w.ToTable(ConfigureTable));
|
||||
}
|
||||
else if (input.StartsWith("windows"))
|
||||
{
|
||||
var children = await client.GetElementsAsync(new Microsoft.Maui.Automation.RemoteGrpc.ElementsRequest());
|
||||
|
||||
AnsiConsole.Write(tree);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
foreach (var w in children.Elements)
|
||||
{
|
||||
var tree = new Tree(w.ToTable(ConfigureTable));
|
||||
|
||||
if (input != null && (input.Equals("quit", StringComparison.OrdinalIgnoreCase)
|
||||
|| input.Equals("q", StringComparison.OrdinalIgnoreCase)
|
||||
|| input.Equals("exit", StringComparison.OrdinalIgnoreCase)))
|
||||
break;
|
||||
AnsiConsole.Write(tree);
|
||||
}
|
||||
}
|
||||
else if (input.StartsWith("test"))
|
||||
{
|
||||
var children = await client.GetElementsAsync(new Microsoft.Maui.Automation.RemoteGrpc.ElementsRequest());
|
||||
|
||||
foreach (var w in children.Elements)
|
||||
{
|
||||
|
||||
var tree = new Tree(w.ToTable(ConfigureTable));
|
||||
|
||||
AnsiConsole.Write(tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
|
||||
if (input != null && (input.Equals("quit", StringComparison.OrdinalIgnoreCase)
|
||||
|| input.Equals("q", StringComparison.OrdinalIgnoreCase)
|
||||
|| input.Equals("exit", StringComparison.OrdinalIgnoreCase)))
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
void PrintTree(IHasTreeNodes node, IElement element, int depth)
|
||||
void PrintTree(IHasTreeNodes node, Element element, int depth)
|
||||
{
|
||||
var subnode = node.AddNode(element.ToTable(ConfigureTable));
|
||||
var subnode = node.AddNode(element.ToTable(ConfigureTable));
|
||||
|
||||
foreach (var c in element.Children)
|
||||
PrintTree(subnode, c, depth);
|
||||
foreach (var c in element.Children)
|
||||
PrintTree(subnode, c, depth);
|
||||
}
|
||||
|
||||
static void ConfigureTable(Table table)
|
||||
{
|
||||
table.Border(TableBorder.Rounded);
|
||||
table.Border(TableBorder.Rounded);
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace Microsoft.Maui.Automation
|
|||
{
|
||||
public static class ViewExtensions
|
||||
{
|
||||
public static Table ToTable(this IElement element, Action<Table>? config = null)
|
||||
public static Table ToTable(this Element element, Action<Table>? config = null)
|
||||
{
|
||||
var table = new Table()
|
||||
.AddColumn("[bold]" + element.Type + "[/]")
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Maui.Automation.Remote\Microsoft.Maui.Automation.Remote.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,19 +3,21 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31724.407
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.Automation.Test", "Microsoft.Maui.Automation.Test\Microsoft.Maui.Automation.Test.csproj", "{6F1E7A5E-B716-4D12-AF3C-FEF4BE0834E1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.Automation.Core", "Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj", "{BBBCB071-A745-4746-ADB7-59B57983718A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.Automation.AppHost", "Microsoft.Maui.Automation.AppHost\Microsoft.Maui.Automation.AppHost.csproj", "{6D8AD0FF-33A3-46B3-BF24-C87332B6C281}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Maui.Automation.Remote", "Microsoft.Maui.Automation.Remote\Microsoft.Maui.Automation.Remote.csproj", "{426EE334-FFB2-4185-A222-58CB629E5654}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteAutomationTests", "tests\RemoteAutomationTests\RemoteAutomationTests.csproj", "{878A4A30-E88E-4C91-9C41-EDF7AB4D1320}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteAutomationTests", "tests\RemoteAutomationTests\RemoteAutomationTests.csproj", "{878A4A30-E88E-4C91-9C41-EDF7AB4D1320}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleMauiApp", "samples\SampleMauiApp\SampleMauiApp.csproj", "{AFB868DD-DAA2-4002-A9FE-A2781E915A95}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleMauiApp", "samples\SampleMauiApp\SampleMauiApp.csproj", "{AFB868DD-DAA2-4002-A9FE-A2781E915A95}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Maui.Automation.Repl", "Microsoft.Maui.Automation.Repl\Microsoft.Maui.Automation.Repl.csproj", "{3B96F274-BD40-44B4-8CC3-C487D278F95F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Maui.Automation.Repl", "Microsoft.Maui.Automation.Repl\Microsoft.Maui.Automation.Repl.csproj", "{3B96F274-BD40-44B4-8CC3-C487D278F95F}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Proto", "Proto", "{99757F39-4185-4511-9D9C-527D2D35B37C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
proto\remoteapp.proto = proto\remoteapp.proto
|
||||
proto\types.proto = proto\types.proto
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -23,10 +25,6 @@ Global
|
|||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6F1E7A5E-B716-4D12-AF3C-FEF4BE0834E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6F1E7A5E-B716-4D12-AF3C-FEF4BE0834E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6F1E7A5E-B716-4D12-AF3C-FEF4BE0834E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6F1E7A5E-B716-4D12-AF3C-FEF4BE0834E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BBBCB071-A745-4746-ADB7-59B57983718A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BBBCB071-A745-4746-ADB7-59B57983718A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BBBCB071-A745-4746-ADB7-59B57983718A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
@ -35,10 +33,6 @@ Global
|
|||
{6D8AD0FF-33A3-46B3-BF24-C87332B6C281}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D8AD0FF-33A3-46B3-BF24-C87332B6C281}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6D8AD0FF-33A3-46B3-BF24-C87332B6C281}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{426EE334-FFB2-4185-A222-58CB629E5654}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{426EE334-FFB2-4185-A222-58CB629E5654}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{426EE334-FFB2-4185-A222-58CB629E5654}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{426EE334-FFB2-4185-A222-58CB629E5654}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{878A4A30-E88E-4C91-9C41-EDF7AB4D1320}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{878A4A30-E88E-4C91-9C41-EDF7AB4D1320}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{878A4A30-E88E-4C91-9C41-EDF7AB4D1320}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
syntax = "proto3";
|
||||
import public "types.proto";
|
||||
|
||||
option csharp_namespace = "Microsoft.Maui.Automation.RemoteGrpc";
|
||||
|
||||
message ElementsRequest {
|
||||
Platform platform = 1;
|
||||
string elementId = 2;
|
||||
optional int32 childDepth = 3;
|
||||
}
|
||||
|
||||
message ElementsResponse {
|
||||
repeated Element elements = 1;
|
||||
}
|
||||
|
||||
message PropertyRequest {
|
||||
Platform platform = 1;
|
||||
string elementId = 2;
|
||||
string propertyName = 3;
|
||||
}
|
||||
|
||||
message PropertyResponse {
|
||||
Platform platform = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
service RemoteApp {
|
||||
rpc GetElements (ElementsRequest) returns (ElementsResponse);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "Microsoft.Maui.Automation";
|
||||
|
||||
message Element {
|
||||
string id = 1;
|
||||
optional string parentId = 2;
|
||||
optional string automationId = 3;
|
||||
|
||||
Platform platform = 4;
|
||||
string type = 5;
|
||||
string fullType = 6;
|
||||
|
||||
optional string text = 7;
|
||||
|
||||
bool visible = 8;
|
||||
bool enabled = 9;
|
||||
bool focused = 10;
|
||||
|
||||
int32 x = 11;
|
||||
int32 y = 12;
|
||||
int32 width = 13;
|
||||
int32 height = 14;
|
||||
|
||||
optional Element parent = 15;
|
||||
repeated Element children = 16;
|
||||
}
|
||||
|
||||
enum Platform {
|
||||
MAUI = 0;
|
||||
IOS = 100;
|
||||
MACCATALYST = 200;
|
||||
MACOS = 210;
|
||||
TVOS = 300;
|
||||
ANDROID = 400;
|
||||
WINAPPSDK = 500;
|
||||
}
|
|
@ -11,9 +11,7 @@ namespace SampleMauiApp
|
|||
|
||||
MainPage = new MainPage();
|
||||
|
||||
|
||||
|
||||
this.StartAutomationServiceConnection("127.0.0.1");
|
||||
this.StartAutomationServiceListener();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,7 +42,6 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.AppHost\Microsoft.Maui.Automation.AppHost.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.Remote\Microsoft.Maui.Automation.Remote.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,36 +3,42 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace RemoteAutomationTests
|
||||
{
|
||||
public class MockApplication : Application
|
||||
{
|
||||
public MockApplication(Platform defaultPlatform = Platform.MAUI) : base()
|
||||
public MockApplication(Platform defaultPlatform = Platform.Maui) : base()
|
||||
{
|
||||
DefaultPlatform = defaultPlatform;
|
||||
}
|
||||
|
||||
public readonly List<MockWindow> MockWindows = new ();
|
||||
public readonly List<Element> MockWindows = new ();
|
||||
|
||||
public MockWindow? CurrentMockWindow { get; set; }
|
||||
public Element? CurrentMockWindow { get; set; }
|
||||
|
||||
public override Platform DefaultPlatform { get; }
|
||||
|
||||
public override Task<IEnumerable<IElement>> Children(Platform platform)
|
||||
=> Task.FromResult<IEnumerable<IElement>>(MockWindows);
|
||||
|
||||
public Func<Platform, string, IAction, Task<IActionResult>>? PerformHandler { get; set; }
|
||||
|
||||
public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
{
|
||||
if (PerformHandler is not null)
|
||||
{
|
||||
return PerformHandler.Invoke(platform, elementId, action);
|
||||
}
|
||||
//public override Task<IActionResult> Perform(Platform platform, string elementId, IAction action)
|
||||
//{
|
||||
// if (PerformHandler is not null)
|
||||
// {
|
||||
// return PerformHandler.Invoke(platform, elementId, action);
|
||||
// }
|
||||
|
||||
return Task.FromResult<IActionResult>(new ActionResult(ActionResultStatus.Error, "No Handler specified."));
|
||||
// return Task.FromResult<IActionResult>(new ActionResult(ActionResultStatus.Error, "No Handler specified."));
|
||||
|
||||
//}
|
||||
|
||||
public override Task<string?> GetProperty(Platform platform, string elementId, string propertyName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<IEnumerable<Element>> GetElements(Platform platform, string? elementId = null, int depth = 0)
|
||||
=> Task.FromResult<IEnumerable<Element>>(MockWindows);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,15 @@
|
|||
namespace RemoteAutomationTests
|
||||
using Microsoft.Maui.Automation;
|
||||
|
||||
namespace RemoteAutomationTests
|
||||
{
|
||||
public class MockWindow
|
||||
{
|
||||
public string Title { get; set; }
|
||||
}
|
||||
|
||||
public static class MockApplicationExtensions
|
||||
{
|
||||
public static MockApplication WithWindow(this MockApplication app, MockWindow window)
|
||||
public static MockApplication WithWindow(this MockApplication app, Element window)
|
||||
{
|
||||
app.MockWindows.Add(window);
|
||||
return app;
|
||||
|
@ -10,22 +17,24 @@
|
|||
|
||||
public static MockApplication WithWindow(this MockApplication app, string id, string? automationId, string? title)
|
||||
{
|
||||
var w = new MockWindow(app, app.DefaultPlatform, id, automationId, title);
|
||||
var w = new Element(app, app.DefaultPlatform, id, new MockWindow());
|
||||
w.Text = title;
|
||||
|
||||
app.MockWindows.Add(w);
|
||||
app.CurrentMockWindow = w;
|
||||
return app;
|
||||
}
|
||||
|
||||
public static MockApplication WithView(this MockApplication app, MockView view)
|
||||
public static MockApplication WithView(this MockApplication app, Element view)
|
||||
{
|
||||
app.CurrentMockWindow!.MockViews.Add(view);
|
||||
app.CurrentMockWindow!.Children.Add(view);
|
||||
return app;
|
||||
}
|
||||
|
||||
public static MockApplication WithView(this MockApplication app, string id)
|
||||
{
|
||||
var window = app.CurrentMockWindow!;
|
||||
window.MockViews.Add(new MockView(app, app.DefaultPlatform, window.Id, id));
|
||||
window.Children.Add(new Element(app, app.DefaultPlatform, id, new MockWindow(), window.Id));
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
namespace RemoteAutomationTests
|
||||
{
|
||||
public class MockNativeView
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class MockNativeWindow
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace RemoteAutomationTests
|
||||
{
|
||||
public class MockView : Element
|
||||
{
|
||||
public MockView(IApplication application, Platform platform, string id, string? parentId = null)
|
||||
: base(application, platform, id, parentId)
|
||||
{
|
||||
Text = string.Empty;
|
||||
|
||||
PlatformElement = new MockNativeView();
|
||||
}
|
||||
|
||||
public readonly List<MockView> MockViews = new List<MockView>();
|
||||
|
||||
|
||||
public override IReadOnlyCollection<IElement> Children
|
||||
=> new ReadOnlyCollection<IElement>(MockViews.ToList<IElement>());
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
using Microsoft.Maui.Automation;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace RemoteAutomationTests
|
||||
{
|
||||
public class MockWindow : Element
|
||||
{
|
||||
public MockWindow(IApplication application, Platform platform, string id, string? automationId, string? title = null)
|
||||
: base(application, platform, id)
|
||||
{
|
||||
AutomationId = automationId ?? Id;
|
||||
|
||||
Text = title ?? string.Empty;
|
||||
|
||||
PlatformElement = new MockNativeWindow();
|
||||
}
|
||||
|
||||
public readonly List<MockView> MockViews = new List<MockView>();
|
||||
|
||||
Platform platform;
|
||||
|
||||
public override Platform Platform => platform;
|
||||
|
||||
public override IReadOnlyCollection<IElement> Children
|
||||
=> new ReadOnlyCollection<IElement>(MockViews.ToList<IElement>());
|
||||
}
|
||||
|
||||
public static class MockWindowExtensions
|
||||
{
|
||||
public static MockWindow WithView(this MockWindow window, MockView view)
|
||||
{
|
||||
window.MockViews.Add(view);
|
||||
return window;
|
||||
}
|
||||
public static MockWindow WithView(this MockWindow window, string windowId, string id)
|
||||
{
|
||||
window.MockViews.Add(new MockView(window.Application, window.Platform, windowId, id));
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,8 +23,6 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.AppHost\Microsoft.Maui.Automation.AppHost.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.Core\Microsoft.Maui.Automation.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.Remote\Microsoft.Maui.Automation.Remote.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.Maui.Automation.Test\Microsoft.Maui.Automation.Test.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Загрузка…
Ссылка в новой задаче