From 932116399494f840ac2c6b522f23a25419c03698 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Wed, 20 Apr 2022 18:47:20 +0100 Subject: [PATCH 01/32] WIP --- ...ransactionMobile.Maui.BusinessLogic.csproj | 2 +- .../Common/BaseTestFixture.cs | 16 ++ .../Common/MobileTestPlatform.cs | 9 ++ .../Common/Retry.cs | 68 ++++++++ .../Common/SpecflowTableHelper.cs | 135 ++++++++++++++++ .../Drivers/AppiumDriver.cs | 111 +++++++++++++ .../Features/Login.feature | 32 ++++ .../Features/Login.feature.cs | 141 +++++++++++++++++ .../Features/LoginFeature.cs | 13 ++ .../Hooks/AppiumHooks.cs | 34 ++++ .../Pages/BasePage.cs | 147 ++++++++++++++++++ .../Pages/Extenstions.cs | 128 +++++++++++++++ .../Pages/LoginPage.cs | 52 +++++++ .../Pages/MainPage.cs | 70 +++++++++ .../Steps/LoginSteps.cs | 56 +++++++ .../TransactionMobile.Maui.UITests.csproj | 26 ++++ .../Common/BaseTestFixture.cs | 17 ++ .../Common/Retry.cs | 68 ++++++++ .../Common/SpecflowTableHelper.cs | 135 ++++++++++++++++ .../Drivers/AppiumDriver.cs | 86 ++++++++++ .../Features/Login.feature | 13 ++ .../Features/Login.feature.cs | 126 +++++++++++++++ .../Features/LoginFeature.cs | 15 ++ .../Hooks/AppiumHooks.cs | 35 +++++ .../Pages/BasePage.cs | 89 +++++++++++ .../Pages/Extenstions.cs | 115 ++++++++++++++ .../Pages/LoginPage.cs | 47 ++++++ .../Pages/MainPage.cs | 71 +++++++++ .../Steps/LoginSteps.cs | 55 +++++++ .../TransactionMobile.Maui.UiTests.csproj | 29 ++++ TransactionMobile.Maui.sln | 17 ++ TransactionMobile.Maui/App.xaml.cs | 41 ++++- TransactionMobile.Maui/MauiProgram.cs | 1 - TransactionMobile.Maui/Pages/LoginPage.xaml | 3 +- .../TransactionMobile.Maui.csproj | 11 +- 35 files changed, 2002 insertions(+), 12 deletions(-) create mode 100644 TransactionMobile.Maui.UITests - Copy/Common/BaseTestFixture.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Common/MobileTestPlatform.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Common/Retry.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Common/SpecflowTableHelper.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Drivers/AppiumDriver.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Features/Login.feature create mode 100644 TransactionMobile.Maui.UITests - Copy/Features/Login.feature.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Features/LoginFeature.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Hooks/AppiumHooks.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Pages/BasePage.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Pages/Extenstions.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Pages/LoginPage.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Pages/MainPage.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/Steps/LoginSteps.cs create mode 100644 TransactionMobile.Maui.UITests - Copy/TransactionMobile.Maui.UITests.csproj create mode 100644 TransactionMobile.Maui.UiTests/Common/BaseTestFixture.cs create mode 100644 TransactionMobile.Maui.UiTests/Common/Retry.cs create mode 100644 TransactionMobile.Maui.UiTests/Common/SpecflowTableHelper.cs create mode 100644 TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs create mode 100644 TransactionMobile.Maui.UiTests/Features/Login.feature create mode 100644 TransactionMobile.Maui.UiTests/Features/Login.feature.cs create mode 100644 TransactionMobile.Maui.UiTests/Features/LoginFeature.cs create mode 100644 TransactionMobile.Maui.UiTests/Hooks/AppiumHooks.cs create mode 100644 TransactionMobile.Maui.UiTests/Pages/BasePage.cs create mode 100644 TransactionMobile.Maui.UiTests/Pages/Extenstions.cs create mode 100644 TransactionMobile.Maui.UiTests/Pages/LoginPage.cs create mode 100644 TransactionMobile.Maui.UiTests/Pages/MainPage.cs create mode 100644 TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs create mode 100644 TransactionMobile.Maui.UiTests/TransactionMobile.Maui.UiTests.csproj diff --git a/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj b/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj index 3501235e..76f97a35 100644 --- a/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj +++ b/TransactionMobile.Maui.BusinessLogic/TransactionMobile.Maui.BusinessLogic.csproj @@ -25,7 +25,7 @@ - + diff --git a/TransactionMobile.Maui.UITests - Copy/Common/BaseTestFixture.cs b/TransactionMobile.Maui.UITests - Copy/Common/BaseTestFixture.cs new file mode 100644 index 00000000..dfc69fc6 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Common/BaseTestFixture.cs @@ -0,0 +1,16 @@ +namespace TransactionMobile.Maui.UITests.Common +{ + using Drivers; + + public abstract class BaseTestFixture + { + #region Constructors + + protected BaseTestFixture(MobileTestPlatform mobileTestPlatform) + { + AppiumDriver.MobileTestPlatform = mobileTestPlatform; + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Common/MobileTestPlatform.cs b/TransactionMobile.Maui.UITests - Copy/Common/MobileTestPlatform.cs new file mode 100644 index 00000000..8465ee22 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Common/MobileTestPlatform.cs @@ -0,0 +1,9 @@ +namespace TransactionMobile.Maui.UITests.Common; + +public enum MobileTestPlatform +{ + iOS, + Android, + Windows, + MacCatalyst +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Common/Retry.cs b/TransactionMobile.Maui.UITests - Copy/Common/Retry.cs new file mode 100644 index 00000000..c82c362a --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Common/Retry.cs @@ -0,0 +1,68 @@ +namespace TransactionMobile.Maui.UITests.Common; + +using System; +using System.Threading; +using System.Threading.Tasks; + +public static class Retry +{ + #region Fields + + /// + /// The default retry for + /// + private static readonly TimeSpan DefaultRetryFor = TimeSpan.FromSeconds(60); + + /// + /// The default retry interval + /// + private static readonly TimeSpan DefaultRetryInterval = TimeSpan.FromSeconds(5); + + #endregion + + #region Methods + + /// + /// Fors the specified action. + /// + /// The action. + /// The retry for. + /// The retry interval. + /// + public static async Task For(Func action, + TimeSpan? retryFor = null, + TimeSpan? retryInterval = null) + { + DateTime startTime = DateTime.Now; + Exception lastException = null; + + if (retryFor == null) + { + retryFor = Retry.DefaultRetryFor; + } + + while (DateTime.Now.Subtract(startTime).TotalMilliseconds < retryFor.Value.TotalMilliseconds) + { + try + { + await action().ConfigureAwait(false); + lastException = null; + break; + } + catch (Exception e) + { + lastException = e; + + // wait before retrying + Thread.Sleep(retryInterval ?? Retry.DefaultRetryInterval); + } + } + + if (lastException != null) + { + throw lastException; + } + } + + #endregion +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Common/SpecflowTableHelper.cs b/TransactionMobile.Maui.UITests - Copy/Common/SpecflowTableHelper.cs new file mode 100644 index 00000000..2e5e2846 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Common/SpecflowTableHelper.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UITests.Common +{ + using TechTalk.SpecFlow; + + public static class SpecflowTableHelper + { + #region Methods + + /// + /// Gets the enum value. + /// + /// + /// The row. + /// The key. + /// + public static T GetEnumValue(TableRow row, + String key) where T : struct + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + Enum.TryParse(field, out T myEnum); + + return myEnum; + } + + /// + /// Gets the boolean value. + /// + /// The row. + /// The key. + /// + public static Boolean GetBooleanValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return bool.TryParse(field, out Boolean value) && value; + } + + /// + /// Gets the date for date string. + /// + /// The date string. + /// The today. + /// + public static DateTime GetDateForDateString(String dateString, + DateTime today) + { + switch (dateString.ToUpper()) + { + case "TODAY": + return today.Date; + case "YESTERDAY": + return today.AddDays(-1).Date; + case "LASTWEEK": + return today.AddDays(-7).Date; + case "LASTMONTH": + return today.AddMonths(-1).Date; + case "LASTYEAR": + return today.AddYears(-1).Date; + case "TOMORROW": + return today.AddDays(1).Date; + default: + return DateTime.Parse(dateString); + } + } + + /// + /// Gets the decimal value. + /// + /// The row. + /// The key. + /// + public static Decimal GetDecimalValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return decimal.TryParse(field, out Decimal value) ? value : 0; + } + + /// + /// Gets the int value. + /// + /// The row. + /// The key. + /// + public static Int32 GetIntValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return int.TryParse(field, out Int32 value) ? value : -1; + } + + /// + /// Gets the short value. + /// + /// The row. + /// The key. + /// + public static Int16 GetShortValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + if (short.TryParse(field, out Int16 value)) + { + return value; + } + + return -1; + } + + /// + /// Gets the string row value. + /// + /// The row. + /// The key. + /// + public static String GetStringRowValue(TableRow row, + String key) + { + return row.TryGetValue(key, out String value) ? value : ""; + } + + #endregion + } +} diff --git a/TransactionMobile.Maui.UITests - Copy/Drivers/AppiumDriver.cs b/TransactionMobile.Maui.UITests - Copy/Drivers/AppiumDriver.cs new file mode 100644 index 00000000..262c8a8d --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Drivers/AppiumDriver.cs @@ -0,0 +1,111 @@ +namespace TransactionMobile.Maui.UITests.Drivers +{ + using System; + using System.IO; + using System.Reflection; + using Common; + using OpenQA.Selenium.Appium; + using OpenQA.Selenium.Appium.Android; + using OpenQA.Selenium.Appium.Enums; + using OpenQA.Selenium.Appium.iOS; + using OpenQA.Selenium.Appium.Mac; + using OpenQA.Selenium.Appium.Service; + using OpenQA.Selenium.Appium.Windows; + + public class AppiumDriver + { + #region Fields + + public static AndroidDriver AndroidDriver; + + public static IOSDriver iOSDriver; + + public static MacDriver MacDriver; + + public static MobileTestPlatform MobileTestPlatform; + + public static WindowsDriver WindowsDriver; + + #endregion + + #region Methods + + public void StartApp() + { + AppiumLocalService appiumService = new AppiumServiceBuilder().UsingPort(4723).Build(); + + if (appiumService.IsRunning == false) + { + appiumService.Start(); + } + + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + var driverOptions = new AppiumOptions(); + driverOptions.AddAdditionalCapability("adbExecTimeout", TimeSpan.FromMinutes(5).Milliseconds); + driverOptions.AddAdditionalCapability(MobileCapabilityType.AutomationName, "Espresso"); + // TODO: Only do this locally + driverOptions.AddAdditionalCapability(MobileCapabilityType.FullReset, true); + driverOptions.AddAdditionalCapability("forceEspressoRebuild", true); + driverOptions.AddAdditionalCapability("enforceAppInstall", true); + driverOptions.AddAdditionalCapability("noSign", true); + driverOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android"); + driverOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, "9.0"); + driverOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, "emulator-5554"); + + String assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + String binariesFolder = Path.Combine(assemblyFolder, "..", "..", "..", "..", @"TransactionMobile.Maui/bin/Release/net6.0-android/"); + var apkPath = Path.Combine(binariesFolder, "com.transactionprocessing.pos-Signed.apk"); + driverOptions.AddAdditionalCapability(MobileCapabilityType.App, apkPath); + driverOptions.AddAdditionalCapability("espressoBuildConfig", + "{ \"additionalAppDependencies\": [ \"com.google.android.material:material:1.0.0\", \"androidx.lifecycle:lifecycle-extensions:2.1.0\" ] }"); + + AppiumDriver.AndroidDriver = new AndroidDriver(appiumService, driverOptions, TimeSpan.FromMinutes(10)); + } + + //if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + //{ + // var driverOptions = new AppiumOptions(); + // driverOptions.AddAdditionalCapability(MobileCapabilityType.PlatformName, "iOS"); + // driverOptions.AddAdditionalCapability(MobileCapabilityType.DeviceName, "iPhone 11"); + // driverOptions.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, "14.4"); + + // String assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + // String binariesFolder = Path.Combine(assemblyFolder, "..", "..", "..", "..", @"TransactionMobile.iOS/bin/iPhoneSimulator/Release"); + // var apkPath = Path.Combine(binariesFolder, "TransactionMobile.iOS.app"); + // driverOptions.AddAdditionalCapability(MobileCapabilityType.App, apkPath); + // driverOptions.AddAdditionalCapability(MobileCapabilityType.FullReset, true); + // driverOptions.AddAdditionalCapability(MobileCapabilityType.AutomationName, "XCUITest"); + // driverOptions.AddAdditionalCapability("useNewWDA", true); + // driverOptions.AddAdditionalCapability("wdaLaunchTimeout", 999999999); + // driverOptions.AddAdditionalCapability("wdaConnectionTimeout", 999999999); + // driverOptions.AddAdditionalCapability("restart", true); + + // AppiumDriver.iOSDriver = new IOSDriver(appiumService, driverOptions, TimeSpan.FromMinutes(5)); + //} + + // TODO: Implement iOS Tests + // TODO: Implement Windows UI Tests + // TODO: Implement Mac UI Tests + } + + public void StopApp() + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + AppiumDriver.AndroidDriver.Quit(); + } + //else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + //{ + // AppiumDriver.iOSDriver.CloseApp(); + // AppiumDriver.iOSDriver.Quit(); + //} + + // TODO: Implement iOS Tests + // TODO: Implement Windows UI Tests + // TODO: Implement Mac UI Tests + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Features/Login.feature b/TransactionMobile.Maui.UITests - Copy/Features/Login.feature new file mode 100644 index 00000000..b2c6a049 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Features/Login.feature @@ -0,0 +1,32 @@ +@background @login +Feature: Login + +Background: + +# Given I have created the following estates +# | EstateName | +# | Test Estate 1 | +# +# Given I have created the following operators +# | EstateName | OperatorName | RequireCustomMerchantNumber | RequireCustomTerminalNumber | +# | Test Estate 1 | Safaricom | True | True | +# +# Given I create the following merchants +# | MerchantName | EstateName | EmailAddress | Password | GivenName | FamilyName | +# | Test Merchant 1 | Test Estate 1 | merchantuser@testmerchant1.co.uk | 123456 | TestMerchant | User1 | +# +# Given I make the following manual merchant deposits +# | Amount | DateTime | MerchantName | EstateName | +# | 1000.00 | Today | Test Merchant 1 | Test Estate 1 | +# | 1000.00 | Yesterday | Test Merchant 1 | Test Estate 1 | +# +# Given the application in in test mode + +@PRTest +Scenario: Login as Merchant + Given I am on the Login Screen + When I enter 'merchantuser@testmerchant1.co.uk' as the Email Address + And I enter '123456' as the Password + And I tap on Login + Then the Merchant Home Page is displayed + #And the available balance is shown as 2000.00 \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Features/Login.feature.cs b/TransactionMobile.Maui.UITests - Copy/Features/Login.feature.cs new file mode 100644 index 00000000..c2358355 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Features/Login.feature.cs @@ -0,0 +1,141 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace TransactionMobile.Maui.UITests.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("Login")] + [NUnit.Framework.CategoryAttribute("background")] + [NUnit.Framework.CategoryAttribute("login")] + public partial class LoginFeature + { + + private TechTalk.SpecFlow.ITestRunner testRunner; + + private string[] _featureTags = new string[] { + "background", + "login"}; + +#line 1 "Login.feature" +#line hidden + + [NUnit.Framework.OneTimeSetUpAttribute()] + public virtual void FeatureSetup() + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Login", null, ProgrammingLanguage.CSharp, new string[] { + "background", + "login"}); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.OneTimeTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); + } + + public virtual void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + public virtual void FeatureBackground() + { +#line 4 +#line hidden + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Login as Merchant")] + [NUnit.Framework.CategoryAttribute("PRTest")] + public virtual void LoginAsMerchant() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Login as Merchant", null, tagsOfScenario, argumentsOfScenario, this._featureTags); +#line 26 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 +this.FeatureBackground(); +#line hidden +#line 27 + testRunner.Given("I am on the Login Screen", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line hidden +#line 28 + testRunner.When("I enter \'merchantuser@testmerchant1.co.uk\' as the Email Address", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 29 + testRunner.And("I enter \'123456\' as the Password", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden +#line 30 + testRunner.And("I tap on Login", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden +#line 31 + testRunner.Then("the Merchant Home Page is displayed", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/TransactionMobile.Maui.UITests - Copy/Features/LoginFeature.cs b/TransactionMobile.Maui.UITests - Copy/Features/LoginFeature.cs new file mode 100644 index 00000000..b8f7f3a8 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Features/LoginFeature.cs @@ -0,0 +1,13 @@ +namespace TransactionMobile.Maui.UITests.Features; + +using Common; +using NUnit.Framework; + +[TestFixture(MobileTestPlatform.Android, Category = "Android")] +public partial class LoginFeature : BaseTestFixture +{ + public LoginFeature(MobileTestPlatform mobileTestPlatform) + : base(mobileTestPlatform) + { + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Hooks/AppiumHooks.cs b/TransactionMobile.Maui.UITests - Copy/Hooks/AppiumHooks.cs new file mode 100644 index 00000000..c0369f61 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Hooks/AppiumHooks.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UITests.Hooks +{ + using Drivers; + using TechTalk.SpecFlow; + + [Binding] + public class AppiumHooks + { + private readonly AppiumDriver _appiumDriver; + + public AppiumHooks(AppiumDriver appiumDriver) + { + _appiumDriver = appiumDriver; + } + + [BeforeScenario()] + public void StartApp() + { + _appiumDriver.StartApp(); + } + + [AfterScenario()] + public void ShutdownApp() + { + _appiumDriver.StopApp(); + } + } +} diff --git a/TransactionMobile.Maui.UITests - Copy/Pages/BasePage.cs b/TransactionMobile.Maui.UITests - Copy/Pages/BasePage.cs new file mode 100644 index 00000000..09e86090 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Pages/BasePage.cs @@ -0,0 +1,147 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UITests +{ + using Common; + using Drivers; + using OpenQA.Selenium; + using Shouldly; + + public abstract class BasePage + { + protected abstract String Trait { get; } + + public async Task AssertOnPage(TimeSpan? timeout = null) + { + timeout = timeout ?? TimeSpan.FromSeconds(60); + + await Retry.For(async () => + { + String message = "Unable to verify on page: " + this.GetType().Name; + + Should.NotThrow(() => this.WaitForElementByAccessibilityId(this.Trait), message); + }, + TimeSpan.FromMinutes(1), + timeout).ConfigureAwait(false); + + } + + /// + /// Verifies that the trait is no longer present. Defaults to a 5 second wait. + /// + /// Time to wait before the assertion fails + public void WaitForPageToLeave(TimeSpan? timeout = null) + { + timeout = timeout ?? TimeSpan.FromSeconds(5); + var message = "Unable to verify *not* on page: " + this.GetType().Name; + + Should.NotThrow(() => this.WaitForNoElementByAccessibilityId(this.Trait), message); + } + + public async Task WaitForElementByAccessibilityId(String x, TimeSpan? timeout = null) + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + return await AppiumDriver.AndroidDriver.WaitForElementByAccessibilityId(x, timeout); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + return await AppiumDriver.iOSDriver.WaitForElementByAccessibilityId(x, timeout); + } + + return null; + } + + public async Task GetPageSource() + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + return await AppiumDriver.AndroidDriver.GetPageSource(); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + return await AppiumDriver.iOSDriver.GetPageSource(); + } + + return null; + } + + public async Task WaitForNoElementByAccessibilityId(String x) + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + await AppiumDriver.AndroidDriver.WaitForNoElementByAccessibilityId(x); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + await AppiumDriver.iOSDriver.WaitForNoElementByAccessibilityId(x); + } + } + + public async Task WaitForToastMessage(String x) + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + await AppiumDriver.AndroidDriver.WaitForToastMessage(x); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + await AppiumDriver.iOSDriver.WaitForToastMessage(x); + } + } + + public void HideKeyboard() + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + AppiumDriver.AndroidDriver.HideKeyboard(); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + //if (AppiumDriver.iOSDriver.IsKeyboardShown()) + // AppiumDriver.iOSDriver.HideKeyboard(); + //AppiumDriver.iOSDriver.FindElementByName("Done").Click(); + //AppiumDriver.iOSDriver.HideKeyboard(); + } + } + + public IWebElement GetAlert() + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + return AppiumDriver.AndroidDriver.FindElementByClassName("androidx.appcompat.widget.AppCompatTextView"); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + return AppiumDriver.iOSDriver.FindElement(By.Name("OK")); + } + + return null; + } + + public IAlert SwitchToAlert() + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + return AppiumDriver.AndroidDriver.SwitchTo().Alert(); + } + + return null; + } + + public void NavigateBack() + { + if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.Android) + { + AppiumDriver.AndroidDriver.Navigate().Back(); + } + else if (AppiumDriver.MobileTestPlatform == MobileTestPlatform.iOS) + { + AppiumDriver.iOSDriver.Navigate().Back(); + } + } + } +} diff --git a/TransactionMobile.Maui.UITests - Copy/Pages/Extenstions.cs b/TransactionMobile.Maui.UITests - Copy/Pages/Extenstions.cs new file mode 100644 index 00000000..09819a33 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Pages/Extenstions.cs @@ -0,0 +1,128 @@ +namespace TransactionMobile.Maui.UITests; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Common; +using OpenQA.Selenium; +using OpenQA.Selenium.Appium.Android; +using OpenQA.Selenium.Appium.iOS; +using Shouldly; + +public static class Extenstions +{ + // TODO: Mac & Windows Extensions + // TODO: May need a platform switch + public static AndroidElement GetAlert(this AndroidDriver driver) + { + return driver.FindElementByClassName("androidx.appcompat.widget.AppCompatTextView"); + } + + public static async Task WaitForElementByAccessibilityId(this AndroidDriver driver, + String selector, + TimeSpan? timeout = null) + { + timeout ??= TimeSpan.FromSeconds(60); + AndroidElement element = null; + await Retry.For(async () => + { + element = driver.FindElementByAccessibilityId(selector); + element.ShouldNotBeNull(); + }); + + return element; + } + + public static async Task WaitForElementByAccessibilityId(this IOSDriver driver, + String selector, + TimeSpan? timeout = null) + { + timeout ??= TimeSpan.FromSeconds(60); + IOSElement element = null; + await Retry.For(async () => + { + element = driver.FindElementByAccessibilityId(selector); + element.ShouldNotBeNull(); + }); + + return element; + + } + + public static async Task WaitForNoElementByAccessibilityId(this IOSDriver driver, + String selector, + TimeSpan? timeout = null) + { + timeout ??= TimeSpan.FromSeconds(60); + + await Retry.For(async () => + { + IOSElement? element = driver.FindElementByAccessibilityId(selector); + element.ShouldBeNull(); + }); + + } + + public static async Task WaitForNoElementByAccessibilityId(this AndroidDriver driver, + String selector, + TimeSpan? timeout = null) + { + timeout ??= TimeSpan.FromSeconds(60); + + await Retry.For(async () => + { + AndroidElement? element = driver.FindElementByAccessibilityId(selector); + element.ShouldBeNull(); + }); + + } + + public static async Task WaitForToastMessage(this AndroidDriver driver, + String expectedToast) + { + await Retry.For(async () => + { + Dictionary args = new Dictionary + { + {"text", expectedToast}, + {"isRegexp", false} + }; + driver.ExecuteScript("mobile: isToastVisible", args); + + }); + } + + public static async Task WaitForToastMessage(this IOSDriver driver, + String expectedToast) + { + Boolean isDisplayed = false; + int count = 0; + do + { + if (driver.PageSource.Contains(expectedToast)) + { + Console.WriteLine(driver.PageSource); + isDisplayed = true; + break; + } + + Thread.Sleep(200); //Add your custom wait if exists + count++; + + } while (count < 10); + + Console.WriteLine(driver.PageSource); + isDisplayed.ShouldBeTrue(); + } + + public static async Task GetPageSource(this AndroidDriver driver) + { + return driver.PageSource; + } + + public static async Task GetPageSource(this IOSDriver driver) + { + return driver.PageSource; + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Pages/LoginPage.cs b/TransactionMobile.Maui.UITests - Copy/Pages/LoginPage.cs new file mode 100644 index 00000000..9b047d5d --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Pages/LoginPage.cs @@ -0,0 +1,52 @@ +namespace TransactionMobile.Maui.UITests; + +using System; +using System.Threading.Tasks; +using OpenQA.Selenium; + +public class LoginPage : BasePage +{ + protected override String Trait => "LoginLabel"; + + //private readonly String EmailEntry; + //private readonly String PasswordEntry; + private readonly String LoginButton; + //private readonly String TestModeButton; + //private readonly String ErrorLabel; + + public LoginPage() + { + //this.EmailEntry = "EmailEntry"; + //this.PasswordEntry = "PasswordEntry"; + this.LoginButton = "LoginButton"; + //this.TestModeButton = "TestModeButton"; + //this.ErrorLabel = "ErrorLabel"; + } + + public async Task EnterEmailAddress(String emailAddress) + { + //IWebElement element = await this.WaitForElementByAccessibilityId(this.EmailEntry); + + //element.SendKeys(emailAddress); + } + + public async Task EnterPassword(String password) + { + //IWebElement element = await this.WaitForElementByAccessibilityId(this.PasswordEntry); + //element.SendKeys(password); + } + + public async Task ClickLoginButton() + { + this.HideKeyboard(); + IWebElement element = await this.WaitForElementByAccessibilityId(this.LoginButton); + element.Click(); + } + + public async Task ClickTestModeButton() + { + //this.HideKeyboard(); + //IWebElement element = await this.WaitForElementByAccessibilityId(this.TestModeButton); + //element.Click(); + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Pages/MainPage.cs b/TransactionMobile.Maui.UITests - Copy/Pages/MainPage.cs new file mode 100644 index 00000000..ce536f4e --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Pages/MainPage.cs @@ -0,0 +1,70 @@ +namespace TransactionMobile.Maui.UITests; + +using System; +using System.Threading.Tasks; + +public class MainPage : BasePage +{ + protected override String Trait => "Home"; + + private readonly String TransactionsButton; + + private readonly String ReportsButton; + + private readonly String ProfileButton; + + private readonly String SupportButton; + + private readonly String AvailableBalanceLabel; + + /// + /// Initializes a new instance of the class. + /// + public MainPage() + { + this.TransactionsButton = "TransactionsButton"; + this.ReportsButton = "ReportsButton"; + this.ProfileButton = "ProfileButton"; + this.SupportButton = "SupportButton"; + this.AvailableBalanceLabel = "AvailableBalanceValueLabel"; + } + + public async Task ClickTransactionsButton() + { + var element = await this.WaitForElementByAccessibilityId(this.TransactionsButton); + element.Click(); + } + + public async Task ClickReportsButton() + { + var element = await this.WaitForElementByAccessibilityId(this.ReportsButton); + element.Click(); + } + + public void ClickProfileButton() + { + //app.WaitForElement(this.ProfileButton); + //app.Tap(this.ProfileButton); + } + + public void ClickSupportButton() + { + //app.WaitForElement(this.SupportButton); + //app.Tap(this.SupportButton); + } + + public async Task GetAvailableBalanceValue(TimeSpan? timeout = default(TimeSpan?)) + { + //await this.ScrollTo(this.Trait, this.AvailableBalanceLabel); + var element = await this.WaitForElementByAccessibilityId(this.AvailableBalanceLabel, timeout: TimeSpan.FromSeconds(30)); + + String availableBalanceText = element.Text.Replace(" KES", String.Empty); + + if (Decimal.TryParse(availableBalanceText, out Decimal balanceValue) == false) + { + throw new Exception($"Failed to parse [{availableBalanceText}] as a Decimal"); + } + + return balanceValue; + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UITests - Copy/Steps/LoginSteps.cs b/TransactionMobile.Maui.UITests - Copy/Steps/LoginSteps.cs new file mode 100644 index 00000000..1011aeb1 --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/Steps/LoginSteps.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UITests.Steps +{ + using Shouldly; + using TechTalk.SpecFlow; + + [Binding] + [Scope(Tag = "login")] + public class LoginSteps + { + LoginPage loginPage = new LoginPage(); + //MainPage mainPage = new MainPage(); + + [Given(@"I am on the Login Screen")] + public async Task GivenIAmOnTheLoginScreen() + { + await this.loginPage.AssertOnPage(); + } + + [When(@"I enter '(.*)' as the Email Address")] + public async Task WhenIEnterAsTheEmailAddress(String emailAddress) + { + await this.loginPage.EnterEmailAddress(emailAddress); + } + + [When(@"I enter '(.*)' as the Password")] + public async Task WhenIEnterAsThePassword(String password) + { + await this.loginPage.EnterPassword(password); + } + + [When(@"I tap on Login")] + public async Task WhenITapOnLogin() + { + await this.loginPage.ClickLoginButton(); + } + + [Then(@"the Merchant Home Page is displayed")] + public async Task ThenTheMerchantHomePageIsDisplayed() + { + //await this.mainPage.AssertOnPage(); + } + + [Then(@"the available balance is shown as (.*)")] + public async Task ThenTheAvailableBalanceIsShownAs(Decimal expectedAvailableBalance) + { + //Decimal availableBalance = await this.mainPage.GetAvailableBalanceValue(TimeSpan.FromSeconds(120)).ConfigureAwait(false); + //availableBalance.ShouldBe(expectedAvailableBalance); + } + } +} diff --git a/TransactionMobile.Maui.UITests - Copy/TransactionMobile.Maui.UITests.csproj b/TransactionMobile.Maui.UITests - Copy/TransactionMobile.Maui.UITests.csproj new file mode 100644 index 00000000..c22100dd --- /dev/null +++ b/TransactionMobile.Maui.UITests - Copy/TransactionMobile.Maui.UITests.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + + false + + Debug;Release;TestAuomation + + + + + + + + + + + + + + + + + diff --git a/TransactionMobile.Maui.UiTests/Common/BaseTestFixture.cs b/TransactionMobile.Maui.UiTests/Common/BaseTestFixture.cs new file mode 100644 index 00000000..09271d7e --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Common/BaseTestFixture.cs @@ -0,0 +1,17 @@ +using OpenQA.Selenium.Appium; +using TransactionMobile.Maui.UiTests.Drivers; + +namespace TransactionMobile.Maui.UITests.Common +{ + public abstract class BaseTestFixture + { + #region Constructors + + protected BaseTestFixture(MobileTestPlatform mobileTestPlatform) + { + AppiumDriverWrapper.MobileTestPlatform = mobileTestPlatform; + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Common/Retry.cs b/TransactionMobile.Maui.UiTests/Common/Retry.cs new file mode 100644 index 00000000..c82c362a --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Common/Retry.cs @@ -0,0 +1,68 @@ +namespace TransactionMobile.Maui.UITests.Common; + +using System; +using System.Threading; +using System.Threading.Tasks; + +public static class Retry +{ + #region Fields + + /// + /// The default retry for + /// + private static readonly TimeSpan DefaultRetryFor = TimeSpan.FromSeconds(60); + + /// + /// The default retry interval + /// + private static readonly TimeSpan DefaultRetryInterval = TimeSpan.FromSeconds(5); + + #endregion + + #region Methods + + /// + /// Fors the specified action. + /// + /// The action. + /// The retry for. + /// The retry interval. + /// + public static async Task For(Func action, + TimeSpan? retryFor = null, + TimeSpan? retryInterval = null) + { + DateTime startTime = DateTime.Now; + Exception lastException = null; + + if (retryFor == null) + { + retryFor = Retry.DefaultRetryFor; + } + + while (DateTime.Now.Subtract(startTime).TotalMilliseconds < retryFor.Value.TotalMilliseconds) + { + try + { + await action().ConfigureAwait(false); + lastException = null; + break; + } + catch (Exception e) + { + lastException = e; + + // wait before retrying + Thread.Sleep(retryInterval ?? Retry.DefaultRetryInterval); + } + } + + if (lastException != null) + { + throw lastException; + } + } + + #endregion +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Common/SpecflowTableHelper.cs b/TransactionMobile.Maui.UiTests/Common/SpecflowTableHelper.cs new file mode 100644 index 00000000..2e5e2846 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Common/SpecflowTableHelper.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UITests.Common +{ + using TechTalk.SpecFlow; + + public static class SpecflowTableHelper + { + #region Methods + + /// + /// Gets the enum value. + /// + /// + /// The row. + /// The key. + /// + public static T GetEnumValue(TableRow row, + String key) where T : struct + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + Enum.TryParse(field, out T myEnum); + + return myEnum; + } + + /// + /// Gets the boolean value. + /// + /// The row. + /// The key. + /// + public static Boolean GetBooleanValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return bool.TryParse(field, out Boolean value) && value; + } + + /// + /// Gets the date for date string. + /// + /// The date string. + /// The today. + /// + public static DateTime GetDateForDateString(String dateString, + DateTime today) + { + switch (dateString.ToUpper()) + { + case "TODAY": + return today.Date; + case "YESTERDAY": + return today.AddDays(-1).Date; + case "LASTWEEK": + return today.AddDays(-7).Date; + case "LASTMONTH": + return today.AddMonths(-1).Date; + case "LASTYEAR": + return today.AddYears(-1).Date; + case "TOMORROW": + return today.AddDays(1).Date; + default: + return DateTime.Parse(dateString); + } + } + + /// + /// Gets the decimal value. + /// + /// The row. + /// The key. + /// + public static Decimal GetDecimalValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return decimal.TryParse(field, out Decimal value) ? value : 0; + } + + /// + /// Gets the int value. + /// + /// The row. + /// The key. + /// + public static Int32 GetIntValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + return int.TryParse(field, out Int32 value) ? value : -1; + } + + /// + /// Gets the short value. + /// + /// The row. + /// The key. + /// + public static Int16 GetShortValue(TableRow row, + String key) + { + String field = SpecflowTableHelper.GetStringRowValue(row, key); + + if (short.TryParse(field, out Int16 value)) + { + return value; + } + + return -1; + } + + /// + /// Gets the string row value. + /// + /// The row. + /// The key. + /// + public static String GetStringRowValue(TableRow row, + String key) + { + return row.TryGetValue(key, out String value) ? value : ""; + } + + #endregion + } +} diff --git a/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs b/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs new file mode 100644 index 00000000..050356fa --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Drivers/AppiumDriver.cs @@ -0,0 +1,86 @@ +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.Enums; +using OpenQA.Selenium.Appium.Service; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UiTests.Drivers +{ + public enum MobileTestPlatform + { + iOS, + Android, + Windows, + MacCatalyst + } + + public class AppiumDriverWrapper + { + public static MobileTestPlatform MobileTestPlatform; + public static AppiumDriver Driver; + + public void StartApp() + { + + var streamWriter = new StreamWriter("C:\\Temp\\Debugging.log", append:true); + try + { + + AppiumLocalService appiumService = new AppiumServiceBuilder().UsingPort(4723).Build(); + + if (appiumService.IsRunning == false) + { + appiumService.Start(); + appiumService.OutputDataReceived += (sender, args) => { streamWriter.WriteLine(args.Data); }; + } + + if (AppiumDriverWrapper.MobileTestPlatform == MobileTestPlatform.Android) + { + // Do Android stuff to start up + var driverOptions = new AppiumOptions(); + driverOptions.AddAdditionalAppiumOption("adbExecTimeout", TimeSpan.FromMinutes(5).Milliseconds); + driverOptions.AutomationName = "UIAutomator2"; + driverOptions.PlatformName = "Android"; + driverOptions.PlatformVersion = "9.0"; + driverOptions.DeviceName = "emulator-5554"; + + + // TODO: Only do this locally + //driverOptions.AddAdditionalAppiumOption(MobileCapabilityType.FullReset, true); + driverOptions.AddAdditionalAppiumOption("appPackage", "com.transactionprocessing.pos"); + //driverOptions.AddAdditionalAppiumOption("forceEspressoRebuild", true); + driverOptions.AddAdditionalAppiumOption("enforceAppInstall", true); + //driverOptions.AddAdditionalAppiumOption("noSign", true); + //driverOptions.AddAdditionalAppiumOption("espressoBuildConfig", + // "{ \"additionalAppDependencies\": [ \"com.google.android.material:material:1.0.0\", \"androidx.lifecycle:lifecycle-extensions:2.1.0\" ] }"); + + String assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + String binariesFolder = Path.Combine(assemblyFolder, "..", "..", "..", "..", + @"TransactionMobile.Maui/bin/Release/net6.0-android/publish/"); + + + var apkPath = Path.Combine(binariesFolder, "com.transactionprocessing.pos.apk"); + driverOptions.App = apkPath; + AppiumDriverWrapper.Driver = + new OpenQA.Selenium.Appium.Android.AndroidDriver(appiumService, driverOptions, + TimeSpan.FromMinutes(5)); + } + } + catch (Exception e) + { + streamWriter.Close(); + throw; + } + } + + public void StopApp() + { + AppiumDriverWrapper.Driver?.CloseApp(); + } + } +} diff --git a/TransactionMobile.Maui.UiTests/Features/Login.feature b/TransactionMobile.Maui.UiTests/Features/Login.feature new file mode 100644 index 00000000..8caee666 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Features/Login.feature @@ -0,0 +1,13 @@ +@background @login +Feature: Login + +Background: + +@PRTest +Scenario: Login as Merchant + # TODO: Set Training mode + Given I am on the Login Screen + When I enter 'merchantuser@testmerchant1.co.uk' as the Email Address + And I enter '123456' as the Password + And I tap on Login + #Then the Merchant Home Page is displayed \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Features/Login.feature.cs b/TransactionMobile.Maui.UiTests/Features/Login.feature.cs new file mode 100644 index 00000000..3b5569f9 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Features/Login.feature.cs @@ -0,0 +1,126 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (https://www.specflow.org/). +// SpecFlow Version:3.9.0.0 +// SpecFlow Generator Version:3.9.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace TransactionMobile.Maui.UiTests.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.9.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("Login")] + [NUnit.Framework.CategoryAttribute("background")] + [NUnit.Framework.CategoryAttribute("login")] + public partial class LoginFeature + { + + private TechTalk.SpecFlow.ITestRunner testRunner; + + private static string[] featureTags = new string[] { + "background", + "login"}; + +#line 1 "Login.feature" +#line hidden + + [NUnit.Framework.OneTimeSetUpAttribute()] + public virtual void FeatureSetup() + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Features", "Login", null, ProgrammingLanguage.CSharp, featureTags); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.OneTimeTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); + } + + public void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + public virtual void FeatureBackground() + { +#line 4 +#line hidden + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Login as Merchant")] + [NUnit.Framework.CategoryAttribute("PRTest")] + public void LoginAsMerchant() + { + string[] tagsOfScenario = new string[] { + "PRTest"}; + System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Login as Merchant", null, tagsOfScenario, argumentsOfScenario, featureTags); +#line 7 +this.ScenarioInitialize(scenarioInfo); +#line hidden + if ((TagHelper.ContainsIgnoreTag(tagsOfScenario) || TagHelper.ContainsIgnoreTag(featureTags))) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 4 +this.FeatureBackground(); +#line hidden +#line 9 + testRunner.Given("I am on the Login Screen", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line hidden +#line 10 + testRunner.When("I enter \'merchantuser@testmerchant1.co.uk\' as the Email Address", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 11 + testRunner.And("I enter \'123456\' as the Password", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden +#line 12 + testRunner.And("I tap on Login", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/TransactionMobile.Maui.UiTests/Features/LoginFeature.cs b/TransactionMobile.Maui.UiTests/Features/LoginFeature.cs new file mode 100644 index 00000000..5c16cc8a --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Features/LoginFeature.cs @@ -0,0 +1,15 @@ +using TransactionMobile.Maui.UITests.Common; +using TransactionMobile.Maui.UiTests.Drivers; + +namespace TransactionMobile.Maui.UiTests.Features; + +using NUnit.Framework; + +[TestFixture(MobileTestPlatform.Android, Category = "Android")] +public partial class LoginFeature : BaseTestFixture +{ + public LoginFeature(MobileTestPlatform mobileTestPlatform) + : base(mobileTestPlatform) + { + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Hooks/AppiumHooks.cs b/TransactionMobile.Maui.UiTests/Hooks/AppiumHooks.cs new file mode 100644 index 00000000..52b2b381 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Hooks/AppiumHooks.cs @@ -0,0 +1,35 @@ +using OpenQA.Selenium.Appium; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TechTalk.SpecFlow; +using TransactionMobile.Maui.UiTests.Drivers; + +namespace TransactionMobile.Maui.UiTests.Hooks +{ + [Binding] + public class AppiumHooks + { + private readonly AppiumDriverWrapper _appiumDriver; + + public AppiumHooks(AppiumDriverWrapper appiumDriver) + { + _appiumDriver = appiumDriver; + AppiumDriverWrapper.MobileTestPlatform = MobileTestPlatform.Android; + } + + [BeforeScenario()] + public void StartApp() + { + _appiumDriver.StartApp(); + } + + [AfterScenario()] + public void ShutdownApp() + { + _appiumDriver.StopApp(); + } + } +} diff --git a/TransactionMobile.Maui.UiTests/Pages/BasePage.cs b/TransactionMobile.Maui.UiTests/Pages/BasePage.cs new file mode 100644 index 00000000..ba9270d7 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Pages/BasePage.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TransactionMobile.Maui.UiTests.Drivers; + +namespace TransactionMobile.Maui.UITests +{ + using Common; + using OpenQA.Selenium; + using Shouldly; + + public abstract class BasePage + { + protected abstract String Trait { get; } + + public async Task AssertOnPage(TimeSpan? timeout = null) + { + timeout = timeout ?? TimeSpan.FromSeconds(60); + + await Retry.For(async () => + { + String message = "Unable to verify on page: " + this.GetType().Name; + + Should.NotThrow(() => this.WaitForElementByAccessibilityId(this.Trait,"Label"), message); + }, + TimeSpan.FromMinutes(1), + timeout).ConfigureAwait(false); + + using (StreamWriter sw = new StreamWriter("C:\\Temp\\PageSource.log")) + { + sw.WriteLine(AppiumDriverWrapper.Driver.PageSource); + } + } + + /// + /// Verifies that the trait is no longer present. Defaults to a 5 second wait. + /// + /// Time to wait before the assertion fails + public void WaitForPageToLeave(TimeSpan? timeout = null) + { + timeout = timeout ?? TimeSpan.FromSeconds(5); + var message = "Unable to verify *not* on page: " + this.GetType().Name; + + Should.NotThrow(() => this.WaitForNoElementByAccessibilityId(this.Trait), message); + } + + public async Task WaitForElementByAccessibilityId(String accessibilityId, String type,TimeSpan? timeout = null) + { + return await AppiumDriverWrapper.Driver.WaitForElementByAccessibilityId(accessibilityId,type, timeout); + } + + public async Task GetPageSource() + { + return await AppiumDriverWrapper.Driver.GetPageSource(); + } + + public async Task WaitForNoElementByAccessibilityId(String accessibilityId) + { + await AppiumDriverWrapper.Driver.WaitForNoElementByAccessibilityId(accessibilityId); + } + + public async Task WaitForToastMessage(String toastMessage) + { + await AppiumDriverWrapper.Driver.WaitForToastMessage(AppiumDriverWrapper.MobileTestPlatform, toastMessage); + } + + public void HideKeyboard() + { + AppiumDriverWrapper.Driver.HideKeyboard(); + } + + public IWebElement GetAlert() + { + return AppiumDriverWrapper.Driver.FindElement(By.Name("OK")); + } + + public IAlert SwitchToAlert() + { + return AppiumDriverWrapper.Driver.SwitchTo().Alert(); + } + + public void NavigateBack() + { + AppiumDriverWrapper.Driver.Navigate().Back(); + } + } +} diff --git a/TransactionMobile.Maui.UiTests/Pages/Extenstions.cs b/TransactionMobile.Maui.UiTests/Pages/Extenstions.cs new file mode 100644 index 00000000..57a8645c --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Pages/Extenstions.cs @@ -0,0 +1,115 @@ +using OpenQA.Selenium.Appium; +using TransactionMobile.Maui.UiTests.Drivers; + +namespace TransactionMobile.Maui.UITests; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Common; +using OpenQA.Selenium; +using OpenQA.Selenium.Appium.Android; +using OpenQA.Selenium.Appium.iOS; +using Shouldly; + +public static class Extenstions +{ + // TODO: Mac & Windows Extensions + // TODO: May need a platform switch + //public static AndroidElement GetAlert(this AndroidDriver driver) + //{ + // return driver.FindElementByClassName("androidx.appcompat.widget.AppCompatTextView"); + //} + + public static async Task WaitForElementByAccessibilityId(this AppiumDriver driver, + String selector, + String type, + TimeSpan? timeout = null) + { + + timeout ??= TimeSpan.FromSeconds(60); + IWebElement element = null; + await Retry.For(async () => + { + /* + String className = "Button"; + if (type == "Label") + { + className = "TextView"; + } + else if (type == "Entry") + { + className = "EditText"; + } + + var elements = driver.FindElements(By.ClassName($"android.widget.{className}")); + element = driver.FindElement(MobileBy.AccessibilityId(selector)); + var x = elements[0]; + var y = elements[1]; + */ + element = driver.FindElement(MobileBy.AccessibilityId(selector)); + element.ShouldNotBeNull(); + // var pageSource = driver.PageSource; + }); + + return element; + } + + public static async Task WaitForNoElementByAccessibilityId(this AppiumDriver driver, + String selector, + TimeSpan? timeout = null) + { + timeout ??= TimeSpan.FromSeconds(60); + + await Retry.For(async () => + { + IWebElement? element = driver.FindElement(MobileBy.AccessibilityId(selector)); + element.ShouldBeNull(); + }); + + } + + public static async Task WaitForToastMessage(this AppiumDriver driver, MobileTestPlatform platform, String expectedToast) + { + if (platform == MobileTestPlatform.Android) + { + await Retry.For(async () => + { + Dictionary args = new Dictionary + { + {"text", expectedToast}, + {"isRegexp", false} + }; + driver.ExecuteScript("mobile: isToastVisible", args); + + }); + } + else if (platform == MobileTestPlatform.iOS) + { + Boolean isDisplayed = false; + int count = 0; + do + { + if (driver.PageSource.Contains(expectedToast)) + { + Console.WriteLine(driver.PageSource); + isDisplayed = true; + break; + } + + Thread.Sleep(200); //Add your custom wait if exists + count++; + + } while (count < 10); + + Console.WriteLine(driver.PageSource); + isDisplayed.ShouldBeTrue(); + } + } + + public static async Task GetPageSource(this AppiumDriver driver) + { + return driver.PageSource; + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs b/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs new file mode 100644 index 00000000..9440d574 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Pages/LoginPage.cs @@ -0,0 +1,47 @@ +using TransactionMobile.Maui.UiTests.Drivers; + +namespace TransactionMobile.Maui.UITests; + +using System; +using System.Threading.Tasks; +using OpenQA.Selenium; + +public class LoginPage : BasePage +{ + protected override String Trait => "LoginLabel"; + + private readonly String UserNameEntry; + private readonly String PasswordEntry; + private readonly String LoginButton; + //private readonly String TestModeButton; + //private readonly String ErrorLabel; + + public LoginPage() + { + this.UserNameEntry = "UserNameEntry"; + this.PasswordEntry = "PasswordEntry"; + this.LoginButton = "LoginButton"; + //this.TestModeButton = "TestModeButton"; + //this.ErrorLabel = "ErrorLabel"; + } + + public async Task EnterEmailAddress(String emailAddress) + { + IWebElement element = await this.WaitForElementByAccessibilityId(this.UserNameEntry,"Entry"); + + element.SendKeys(emailAddress); + } + + public async Task EnterPassword(String password) + { + IWebElement element = await this.WaitForElementByAccessibilityId(this.PasswordEntry,"Entry"); + element.SendKeys(password); + } + + public async Task ClickLoginButton() + { + this.HideKeyboard(); + //IWebElement element = await this.WaitForElementByAccessibilityId(this.LoginButton); + //element.Click(); + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Pages/MainPage.cs b/TransactionMobile.Maui.UiTests/Pages/MainPage.cs new file mode 100644 index 00000000..472c283d --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Pages/MainPage.cs @@ -0,0 +1,71 @@ +namespace TransactionMobile.Maui.UITests; + +using System; +using System.Threading.Tasks; + +public class MainPage : BasePage +{ + protected override String Trait => "Home"; + + private readonly String TransactionsButton; + + private readonly String ReportsButton; + + private readonly String ProfileButton; + + private readonly String SupportButton; + + private readonly String AvailableBalanceLabel; + + /// + /// Initializes a new instance of the class. + /// + public MainPage() + { + this.TransactionsButton = "TransactionsButton"; + this.ReportsButton = "ReportsButton"; + this.ProfileButton = "ProfileButton"; + this.SupportButton = "SupportButton"; + this.AvailableBalanceLabel = "AvailableBalanceValueLabel"; + } + + public async Task ClickTransactionsButton() + { + //var element = await this.WaitForElementByAccessibilityId(this.TransactionsButton); + //element.Click(); + } + + public async Task ClickReportsButton() + { + //var element = await this.WaitForElementByAccessibilityId(this.ReportsButton); + //element.Click(); + } + + public void ClickProfileButton() + { + //app.WaitForElement(this.ProfileButton); + //app.Tap(this.ProfileButton); + } + + public void ClickSupportButton() + { + //app.WaitForElement(this.SupportButton); + //app.Tap(this.SupportButton); + } + + public async Task GetAvailableBalanceValue(TimeSpan? timeout = default(TimeSpan?)) + { + //await this.ScrollTo(this.Trait, this.AvailableBalanceLabel); + //var element = await this.WaitForElementByAccessibilityId(this.AvailableBalanceLabel, timeout: TimeSpan.FromSeconds(30)); + + //String availableBalanceText = element.Text.Replace(" KES", String.Empty); + + //if (Decimal.TryParse(availableBalanceText, out Decimal balanceValue) == false) + //{ + // throw new Exception($"Failed to parse [{availableBalanceText}] as a Decimal"); + //} + + //return balanceValue; + return 0; + } +} \ No newline at end of file diff --git a/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs b/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs new file mode 100644 index 00000000..be2a805d --- /dev/null +++ b/TransactionMobile.Maui.UiTests/Steps/LoginSteps.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TransactionMobile.Maui.UITests.Steps +{ + using TechTalk.SpecFlow; + + [Binding] + [Scope(Tag = "login")] + public class LoginSteps + { + LoginPage loginPage = new LoginPage(); + //MainPage mainPage = new MainPage(); + + [Given(@"I am on the Login Screen")] + public async Task GivenIAmOnTheLoginScreen() + { + await this.loginPage.AssertOnPage(); + } + + [When(@"I enter '(.*)' as the Email Address")] + public async Task WhenIEnterAsTheEmailAddress(String emailAddress) + { + await this.loginPage.EnterEmailAddress(emailAddress); + } + + [When(@"I enter '(.*)' as the Password")] + public async Task WhenIEnterAsThePassword(String password) + { + await this.loginPage.EnterPassword(password); + } + + [When(@"I tap on Login")] + public async Task WhenITapOnLogin() + { + await this.loginPage.ClickLoginButton(); + } + + [Then(@"the Merchant Home Page is displayed")] + public async Task ThenTheMerchantHomePageIsDisplayed() + { + //await this.mainPage.AssertOnPage(); + } + + [Then(@"the available balance is shown as (.*)")] + public async Task ThenTheAvailableBalanceIsShownAs(Decimal expectedAvailableBalance) + { + //Decimal availableBalance = await this.mainPage.GetAvailableBalanceValue(TimeSpan.FromSeconds(120)).ConfigureAwait(false); + //availableBalance.ShouldBe(expectedAvailableBalance); + } + } +} diff --git a/TransactionMobile.Maui.UiTests/TransactionMobile.Maui.UiTests.csproj b/TransactionMobile.Maui.UiTests/TransactionMobile.Maui.UiTests.csproj new file mode 100644 index 00000000..6f8ea286 --- /dev/null +++ b/TransactionMobile.Maui.UiTests/TransactionMobile.Maui.UiTests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + diff --git a/TransactionMobile.Maui.sln b/TransactionMobile.Maui.sln index 5a1818ea..95de4fa9 100644 --- a/TransactionMobile.Maui.sln +++ b/TransactionMobile.Maui.sln @@ -18,10 +18,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Nuget.config = Nuget.config EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TransactionMobile.Maui.UiTests", "TransactionMobile.Maui.UiTests\TransactionMobile.Maui.UiTests.csproj", "{1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + TestAuomation|Any CPU = TestAuomation|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {73668181-7A26-435D-83E3-CF141AC8FD0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -30,14 +33,27 @@ Global {73668181-7A26-435D-83E3-CF141AC8FD0B}.Release|Any CPU.ActiveCfg = Release|Any CPU {73668181-7A26-435D-83E3-CF141AC8FD0B}.Release|Any CPU.Build.0 = Release|Any CPU {73668181-7A26-435D-83E3-CF141AC8FD0B}.Release|Any CPU.Deploy.0 = Release|Any CPU + {73668181-7A26-435D-83E3-CF141AC8FD0B}.TestAuomation|Any CPU.ActiveCfg = Release|Any CPU + {73668181-7A26-435D-83E3-CF141AC8FD0B}.TestAuomation|Any CPU.Build.0 = Release|Any CPU + {73668181-7A26-435D-83E3-CF141AC8FD0B}.TestAuomation|Any CPU.Deploy.0 = Release|Any CPU {0894F054-5C4D-4DDD-A8E9-636416189234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0894F054-5C4D-4DDD-A8E9-636416189234}.Debug|Any CPU.Build.0 = Debug|Any CPU {0894F054-5C4D-4DDD-A8E9-636416189234}.Release|Any CPU.ActiveCfg = Release|Any CPU {0894F054-5C4D-4DDD-A8E9-636416189234}.Release|Any CPU.Build.0 = Release|Any CPU + {0894F054-5C4D-4DDD-A8E9-636416189234}.TestAuomation|Any CPU.ActiveCfg = Release|Any CPU + {0894F054-5C4D-4DDD-A8E9-636416189234}.TestAuomation|Any CPU.Build.0 = Release|Any CPU {902D54CF-CD5F-4932-B1DC-01A3937AC054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {902D54CF-CD5F-4932-B1DC-01A3937AC054}.Debug|Any CPU.Build.0 = Debug|Any CPU {902D54CF-CD5F-4932-B1DC-01A3937AC054}.Release|Any CPU.ActiveCfg = Release|Any CPU {902D54CF-CD5F-4932-B1DC-01A3937AC054}.Release|Any CPU.Build.0 = Release|Any CPU + {902D54CF-CD5F-4932-B1DC-01A3937AC054}.TestAuomation|Any CPU.ActiveCfg = Release|Any CPU + {902D54CF-CD5F-4932-B1DC-01A3937AC054}.TestAuomation|Any CPU.Build.0 = Release|Any CPU + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}.Release|Any CPU.Build.0 = Release|Any CPU + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}.TestAuomation|Any CPU.ActiveCfg = Debug|Any CPU + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9}.TestAuomation|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -46,6 +62,7 @@ Global {73668181-7A26-435D-83E3-CF141AC8FD0B} = {1CBEF4C1-7D90-4A78-AA55-D81F1447A70E} {0894F054-5C4D-4DDD-A8E9-636416189234} = {AB312EE3-CBA4-469A-8694-67C5466298C5} {902D54CF-CD5F-4932-B1DC-01A3937AC054} = {1CBEF4C1-7D90-4A78-AA55-D81F1447A70E} + {1F7CE7A2-6350-4F8A-B758-5E275C9C88C9} = {AB312EE3-CBA4-469A-8694-67C5466298C5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572} diff --git a/TransactionMobile.Maui/App.xaml.cs b/TransactionMobile.Maui/App.xaml.cs index d4fd107f..cb420694 100644 --- a/TransactionMobile.Maui/App.xaml.cs +++ b/TransactionMobile.Maui/App.xaml.cs @@ -1,4 +1,5 @@ -using TransactionMobile.Maui.Pages.Reports; +using Microsoft.Maui.Platform; +using TransactionMobile.Maui.Pages.Reports; using TransactionMobile.Maui.Pages.Transactions.MobileTopup; using TransactionMobile.Maui.Pages.Transactions.Voucher; @@ -15,7 +16,7 @@ public App() { InitializeComponent(); - Microsoft.Maui.Handlers.EntryHandler.ElementMapper.AppendToMapping("TrainingMode", (handler, view) => + Microsoft.Maui.Handlers.LabelHandler.ElementMapper.AppendToMapping("TrainingMode", (handler, view) => { if (view is TitleLabel) { @@ -30,6 +31,42 @@ public App() } }); + Microsoft.Maui.Handlers.EntryHandler.ElementMapper.AppendToMapping("AutomationIdEntry", (handler, view) => + { + if (view is Entry) + { + var e = (Entry) view; + if (String.IsNullOrEmpty(e.AutomationId) == false) + { + SemanticProperties.SetHint(e,e.AutomationId); + } + } + }); + + Microsoft.Maui.Handlers.LabelHandler.ElementMapper.AppendToMapping("AutomationIdLabel", (handler, view) => + { + if (view is Label) + { + var e = (Label) view; + if (String.IsNullOrEmpty(e.AutomationId) == false) + { + SemanticProperties.SetDescription(e, e.AutomationId); + } + } + }); + + Microsoft.Maui.Handlers.ButtonHandler.ElementMapper.AppendToMapping("AutomationIdButton", (handler, view) => + { + if (view is Button) + { + var e = (Button) view; + if (String.IsNullOrEmpty(e.AutomationId) == false) + { + SemanticProperties.SetDescription(e, e.AutomationId); + } + } + }); + MainPage = new AppShell(); Routing.RegisterRoute(nameof(MobileTopupSelectOperatorPage), typeof(MobileTopupSelectOperatorPage)); diff --git a/TransactionMobile.Maui/MauiProgram.cs b/TransactionMobile.Maui/MauiProgram.cs index c8d6597b..9a1dc567 100644 --- a/TransactionMobile.Maui/MauiProgram.cs +++ b/TransactionMobile.Maui/MauiProgram.cs @@ -21,7 +21,6 @@ public static MauiApp CreateMauiApp() Platforms.Services.DangerousTrustProvider.Register(); #endif - //raw.SetProvider(new SQLite3Provider_sqlite3()); Builder = MauiApp.CreateBuilder(); Builder.UseMauiApp() .ConfigureRequestHandlers() diff --git a/TransactionMobile.Maui/Pages/LoginPage.xaml b/TransactionMobile.Maui/Pages/LoginPage.xaml index 876d172b..f9fc5adc 100644 --- a/TransactionMobile.Maui/Pages/LoginPage.xaml +++ b/TransactionMobile.Maui/Pages/LoginPage.xaml @@ -12,7 +12,8 @@ -