From 24c6cd41272a274c4e6516ddecd98f2ded2d32f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Mon, 14 Oct 2024 19:52:01 +0200 Subject: [PATCH] [Testing] Add UITest Stepper actions (#25130) * Added Stepper UITests * Added iOS version * Updated Stepper UITests * Fix build error * More changes in the iOS impl * Added support to Windows * More changes in the Windows impl * More changes * More changes * Changes to avoid not find MauiStepper container from Appium * More changes * More fixes --- .../Elements/StepperCoreGalleryPage.cs | 37 +++++--- .../Tests/StepperUITests.cs | 82 ++++++++++++++++ src/Core/src/Platform/Windows/MauiStepper.cs | 10 +- .../Actions/AppiumAndroidStepperActions.cs | 94 +++++++++++++++++++ .../Actions/AppiumIOSStepperActions.cs | 94 +++++++++++++++++++ .../Actions/AppiumWindowsStepperActions.cs | 61 ++++++++++++ .../src/UITest.Appium/AppiumAndroidApp.cs | 1 + .../src/UITest.Appium/AppiumIOSApp.cs | 1 + .../src/UITest.Appium/AppiumWindowsApp.cs | 2 +- .../src/UITest.Appium/HelperExtensions.cs | 26 +++++ 10 files changed, 394 insertions(+), 14 deletions(-) create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/StepperUITests.cs create mode 100644 src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidStepperActions.cs create mode 100644 src/TestUtils/src/UITest.Appium/Actions/AppiumIOSStepperActions.cs create mode 100644 src/TestUtils/src/UITest.Appium/Actions/AppiumWindowsStepperActions.cs diff --git a/src/Controls/tests/TestCases.HostApp/Elements/StepperCoreGalleryPage.cs b/src/Controls/tests/TestCases.HostApp/Elements/StepperCoreGalleryPage.cs index df3b01a59a96..4f9222513d13 100644 --- a/src/Controls/tests/TestCases.HostApp/Elements/StepperCoreGalleryPage.cs +++ b/src/Controls/tests/TestCases.HostApp/Elements/StepperCoreGalleryPage.cs @@ -1,18 +1,31 @@ -using Microsoft.Maui.Controls; - namespace Maui.Controls.Sample; -internal class StepperCoreGalleryPage : CoreGalleryPage +internal class StepperCoreGalleryPage : ContentPage { - protected override bool SupportsFocus => false; - protected override bool SupportsTapGestureRecognizer => false; - - protected override void InitializeElement(Stepper element) + public StepperCoreGalleryPage() { - } + var layout = new StackLayout + { + Padding = new Thickness(12) + }; - protected override void Build() - { - base.Build(); + // Default + var defaultLabel = new Label { AutomationId = "DefaultLabel", Text = "Default" }; + var defaultStepper = new Stepper { AutomationId = "DefaultStepper" }; + var defaultLabelValue = new Label + { + AutomationId = "DefaultLabelValue", + Text = defaultStepper.Value.ToString() + }; + layout.Add(defaultLabel); + layout.Add(defaultStepper); + layout.Add(defaultLabelValue); + + defaultStepper.ValueChanged += (s, e) => + { + defaultLabelValue.Text = e.NewValue.ToString(); + }; + + Content = layout; } -} +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/StepperUITests.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/StepperUITests.cs new file mode 100644 index 000000000000..6104379e7a7f --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/StepperUITests.cs @@ -0,0 +1,82 @@ +#if ANDROID || IOS || WINDOWS +using NUnit.Framework; +using NUnit.Framework.Legacy; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests +{ + [Category(UITestCategories.Stepper)] + public class StepperUITests : UITest + { + public const string StepperGallery = "Stepper Gallery"; + + public StepperUITests(TestDevice device) + : base(device) + { + } + + protected override void FixtureSetup() + { + base.FixtureSetup(); + App.NavigateToGallery(StepperGallery); + } + + [Test] + [Category(UITestCategories.Stepper)] + [Description("Increase the Stepper value")] + public void IncreaseStepper() + { + const string titleAutomationId = "DefaultLabel"; + const string stepperAutomationId = "DefaultStepper"; + const string valueAutomationId = "DefaultLabelValue"; + + App.WaitForElement(titleAutomationId); + + // 1. Check the current value. + var step1Value = App.FindElement(valueAutomationId).GetText(); + ClassicAssert.AreEqual("0", step1Value); + + // 2. Increase the value. + App.IncreaseStepper(stepperAutomationId); + App.Screenshot("Increase the Stepper value"); + + // 3. Verify that the value has increased. + var step3Value = App.FindElement(valueAutomationId).GetText(); + ClassicAssert.AreEqual("1", step3Value); + } + + [Test] + [Category(UITestCategories.Stepper)] + [Description("Decrease the Stepper value")] + public void DecreaseStepper() + { + const string titleAutomationId = "DefaultLabel"; + const string stepperAutomationId = "DefaultStepper"; + const string valueAutomationId = "DefaultLabelValue"; + + App.WaitForElement(titleAutomationId); + + // 1. Check the current value. + var step1Value = App.FindElement(valueAutomationId).GetText(); + ClassicAssert.AreEqual("0", step1Value); + + // 2. Increase the value. + App.IncreaseStepper(stepperAutomationId); + App.Screenshot("Increase the Stepper value"); + + // 3. Verify that the value has increased. + var step3Value = App.FindElement(valueAutomationId).GetText(); + ClassicAssert.AreEqual("1", step3Value); + + // 4. Decrease the value. + App.DecreaseStepper(stepperAutomationId); + App.Screenshot("Decrease the Stepper value"); + + // 5. Verify that the value has decreased. + var step5Value = App.FindElement(valueAutomationId).GetText(); + ClassicAssert.AreEqual("0", step5Value); + } + } +} +#endif \ No newline at end of file diff --git a/src/Core/src/Platform/Windows/MauiStepper.cs b/src/Core/src/Platform/Windows/MauiStepper.cs index 2ae0eda2ec4a..7bfacce47176 100644 --- a/src/Core/src/Platform/Windows/MauiStepper.cs +++ b/src/Core/src/Platform/Windows/MauiStepper.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.Maui.Graphics; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using WBrush = Microsoft.UI.Xaml.Media.Brush; @@ -98,14 +99,21 @@ protected override void OnApplyTemplate() { base.OnApplyTemplate(); + var mauiStepperAutomationId = AutomationProperties.GetAutomationId(this); + _plus = GetTemplateChild("Plus") as Button; if (_plus != null) + { + AutomationProperties.SetAutomationId(_plus, mauiStepperAutomationId + "Plus"); _plus.Click += OnPlusClicked; + } _minus = GetTemplateChild("Minus") as Button; if (_minus != null) + { + AutomationProperties.SetAutomationId(_minus, mauiStepperAutomationId + "Minus"); _minus.Click += OnMinusClicked; - + } UpdateEnabled(Value); UpdateButtonBackground(); } diff --git a/src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidStepperActions.cs b/src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidStepperActions.cs new file mode 100644 index 000000000000..7d624e552b85 --- /dev/null +++ b/src/TestUtils/src/UITest.Appium/Actions/AppiumAndroidStepperActions.cs @@ -0,0 +1,94 @@ +using OpenQA.Selenium.Appium; +using UITest.Core; + +namespace UITest.Appium; + +public class AppiumAndroidStepperActions : ICommandExecutionGroup +{ + const string IncreaseCommand = "increaseStepper"; + const string DecreaseCommand = "decreaseStepper"; + + readonly List _commands = new() + { + IncreaseCommand, + DecreaseCommand + }; + readonly AppiumApp _appiumApp; + + public AppiumAndroidStepperActions(AppiumApp appiumApp) + { + _appiumApp = appiumApp; + } + + public bool IsCommandSupported(string commandName) + { + return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase); + } + + public CommandResponse Execute(string commandName, IDictionary parameters) + { + return commandName switch + { + IncreaseCommand => Increase(parameters), + DecreaseCommand => Decrease(parameters), + _ => CommandResponse.FailedEmptyResponse, + }; + } + + CommandResponse Increase(IDictionary parameters) + { + string? elementId = parameters["elementId"].ToString(); + + if (elementId is null) + return CommandResponse.FailedEmptyResponse; + + var element = _appiumApp.FindElement(elementId); + var stepper = GetAppiumElement(element); + + if (stepper is null) + return CommandResponse.FailedEmptyResponse; + + var buttons = AppiumQuery.ByClass("android.widget.Button").FindElements(stepper, _appiumApp); + + if(buttons is not null && buttons.Count > 1) + { + var increaseButton = buttons.LastOrDefault(); + increaseButton?.Tap(); + } + + return CommandResponse.SuccessEmptyResponse; + } + + CommandResponse Decrease(IDictionary parameters) + { + string? elementId = parameters["elementId"].ToString(); + + if (elementId is null) + return CommandResponse.FailedEmptyResponse; + + var element = _appiumApp.FindElement(elementId); + var stepper = GetAppiumElement(element); + + if (stepper is null) + return CommandResponse.FailedEmptyResponse; + + var buttons = AppiumQuery.ByClass("android.widget.Button").FindElements(stepper, _appiumApp); + + if (buttons is not null && buttons.Count > 1) + { + var decreaseButton = buttons.FirstOrDefault(); + decreaseButton?.Tap(); + } + + return CommandResponse.SuccessEmptyResponse; + } + + static AppiumElement? GetAppiumElement(object element) => + element switch + { + AppiumElement appiumElement => appiumElement, + AppiumDriverElement driverElement => driverElement.AppiumElement, + _ => null + }; +} + diff --git a/src/TestUtils/src/UITest.Appium/Actions/AppiumIOSStepperActions.cs b/src/TestUtils/src/UITest.Appium/Actions/AppiumIOSStepperActions.cs new file mode 100644 index 000000000000..4c10b31430bd --- /dev/null +++ b/src/TestUtils/src/UITest.Appium/Actions/AppiumIOSStepperActions.cs @@ -0,0 +1,94 @@ +using OpenQA.Selenium.Appium; +using UITest.Core; + +namespace UITest.Appium; + +class AppiumIOSStepperActions : ICommandExecutionGroup +{ + const string IncreaseCommand = "increaseStepper"; + const string DecreaseCommand = "decreaseStepper"; + + readonly List _commands = new() + { + IncreaseCommand, + DecreaseCommand + }; + + readonly AppiumApp _appiumApp; + + public AppiumIOSStepperActions(AppiumApp appiumApp) + { + _appiumApp = appiumApp; + } + + public bool IsCommandSupported(string commandName) + { + return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase); + } + + public CommandResponse Execute(string commandName, IDictionary parameters) + { + return commandName switch + { + IncreaseCommand => Increase(parameters), + DecreaseCommand => Decrease(parameters), + _ => CommandResponse.FailedEmptyResponse, + }; + } + + CommandResponse Increase(IDictionary parameters) + { + string? elementId = parameters["elementId"].ToString(); + + if (elementId is null) + return CommandResponse.FailedEmptyResponse; + + var element = _appiumApp.FindElement(elementId); + var stepper = GetAppiumElement(element); + + if (stepper is null) + return CommandResponse.FailedEmptyResponse; + + var buttons = AppiumQuery.ByClass("XCUIElementTypeButton").FindElements(stepper, _appiumApp); + + if (buttons is not null && buttons.Count > 1) + { + var increaseButton = buttons.LastOrDefault(); + increaseButton?.Tap(); + } + + return CommandResponse.SuccessEmptyResponse; + } + + CommandResponse Decrease(IDictionary parameters) + { + string? elementId = parameters["elementId"].ToString(); + + if (elementId is null) + return CommandResponse.FailedEmptyResponse; + + var element = _appiumApp.FindElement(elementId); + var stepper = GetAppiumElement(element); + + if (stepper is null) + return CommandResponse.FailedEmptyResponse; + + var buttons = AppiumQuery.ByClass("XCUIElementTypeButton").FindElements(stepper, _appiumApp); + + if (buttons is not null && buttons.Count > 1) + { + var decreaseButton = buttons.FirstOrDefault(); + decreaseButton?.Tap(); + } + + return CommandResponse.SuccessEmptyResponse; + } + + static AppiumElement? GetAppiumElement(object element) => + element switch + { + AppiumElement appiumElement => appiumElement, + AppiumDriverElement driverElement => driverElement.AppiumElement, + _ => null + }; +} \ No newline at end of file diff --git a/src/TestUtils/src/UITest.Appium/Actions/AppiumWindowsStepperActions.cs b/src/TestUtils/src/UITest.Appium/Actions/AppiumWindowsStepperActions.cs new file mode 100644 index 000000000000..12520d0888e3 --- /dev/null +++ b/src/TestUtils/src/UITest.Appium/Actions/AppiumWindowsStepperActions.cs @@ -0,0 +1,61 @@ +using UITest.Core; + +namespace UITest.Appium; + +public class AppiumWindowsStepperActions : ICommandExecutionGroup +{ + const string IncreaseCommand = "increaseStepper"; + const string DecreaseCommand = "decreaseStepper"; + + readonly List _commands = new() + { + IncreaseCommand, + DecreaseCommand + }; + readonly AppiumApp _appiumApp; + + public AppiumWindowsStepperActions(AppiumApp appiumApp) + { + _appiumApp = appiumApp; + } + + public bool IsCommandSupported(string commandName) + { + return _commands.Contains(commandName, StringComparer.OrdinalIgnoreCase); + } + + public CommandResponse Execute(string commandName, IDictionary parameters) + { + return commandName switch + { + IncreaseCommand => Increase(parameters), + DecreaseCommand => Decrease(parameters), + _ => CommandResponse.FailedEmptyResponse, + }; + } + + CommandResponse Increase(IDictionary parameters) + { + string? elementId = parameters["elementId"].ToString(); + + if (elementId is null) + return CommandResponse.FailedEmptyResponse; + + var increaseButton = _appiumApp.FindElement(elementId + "Plus"); + increaseButton?.Click(); + return CommandResponse.SuccessEmptyResponse; + } + + CommandResponse Decrease(IDictionary parameters) + { + string? elementId = parameters["elementId"].ToString(); + + if (elementId is null) + return CommandResponse.FailedEmptyResponse; + + var decreaseButton = _appiumApp.FindElement(elementId + "Minus"); + decreaseButton?.Click(); + + return CommandResponse.SuccessEmptyResponse; + } +} \ No newline at end of file diff --git a/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs b/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs index c842e1602ee3..d4986e1bd749 100644 --- a/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs +++ b/src/TestUtils/src/UITest.Appium/AppiumAndroidApp.cs @@ -14,6 +14,7 @@ private AppiumAndroidApp(Uri remoteAddress, IConfig config) _commandExecutor.AddCommandGroup(new AppiumAndroidSpecificActions(this)); _commandExecutor.AddCommandGroup(new AppiumAndroidVirtualKeyboardActions(this)); _commandExecutor.AddCommandGroup(new AppiumAndroidAlertActions(this)); + _commandExecutor.AddCommandGroup(new AppiumAndroidStepperActions(this)); } public static AppiumAndroidApp CreateAndroidApp(Uri remoteAddress, IConfig config) diff --git a/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs b/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs index e94cc91361e3..f9e61425d2f0 100644 --- a/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs +++ b/src/TestUtils/src/UITest.Appium/AppiumIOSApp.cs @@ -18,6 +18,7 @@ public AppiumIOSApp(Uri remoteAddress, IConfig config) _commandExecutor.AddCommandGroup(new AppiumIOSThemeChangeAction(this)); _commandExecutor.AddCommandGroup(new AppiumIOSAlertActions(this)); _commandExecutor.AddCommandGroup(new AppiumIOSThemeChangeAction(this)); + _commandExecutor.AddCommandGroup(new AppiumIOSStepperActions(this)); } public override ApplicationState AppState diff --git a/src/TestUtils/src/UITest.Appium/AppiumWindowsApp.cs b/src/TestUtils/src/UITest.Appium/AppiumWindowsApp.cs index b72f4c10b4da..4ef37db79aec 100644 --- a/src/TestUtils/src/UITest.Appium/AppiumWindowsApp.cs +++ b/src/TestUtils/src/UITest.Appium/AppiumWindowsApp.cs @@ -10,7 +10,7 @@ public class AppiumWindowsApp : AppiumApp, IWindowsApp public AppiumWindowsApp(Uri remoteAddress, IConfig config) : base(new WindowsDriver(remoteAddress, GetOptions(config)), config) { - + _commandExecutor.AddCommandGroup(new AppiumWindowsStepperActions(this)); } public override ApplicationState AppState diff --git a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs index 8383ada22f3f..b7a6c3214b4a 100644 --- a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs +++ b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs @@ -766,6 +766,32 @@ public static void SetSliderValue(this IApp app, string marked, double value, do }); } + /// + /// Increases the value of a Stepper control. + /// + /// Represents the main gateway to interact with an app. + /// Marked selector of the Stepper element to increase. + public static void IncreaseStepper(this IApp app, string marked) + { + app.CommandExecutor.Execute("increaseStepper", new Dictionary + { + ["elementId"] = marked + }); + } + + /// + /// Decreases the value of a Stepper control. + /// + /// Represents the main gateway to interact with an app. + /// Marked selector of the Stepper element to decrease. + public static void DecreaseStepper(this IApp app, string marked) + { + app.CommandExecutor.Execute("decreaseStepper", new Dictionary + { + ["elementId"] = marked + }); + } + /// /// Performs a continuous drag gesture between 2 points. ///