diff --git a/src/Core/Query/DescendantsQueryStep.cs b/src/Core/Query/DescendantsQueryStep.cs index 77e0a71..fdffa61 100644 --- a/src/Core/Query/DescendantsQueryStep.cs +++ b/src/Core/Query/DescendantsQueryStep.cs @@ -4,7 +4,7 @@ namespace Microsoft.Maui.Automation.Querying; public class DescendantsQueryStep : PredicateQueryStep { - public DescendantsQueryStep(Predicate predicate) : base(predicate) + public DescendantsQueryStep(Predicate? predicate) : base(predicate) { } diff --git a/src/Core/Query/DriverQuery.cs b/src/Core/Query/DriverQuery.cs index e5babf2..a7295da 100644 --- a/src/Core/Query/DriverQuery.cs +++ b/src/Core/Query/DriverQuery.cs @@ -7,9 +7,12 @@ using System.Threading.Tasks; namespace Microsoft.Maui.Automation.Querying -{ +{ public class DriverQuery { + public const int DefaultAutoWaitMilliseconds = 3000; + public const int DefaultAutoWaitRetryMilliseconds = 200; + public DriverQuery(IDriver driver) { Driver = driver; @@ -31,16 +34,60 @@ public DriverQuery(IDriver driver, Platform automationPlatform) public readonly IDriver Driver; public readonly Query Query; + public async Task Element(int autoWaitMs = DefaultAutoWaitMilliseconds, int retryDelayMs = DefaultAutoWaitRetryMilliseconds) + => (await AutoWait(autoWaitMs, retryDelayMs, false).ConfigureAwait(false)).FirstOrDefault(); + + public async Task NoElements(int autoWaitMs = DefaultAutoWaitMilliseconds, int retryDelayMs = DefaultAutoWaitRetryMilliseconds) + => !(await AutoWait(autoWaitMs, retryDelayMs, true).ConfigureAwait(false)).Any(); - public async Task> Execute() - => await Query.Execute(Driver, await Driver.GetElements().ConfigureAwait(false)).ConfigureAwait(false); + public async Task> Elements(int autoWaitMs = DefaultAutoWaitMilliseconds, int retryDelayMs = DefaultAutoWaitRetryMilliseconds) + => await AutoWait(autoWaitMs, retryDelayMs, false).ConfigureAwait(false); + public TaskAwaiter> GetAwaiter() + => AutoWait().GetAwaiter(); - public TaskAwaiter> GetAwaiter() + async Task> AutoWait(int autoWaitMs = DefaultAutoWaitMilliseconds, int retryDelayMs = DefaultAutoWaitRetryMilliseconds, bool waitForNone = false) { - return Execute().GetAwaiter(); - } + var waited = 0; + + while (waited < autoWaitMs || autoWaitMs <= 0) + { + // See which automation platform to use + var platform = Query.AutomationPlatform ?? Driver.Configuration.AutomationPlatform; + + var elements = await Driver.GetElements(platform).ConfigureAwait(false); + + var results = await Query.Execute(Driver, elements); + + var anyResults = results.Any(); + + if (waitForNone) + { + // Wait until no results found + if (autoWaitMs <= 0 || !anyResults) + { + if (anyResults) + throw new ElementsStillFoundException(Query); + + return results; + } + } + else + { + // Wait until we find 1 or more + if (autoWaitMs <= 0 || anyResults) + return results; + } + + Thread.Sleep(retryDelayMs); + waited += retryDelayMs; + } + if (waitForNone) + throw new ElementsStillFoundException(Query); + else + throw new ElementsNotFoundException(Query); + } - } + } } diff --git a/src/Core/Query/FirstQueryStep.cs b/src/Core/Query/FirstQueryStep.cs new file mode 100644 index 0000000..8284436 --- /dev/null +++ b/src/Core/Query/FirstQueryStep.cs @@ -0,0 +1,23 @@ +using Microsoft.Maui.Automation.Driver; + +namespace Microsoft.Maui.Automation.Querying; + +class FirstQueryStep : PredicateQueryStep +{ + public FirstQueryStep() : base() + { } + + public FirstQueryStep(Predicate? predicate = null) : base(predicate) + { } + + public override Task> Execute(IDriver driver, IEnumerable tree, IEnumerable currentSet) + { + var first = currentSet.FirstOrDefault(e => Predicate(e)); + + if (first is not null) + return Task.FromResult>(new[] { first }); + else + return Task.FromResult(Enumerable.Empty()); + } +} + diff --git a/src/Core/Query/QueryExtensions.cs b/src/Core/Query/QueryExtensions.cs index 38ba139..77b0138 100644 --- a/src/Core/Query/QueryExtensions.cs +++ b/src/Core/Query/QueryExtensions.cs @@ -49,7 +49,7 @@ public static Query ClearText(this Query query, string text) public static class DriverQueryExtensions { - static DriverQuery Append(this DriverQuery query, Predicate predicate) + static DriverQuery AppendChildren(this DriverQuery query, Predicate predicate) { query.Query.Append(predicate); return query; @@ -59,35 +59,67 @@ static DriverQuery Append(this DriverQuery query, IQueryStep step) { query.Query.Append(step); return query; - } - - public static DriverQuery By(this DriverQuery query, Predicate predicate) - => query.Append(predicate); - - public static DriverQuery ByAutomationId(this DriverQuery query, string automationId) - => query.Append(e => e.AutomationId == automationId); - - public static DriverQuery ById(this DriverQuery query, string id) - => query.Append(e => e.Id == id); - - public static DriverQuery OfType(this DriverQuery query, string typeName) - => query.Append(e => e.Type == typeName); - - public static DriverQuery OfFullType(this DriverQuery query, string fullTypeName) - => query.Append(e => e.FullType == fullTypeName); - - public static DriverQuery ContainingText(this DriverQuery query, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) - => query.Append(e => e.Text.Contains(text, comparisonType)); - - public static DriverQuery Marked(this DriverQuery query, string marked, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) - => query.Append(e => e.Id.Equals(marked, comparisonType) + } + + + public static DriverQuery By(this DriverQuery query, Predicate? predicate = null) + => query.Append(new DescendantsQueryStep(predicate)); + + + public static DriverQuery AutomationId(this DriverQuery query, string automationId) + => query.By(e => e.AutomationId == automationId); + + public static DriverQuery Id(this DriverQuery query, string id) + => query.By(e => e.Id == id); + + public static DriverQuery Type(this DriverQuery query, string typeName) + => query.By(e => e.Type == typeName); + + public static DriverQuery FullType(this DriverQuery query, string fullTypeName) + => query.By(e => e.FullType == fullTypeName); + + public static DriverQuery Text(this DriverQuery query, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => query.By(e => e.Text.Equals(text, comparisonType)); + + public static DriverQuery ContainsText(this DriverQuery query, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => query.By(e => e.Text.Contains(text, comparisonType)); + + public static DriverQuery Marked(this DriverQuery query, string marked, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => query.By(e => e.Id.Equals(marked, comparisonType) + || e.AutomationId.Equals(marked, comparisonType) + || e.Text.Equals(marked, comparisonType)); + + public static DriverQuery First(this DriverQuery query) + => query.Append(new FirstQueryStep()); + + + public static DriverQuery Children(this DriverQuery query, Predicate predicate) + => query.AppendChildren(predicate); + + public static DriverQuery ChildrenByAutomationId(this DriverQuery query, string automationId) + => query.AppendChildren(e => e.AutomationId == automationId); + + public static DriverQuery ChildrenById(this DriverQuery query, string id) + => query.AppendChildren(e => e.Id == id); + + public static DriverQuery ChildrenOfType(this DriverQuery query, string typeName) + => query.AppendChildren(e => e.Type == typeName); + + public static DriverQuery ChildrenOfFullType(this DriverQuery query, string fullTypeName) + => query.AppendChildren(e => e.FullType == fullTypeName); + + public static DriverQuery ChildrenContainingText(this DriverQuery query, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => query.AppendChildren(e => e.Text.Contains(text, comparisonType)); + + public static DriverQuery ChildrenMarked(this DriverQuery query, string marked, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => query.AppendChildren(e => e.Id.Equals(marked, comparisonType) || e.AutomationId.Equals(marked, comparisonType) || e.Text.Equals(marked, comparisonType)); public static DriverQuery Siblings(this DriverQuery query, Predicate? predicate = null) => query.Append(new SiblingsQueryStep(predicate)); - public static DriverQuery AtIndex(this DriverQuery query, int index) + public static DriverQuery Index(this DriverQuery query, int index) => query.Append(new IndexQueryStep(index)); public static DriverQuery Tap(this DriverQuery query) diff --git a/src/Core/Query/SiblingsQueryStep.cs b/src/Core/Query/SiblingsQueryStep.cs index e9ef2cd..c9b4bfc 100644 --- a/src/Core/Query/SiblingsQueryStep.cs +++ b/src/Core/Query/SiblingsQueryStep.cs @@ -28,4 +28,3 @@ public override Task> Execute(IDriver driver, IEnumerable< return Task.FromResult>(newSet); } } - diff --git a/src/Driver/Extensions/DriverExtensions.cs b/src/Driver/Extensions/DriverExtensions.cs index f088ab0..24e6c94 100644 --- a/src/Driver/Extensions/DriverExtensions.cs +++ b/src/Driver/Extensions/DriverExtensions.cs @@ -12,83 +12,59 @@ namespace Microsoft.Maui.Automation.Driver; public static partial class DriverExtensions { - public static DriverQuery On(this IDriver driver, Platform automationPlatform) => new DriverQuery(driver, automationPlatform); public static DriverQuery Query(this IDriver driver) => new DriverQuery(driver); - + public static DriverQuery Query(this IDriver driver, Platform automationPlatform) + => new DriverQuery(driver, automationPlatform); + public static DriverQuery ChildrenBy(this IDriver driver, Predicate predicate) + => new DriverQuery(driver).Children(predicate); + public static DriverQuery ChildrenByAutomationId(this IDriver driver, string automationId) + => new DriverQuery(driver).ChildrenByAutomationId(automationId); - public static Task> All(this IDriver driver, Query query) - => driver.AutoWait(query); - - public static Task> Any(this IDriver driver, Query query) - => driver.AutoWait(query); - - public static Task First(this IDriver driver, Query query) - => driver.AutoWaitFirst(query); - - - - - static async Task AutoWaitFirst(this IDriver driver, Query query, int autoWaitMs = 3000, int retryDelayMs = 200) - => (await driver.AutoWait(query, autoWaitMs, retryDelayMs).ConfigureAwait(false)).FirstOrDefault(); - - - //static async Task> AutoWait(this DriverQuery query, int autoWaitMs = 3000, int retryDelayMs = 200, bool waitForNone = false) - //{ - // var elements = await AutoWait(query.Driver, query.Query, autoWaitMs, retryDelayMs).ConfigureAwait(false); - - - // return elements; - //} - - static async Task> AutoWait(this IDriver driver, Query query, int autoWaitMs = 3000, int retryDelayMs = 200, bool waitForNone = false) - { - var waited = 0; - - while (waited < autoWaitMs || autoWaitMs <= 0) - { - // See which automation platform to use - var platform = query.AutomationPlatform ?? driver.Configuration.AutomationPlatform; - - var elements = await driver.GetElements(platform).ConfigureAwait(false); - - var results = await query.Execute(driver, elements); - - var anyResults = results.Any(); - - if (waitForNone) - { - // Wait until no results found - if (autoWaitMs <= 0 || !anyResults) - { - if (anyResults) - throw new ElementsStillFoundException(query); - - return results; - } - } - else - { - // Wait until we find 1 or more - if (autoWaitMs <= 0 || anyResults) - return results; - } - - Thread.Sleep(retryDelayMs); - waited += retryDelayMs; - } - - if (waitForNone) - throw new ElementsStillFoundException(query); - else - throw new ElementsNotFoundException(query); - } + public static DriverQuery ChildrenById(this IDriver driver, string id) + => new DriverQuery(driver).ChildrenById(id); + + public static DriverQuery ChildrenOfType(this IDriver driver, string typeName) + => new DriverQuery(driver).ChildrenOfType(typeName); + + public static DriverQuery ChildrenOfFullType(this IDriver driver, string fullTypeName) + => new DriverQuery(driver).ChildrenOfFullType(fullTypeName); + + public static DriverQuery ChildrenContainingText(this IDriver driver, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => new DriverQuery(driver).ChildrenContainingText(text, comparisonType); + + public static DriverQuery ChildrenMarked(this IDriver driver, string marked, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => new DriverQuery(driver).ChildrenMarked(marked, comparisonType); + + + public static DriverQuery By(this IDriver driver, Predicate? predicate = null) + => new DriverQuery(driver).By(predicate); + + public static DriverQuery AutomationId(this IDriver driver, string automationId) + => new DriverQuery(driver).AutomationId(automationId); + + public static DriverQuery Id(this IDriver driver, string id) + => new DriverQuery(driver).Id(id); + + public static DriverQuery Type(this IDriver driver, string typeName) + => new DriverQuery(driver).Type(typeName); + + public static DriverQuery FullType(this IDriver driver, string fullTypeName) + => new DriverQuery(driver).FullType(fullTypeName); + + public static DriverQuery Text(this IDriver driver, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => new DriverQuery(driver).Text(text, comparisonType); + + public static DriverQuery ContainsText(this IDriver driver, string text, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => new DriverQuery(driver).ContainsText(text, comparisonType); + + public static DriverQuery Marked(this IDriver driver, string marked, StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + => new DriverQuery(driver).Marked(marked, comparisonType); } - diff --git a/src/Interactive/AutomationExtension.cs b/src/Interactive/AutomationExtension.cs index e894c5d..4e4c0c6 100644 --- a/src/Interactive/AutomationExtension.cs +++ b/src/Interactive/AutomationExtension.cs @@ -191,7 +191,7 @@ Task DisposeAppDriver(IDriver driver) { try { - driver.Dispose(); + driver.DisposeAsync(); } catch { } diff --git a/src/Repl/Program.cs b/src/Repl/Program.cs index 0030317..f642282 100644 --- a/src/Repl/Program.cs +++ b/src/Repl/Program.cs @@ -43,36 +43,41 @@ { "windows", Windows }, { "test", async (string[] args) => { + // Fill in username/password await driver - .Query() - .ByAutomationId("entryUsername") - .InputText("xamarin"); - + .AutomationId("entryUsername") + .First() + .InputText("redth"); + await driver + .AutomationId("entryPassword") + .First() + .InputText("1234"); + + // Click Login + await driver + .Type("Button") + .Text("Login") + .First() + .Tap(); + + // Wait for next page await driver - .Query() - .ByAutomationId("entryPassword") - .InputText("1234"); + .ContainsText("Hello, World!") + .First(); + // Click the counter button await driver - .Query() - .ContainingText("Login") + .AutomationId("buttonOne") + .First() .Tap(); - await driver.None(By.AutomationId("entryUsername")); - - var label = await driver.Query() - .ContainingText("Hello, World!"); - - var button = await driver.Query() - .ByAutomationId("buttonOne"); - - await button.First().Tap(); - - await driver.Screenshot(); - - await driver.Any(By.Type("Label").ThenContainingText("Current count: 1")); + // Find the label we expect to be incremented + var label = await driver + .Type("Label") + .ContainsText("Current count:") + .Element(); - Console.WriteLine(label.Text); + Console.WriteLine(label!.Text); } }, { "perf", async (string[] args) => @@ -83,7 +88,7 @@ await driver for (int i = 0; i < 1000; i++) { var indstart = DateTime.UtcNow; - var f = await driver.First(e => e.Type == "Label" && e.Text.Contains("count")); + var f = await driver.Query().By(e => e.Type == "Label" && e.Text.Contains("count")).Element(); var t = f.Text; var indtotal = DateTime.UtcNow - indstart; ind.Add(indtotal.TotalMilliseconds); @@ -180,7 +185,7 @@ async Task Windows(params string[] args) } } -void PrintTree(IHasTreeNodes node, Element element, int depth) +void PrintTree(IHasTreeNodes node, IElement element, int depth) { var subnode = node.AddNode(element.ToTable(ConfigureTable)); diff --git a/src/Test/Tests.cs b/src/Test/Tests.cs index ed3068d..e71d729 100644 --- a/src/Test/Tests.cs +++ b/src/Test/Tests.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Core.Query; using Microsoft.Maui.Automation.Driver; using Xunit; @@ -31,16 +30,20 @@ public async Task RunApp() await driver.Start(); // Find the button by its MAUI AutomationId property - var button = await driver.First(By.AutomationId("buttonOne")); + var button = await driver + .AutomationId("buttonOne") + .Element(); Assert.NotNull(button); // Tap the button to increment the counter await driver.Tap(button); // Find the label we expect to have changed - var label = await driver.First(e => - e.Type == "Label" - && e.Text.Contains("1")); + var label = await driver + .By(e => + e.Type == "Label" + && e.Text.Contains("1")) + .Element(); Assert.NotNull(label); } diff --git a/tests/RemoteAutomationTests/MockApplication.cs b/tests/RemoteAutomationTests/MockApplication.cs index e37c490..428dda6 100644 --- a/tests/RemoteAutomationTests/MockApplication.cs +++ b/tests/RemoteAutomationTests/MockApplication.cs @@ -42,20 +42,20 @@ public override Task PerformAction(string action, string el throw new NotImplementedException(); } - public override Task> GetElements() - => Task.FromResult>(MockWindows); + public override Task> GetElements() + => Task.FromResult>(MockWindows); - public override Task> FindElements(Predicate matcher) + public override Task> FindElements(Predicate matcher) { var windows = MockWindows; - var matches = new List(); + var matches = new List(); Traverse(windows, matches, matcher); - return Task.FromResult>(matches); + return Task.FromResult>(matches); } - void Traverse(IEnumerable elements, IList matches, Predicate matcher) + void Traverse(IEnumerable elements, IList matches, Predicate matcher) { foreach (var e in elements) { diff --git a/tests/RemoteAutomationTests/UnitTest1.cs b/tests/RemoteAutomationTests/UnitTest1.cs index e9b7644..85bfb78 100644 --- a/tests/RemoteAutomationTests/UnitTest1.cs +++ b/tests/RemoteAutomationTests/UnitTest1.cs @@ -16,7 +16,7 @@ public async Task ListWindowsTest() .WithWindow("window1", "Window", "Window Title") .WithView("view1"); - var windows = new List(); + var windows = new List(); var elems = await app.GetElements(); // Query the remote host