diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Bugzilla/Bugzilla32148.cs b/src/Controls/tests/TestCases.HostApp/Issues/Bugzilla/Bugzilla32148.cs index accc063bd5f7..0bf907eefbd1 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/Bugzilla/Bugzilla32148.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/Bugzilla/Bugzilla32148.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Linq.Expressions; +using Microsoft.Maui.Controls.Internals; namespace Maui.Controls.Sample.Issues; diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Bugzilla/Bugzilla32148.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Bugzilla/Bugzilla32148.cs index d89eae100c05..c67d22d7fb9b 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Bugzilla/Bugzilla32148.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Bugzilla/Bugzilla32148.cs @@ -12,14 +12,15 @@ public Bugzilla32148(TestDevice testDevice) : base(testDevice) public override string Issue => " Pull to refresh hides the first item on a list view"; - // [Test] - // [Category(UITestCategories.ListView)] - // [FailsOnIOS] - // public void Bugzilla32148Test() - // { - // App.WaitForElement("Contact0 LastName"); - // App.Tap("Search"); - // App.WaitForElement("Contact0 LastName"); - // App.Screenshot("For manual review, is the first cell visible?"); - // } + [Test] + [Category(UITestCategories.ListView)] + [FailsOnApple] + [FailsOnWindows("Sometimes the Teardown process fails after running the test.")] + public void Bugzilla32148Test() + { + App.WaitForNoElement("Contact0 LastName"); + App.Tap(AppiumQuery.ByXPath("//*[@text='" + "Search" + "']")); + App.WaitForNoElement("Contact0 LastName"); + App.Screenshot("For manual review, verify that the first cell is visible"); + } } diff --git a/src/Controls/tests/TestCases.Shared.Tests/UITestIgnoreAttributes.cs b/src/Controls/tests/TestCases.Shared.Tests/UITestIgnoreAttributes.cs index 1f1674874fbc..b975bc9af228 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/UITestIgnoreAttributes.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/UITestIgnoreAttributes.cs @@ -78,6 +78,28 @@ public FailsOnMac(string description) : base(description) } #endif +#if IOS || MACCATALYST + public class FailsOnApple : IgnoreAttribute + { + public FailsOnApple() : base(nameof(FailsOnApple)) + { + } + public FailsOnApple(string reason) : base(reason) + { + } + } +#else + public class FailsOnApple : CategoryAttribute + { + public FailsOnApple() : base(nameof(FailsOnApple)) + { + } + public FailsOnApple(string description) : base(description) + { + } + } +#endif + #if WINDOWS public class FailsOnWindows : IgnoreAttribute { diff --git a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs index 354607e5bd37..3437ab1f5d63 100644 --- a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs +++ b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs @@ -24,6 +24,19 @@ public static void Tap(this IApp app, string element) app.FindElement(element).Click(); } + /// + /// For desktop, this will perform a mouse click on the target element. + /// For mobile, this will tap the element. + /// This API works for all platforms whereas TapCoordinates currently doesn't work on Catalyst + /// https://github.com/dotnet/maui/issues/19754 + /// + /// Represents the main gateway to interact with an app. + /// Represents the query that identify an element by parameters such as type, text it contains or identifier. + public static void Tap(this IApp app, IQuery query) + { + app.FindElement(query).Tap(); + } + /// /// Performs a mouse click on the matched element. /// @@ -34,6 +47,16 @@ public static void Click(this IApp app, string element) app.FindElement(element).Click(); } + /// + /// Performs a mouse click on the matched element. + /// + /// Represents the main gateway to interact with an app. + /// Represents the query that identify an element by parameters such as type, text it contains or identifier. + public static void Click(this IApp app, IQuery query) + { + app.FindElement(query).Click(); + } + public static void RightClick(this IApp app, string element) { var uiElement = app.FindElement(element); @@ -82,7 +105,7 @@ public static Rectangle GetRect(this IUIElement element) } /// - /// Enters text into the currently focused element. + /// Enters text into the element identified by the query. /// /// Represents the main gateway to interact with an app. /// Target Element. @@ -94,6 +117,20 @@ public static void EnterText(this IApp app, string element, string text) app.DismissKeyboard(); } + /// + /// Enters text into the element identified by the query. + /// + /// Represents the main gateway to interact with an app. + /// Target Element. + /// Represents the query that identify an element by parameters such as type, text it contains or identifier. + public static void EnterText(this IApp app, IQuery query, string text) + { + var appElement = app.FindElement(query); + appElement.SendKeys(text); + app.DismissKeyboard(); + } + + /// /// Hides soft keyboard if present. /// @@ -148,6 +185,16 @@ public static void ClearText(this IApp app, string element) app.FindElement(element).Clear(); } + /// + /// Clears text from the currently focused element. + /// + /// Represents the main gateway to interact with an app. + /// Represents the query that identify an element by parameters such as type, text it contains or identifier. + public static void ClearText(this IApp app, IQuery query) + { + app.FindElement(query).Clear(); + } + /// /// Performs a mouse click on the matched element. /// @@ -383,6 +430,16 @@ public static IReadOnlyCollection GetAlertText(this IUIElement alertElem return (IReadOnlyCollection?)result.Value ?? Array.Empty(); } + /// + /// Wait function that will repeatly query the app until a matching element is found. + /// Throws a TimeoutException if no element is found within the time limit. + /// + /// Represents the main gateway to interact with an app. + /// Target Element. + /// The message used in the TimeoutException. + /// The TimeSpan to wait before failing. + /// The TimeSpan to wait between each query call to the app. + /// The final TimeSpan to wait after the element has been found. public static IUIElement WaitForElement(this IApp app, string marked, string timeoutMessage = "Timed out waiting for element...", TimeSpan? timeout = null, TimeSpan? retryFrequency = null, TimeSpan? postTimeout = null) { IUIElement result() => app.FindElement(marked); @@ -391,6 +448,33 @@ public static IReadOnlyCollection GetAlertText(this IUIElement alertElem return results; } + /// + /// Wait function that will repeatly query the app until a matching element is found. + /// Throws a TimeoutException if no element is found within the time limit. + /// + /// Represents the main gateway to interact with an app. + /// Represents the query that identify an element by parameters such as type, text it contains or identifier. + /// The message used in the TimeoutException. + /// The TimeSpan to wait before failing. + /// The TimeSpan to wait between each query call to the app. + /// The final TimeSpan to wait after the element has been found. + public static IUIElement WaitForElement(this IApp app, IQuery query, string timeoutMessage = "Timed out waiting for element...", TimeSpan? timeout = null, TimeSpan? retryFrequency = null, TimeSpan? postTimeout = null) + { + IUIElement result() => app.FindElement(query); + var results = WaitForAtLeastOne(result, timeoutMessage, timeout, retryFrequency); + + return results; + } + + /// + /// Wait function that will repeatly query the app until a matching element is found. + /// Throws a TimeoutException if no element is found within the time limit. + /// + /// Represents the main gateway to interact with an app. + /// Entry point for the fluent API to specify the element. + /// The message used in the TimeoutException. + /// The TimeSpan to wait before failing. + /// The TimeSpan to wait between each query call to the app. public static IUIElement WaitForElement( this IApp app, Func query, @@ -403,12 +487,47 @@ public static IUIElement WaitForElement( return results; } + /// + /// Wait function that will repeatly query the app until a matching element is no longer found. + /// Throws a TimeoutException if the element is visible at the end of the time limit. + /// + /// Represents the main gateway to interact with an app. + /// Target Element. + /// The message used in the TimeoutException. + /// The TimeSpan to wait before failing. + /// The TimeSpan to wait between each query call to the app. + /// The final TimeSpan to wait after the element has been found. public static void WaitForNoElement(this IApp app, string marked, string timeoutMessage = "Timed out waiting for no element...", TimeSpan? timeout = null, TimeSpan? retryFrequency = null, TimeSpan? postTimeout = null) { IUIElement result() => app.FindElement(marked); WaitForNone(result, timeoutMessage, timeout, retryFrequency); } + /// + /// Wait function that will repeatly query the app until a matching element is no longer found. + /// Throws a TimeoutException if the element is visible at the end of the time limit. + /// + /// Represents the main gateway to interact with an app. + /// Represents the query that identify an element by parameters such as type, text it contains or identifier. + /// The message used in the TimeoutException. + /// The TimeSpan to wait before failing. + /// The TimeSpan to wait between each query call to the app. + /// The final TimeSpan to wait after the element has been found. + public static void WaitForNoElement(this IApp app, IQuery query, string timeoutMessage = "Timed out waiting for no element...", TimeSpan? timeout = null, TimeSpan? retryFrequency = null, TimeSpan? postTimeout = null) + { + IUIElement result() => app.FindElement(query); + WaitForNone(result, timeoutMessage, timeout, retryFrequency); + } + + /// + /// Wait function that will repeatly query the app until a matching element is no longer found. + /// Throws a TimeoutException if the element is visible at the end of the time limit. + /// + /// Represents the main gateway to interact with an app. + /// Entry point for the fluent API to specify the element. + /// The message used in the TimeoutException. + /// The TimeSpan to wait before failing. + /// The TimeSpan to wait between each query call to the app. public static void WaitForNoElement( this IApp app, Func query,