From b853c513085e79762b354d49435edf67f2dfee2c Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 12:10:23 +0100 Subject: [PATCH 01/12] nuget upates --- Directory.Packages.props | 60 +++++------ .../Common/DockerHelper.cs | 20 +++- .../Features/Reporting.feature.cs | 102 +++++++++--------- .../Steps/SharedSteps.cs | 5 +- 4 files changed, 102 insertions(+), 85 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fc5b0573..3d599a95 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,23 +3,23 @@ true - + - - + + - - - - - - - - - - + + + + + + + + + + @@ -28,24 +28,24 @@ - - - - - + + + + + - - - + + + - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/TransactionProcessor.Mobile.UITests/Common/DockerHelper.cs b/TransactionProcessor.Mobile.UITests/Common/DockerHelper.cs index 1143277c..112ccaaf 100644 --- a/TransactionProcessor.Mobile.UITests/Common/DockerHelper.cs +++ b/TransactionProcessor.Mobile.UITests/Common/DockerHelper.cs @@ -12,6 +12,7 @@ using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; +using TestHosts.Clients; using TransactionProcessor.Client; using TransactionProcessor.DataTransferObjects.Responses.Contract; using TransactionProcessor.IntegrationTesting.Helpers; @@ -42,6 +43,8 @@ public class DockerHelper : global::Shared.IntegrationTesting.TestContainers.Doc private readonly TestingContext TestingContext; + public IAgencyBankingClient AgencyBankingClient; + #endregion public EventStoreProjectionManagementClient ProjectionManagementClient; @@ -119,6 +122,16 @@ Object Deserialise(String arg, Type type) return StringSerialiser.DeserializeObject(arg, type, new SerialiserOptions(SerialiserPropertyFormat.SnakeCase)); } + String Serialise_CamelCase(Object arg) + { + return StringSerialiser.Serialise(arg, new SerialiserOptions(SerialiserPropertyFormat.CamelCase)); + } + + Object Deserialise_CamelCase(String arg, Type type) + { + return StringSerialiser.DeserializeObject(arg, type, new SerialiserOptions(SerialiserPropertyFormat.CamelCase)); + } + /// /// Starts the containers for scenario run. /// @@ -164,7 +177,8 @@ public override async Task StartContainersForScenarioRun(String scenarioName, Do this.TestHostHttpClient = new HttpClient(clientHandler); this.TestHostHttpClient.BaseAddress = new Uri($"http://127.0.0.1:{this.TestHostServicePort}"); - + this.AgencyBankingClient = new AgencyBankingClient(TestHostServiceBaseAddressResolver, httpClient, Serialise_CamelCase, this.Deserialise_CamelCase); + this.ProjectionManagementClient = new EventStoreProjectionManagementClient(this.ConfigureEventStoreSettings()); } @@ -173,7 +187,9 @@ public override async Task StartContainersForScenarioRun(String scenarioName, Do public String TransactionProcessorAclBaseAddressResolver(String api) => $"http://127.0.0.1:{this.TransactionProcessorAclPort}"; public String SecurityServiceBaseAddressResolver(String api) => $"https://127.0.0.1:{this.SecurityServicePort}"; - + + public String TestHostServiceBaseAddressResolver(String api) => $"http://127.0.0.1:{this.TestHostServicePort}"; + public const int ConfigHostDockerPort = 9200; public String ConfigHostContainerName; diff --git a/TransactionProcessor.Mobile.UITests/Features/Reporting.feature.cs b/TransactionProcessor.Mobile.UITests/Features/Reporting.feature.cs index 24b3f895..a10cab53 100644 --- a/TransactionProcessor.Mobile.UITests/Features/Reporting.feature.cs +++ b/TransactionProcessor.Mobile.UITests/Features/Reporting.feature.cs @@ -125,119 +125,119 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa { #line 4 #line hidden - global::Reqnroll.Table table1 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table25 = new global::Reqnroll.Table(new string[] { "Role Name"}); - table1.AddRow(new string[] { + table25.AddRow(new string[] { "Merchant"}); #line 6 - await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table1, "Given "); + await testRunner.GivenAsync("the following security roles exist", ((string)(null)), table25, "Given "); #line hidden - global::Reqnroll.Table table2 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table26 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Description"}); - table2.AddRow(new string[] { + table26.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST Scope", "A scope for Transaction Processor REST"}); - table2.AddRow(new string[] { + table26.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST Scope", "A scope for Transaction Processor ACL REST"}); #line 10 - await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table2, "Given "); + await testRunner.GivenAsync("I create the following api scopes", ((string)(null)), table26, "Given "); #line hidden - global::Reqnroll.Table table3 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table27 = new global::Reqnroll.Table(new string[] { "Name", "DisplayName", "Secret", "Scopes", "UserClaims"}); - table3.AddRow(new string[] { + table27.AddRow(new string[] { "transactionProcessor", "Transaction Processor REST", "Secret1", "transactionProcessor", "merchantId, estateId, role"}); - table3.AddRow(new string[] { + table27.AddRow(new string[] { "transactionProcessorACL", "Transaction Processor ACL REST", "Secret1", "transactionProcessorACL", "merchantId, estateId, role"}); #line 15 - await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table3, "Given "); + await testRunner.GivenAsync("the following api resources exist", ((string)(null)), table27, "Given "); #line hidden - global::Reqnroll.Table table4 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table28 = new global::Reqnroll.Table(new string[] { "ClientId", "ClientName", "Secret", "Scopes", "GrantTypes"}); - table4.AddRow(new string[] { + table28.AddRow(new string[] { "serviceClient", "Service Client", "Secret1", "transactionProcessor,transactionProcessorACL", "client_credentials"}); - table4.AddRow(new string[] { + table28.AddRow(new string[] { "mobileAppClient", "Mobile App Client", "Secret1", "transactionProcessorACL,transactionProcessor", "password"}); #line 20 - await testRunner.GivenAsync("the following clients exist", ((string)(null)), table4, "Given "); + await testRunner.GivenAsync("the following clients exist", ((string)(null)), table28, "Given "); #line hidden - global::Reqnroll.Table table5 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table29 = new global::Reqnroll.Table(new string[] { "ClientId"}); - table5.AddRow(new string[] { + table29.AddRow(new string[] { "serviceClient"}); #line 25 await testRunner.GivenAsync("I have a token to access the estate management and transaction processor acl reso" + - "urces", ((string)(null)), table5, "Given "); + "urces", ((string)(null)), table29, "Given "); #line hidden - global::Reqnroll.Table table6 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table30 = new global::Reqnroll.Table(new string[] { "EstateName"}); - table6.AddRow(new string[] { + table30.AddRow(new string[] { "Test Estate 1"}); #line 29 - await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table6, "Given "); + await testRunner.GivenAsync("I have created the following estates", ((string)(null)), table30, "Given "); #line hidden - global::Reqnroll.Table table7 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table31 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "RequireCustomMerchantNumber", "RequireCustomTerminalNumber"}); - table7.AddRow(new string[] { + table31.AddRow(new string[] { "Test Estate 1", "Safaricom", "True", "True"}); #line 33 - await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table7, "Given "); + await testRunner.GivenAsync("I have created the following operators", ((string)(null)), table31, "Given "); #line hidden - global::Reqnroll.Table table8 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table32 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName"}); - table8.AddRow(new string[] { + table32.AddRow(new string[] { "Test Estate 1", "Safaricom"}); #line 37 - await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table8, "And "); + await testRunner.AndAsync("I have assigned the following operators to the estates", ((string)(null)), table32, "And "); #line hidden - global::Reqnroll.Table table9 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table33 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription"}); - table9.AddRow(new string[] { + table33.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract"}); #line 41 - await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table9, "Given "); + await testRunner.GivenAsync("I create a contract with the following values", ((string)(null)), table33, "Given "); #line hidden - global::Reqnroll.Table table10 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table34 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -245,7 +245,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "DisplayText", "Value", "ProductType"}); - table10.AddRow(new string[] { + table34.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -254,9 +254,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "", "MobileTopup"}); #line 45 - await testRunner.WhenAsync("I create the following Products", ((string)(null)), table10, "When "); + await testRunner.WhenAsync("I create the following Products", ((string)(null)), table34, "When "); #line hidden - global::Reqnroll.Table table11 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table35 = new global::Reqnroll.Table(new string[] { "EstateName", "OperatorName", "ContractDescription", @@ -264,7 +264,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "CalculationType", "FeeDescription", "Value"}); - table11.AddRow(new string[] { + table35.AddRow(new string[] { "Test Estate 1", "Safaricom", "Safaricom Contract", @@ -273,9 +273,9 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Merchant Commission", "2.50"}); #line 49 - await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table11, "When "); + await testRunner.WhenAsync("I add the following Transaction Fees", ((string)(null)), table35, "When "); #line hidden - global::Reqnroll.Table table12 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table36 = new global::Reqnroll.Table(new string[] { "MerchantName", "AddressLine1", "AddressLine2", @@ -288,7 +288,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "ContactName", "EmailAddress", "EstateName"}); - table12.AddRow(new string[] { + table36.AddRow(new string[] { "Test Merchant 1", "test address line 1", "test address line 2", @@ -302,51 +302,51 @@ await testRunner.GivenAsync("I have a token to access the estate management and "testcontact1@merchant1.co.uk", "Test Estate 1"}); #line 53 - await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table12, "Given "); + await testRunner.GivenAsync("I create the following merchants", ((string)(null)), table36, "Given "); #line hidden - global::Reqnroll.Table table13 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table37 = new global::Reqnroll.Table(new string[] { "OperatorName", "MerchantName", "MerchantNumber", "TerminalNumber", "EstateName"}); - table13.AddRow(new string[] { + table37.AddRow(new string[] { "Safaricom", "Test Merchant 1", "00000001", "10000001", "Test Estate 1"}); #line 57 - await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table13, "Given "); + await testRunner.GivenAsync("I have assigned the following operator to the merchants", ((string)(null)), table37, "Given "); #line hidden - global::Reqnroll.Table table14 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table38 = new global::Reqnroll.Table(new string[] { "MerchantName", "EstateName"}); - table14.AddRow(new string[] { + table38.AddRow(new string[] { "Test Merchant 1", "Test Estate 1"}); #line 61 - await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table14, "Given "); + await testRunner.GivenAsync("I have assigned the following devices to the merchants", ((string)(null)), table38, "Given "); #line hidden - global::Reqnroll.Table table15 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table39 = new global::Reqnroll.Table(new string[] { "EstateName", "MerchantName", "ContractDescription"}); - table15.AddRow(new string[] { + table39.AddRow(new string[] { "Test Estate 1", "Test Merchant 1", "Safaricom Contract"}); #line 65 - await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table15, "When "); + await testRunner.WhenAsync("I add the following contracts to the following merchants", ((string)(null)), table39, "When "); #line hidden - global::Reqnroll.Table table16 = new global::Reqnroll.Table(new string[] { + global::Reqnroll.Table table40 = new global::Reqnroll.Table(new string[] { "EmailAddress", "Password", "GivenName", "FamilyName", "EstateName", "MerchantName"}); - table16.AddRow(new string[] { + table40.AddRow(new string[] { "user1", "123456", "TestMerchant", @@ -354,7 +354,7 @@ await testRunner.GivenAsync("I have a token to access the estate management and "Test Estate 1", "Test Merchant 1"}); #line 69 - await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table16, "Given "); + await testRunner.GivenAsync("I have created the following security users", ((string)(null)), table40, "Given "); #line hidden #line 73 await testRunner.GivenAsync("I have created a config for my device", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); diff --git a/TransactionProcessor.Mobile.UITests/Steps/SharedSteps.cs b/TransactionProcessor.Mobile.UITests/Steps/SharedSteps.cs index 1f2da087..7889c146 100644 --- a/TransactionProcessor.Mobile.UITests/Steps/SharedSteps.cs +++ b/TransactionProcessor.Mobile.UITests/Steps/SharedSteps.cs @@ -46,7 +46,8 @@ public SharedSteps(TestingContext testingContext) { this.loginPage = new LoginPage(testingContext); this.SecurityServiceSteps = new SecurityServiceSteps(testingContext.DockerHelper.SecurityServiceClient); this.TransactionProcessorSteps = new TransactionProcessorSteps(this.TestingContext.DockerHelper.TransactionProcessorClient, this.TestingContext.DockerHelper.TestHostHttpClient, - this.TestingContext.DockerHelper.ProjectionManagementClient); + this.TestingContext.DockerHelper.ProjectionManagementClient, + this.TestingContext.DockerHelper.AgencyBankingClient); } [Given(@"the following security roles exist")] @@ -168,7 +169,7 @@ public async Task WhenIAddTheFollowingTransactionFees(DataTable table) public async Task GivenICreateTheFollowingMerchants(DataTable table) { var estates = this.TestingContext.Estates.Select(e => e).ToList(); - List<(EstateDetails estate, CreateMerchantRequest)> requests = table.Rows.ToCreateMerchantRequests(estates); + var requests = table.Rows.ToCreateMerchantRequests(estates); List verifiedMerchants = await this.TransactionProcessorSteps.WhenICreateTheFollowingMerchants(this.TestingContext.AccessToken, requests); From cc75d8c9da1f6473b71ced1b9e9c3afeed45c34f Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 13:28:11 +0100 Subject: [PATCH 02/12] Add MTP test runner config --- global.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 00000000..3140116d --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} From 32b10862de40519f2937cb9c8c5be622be8b8d91 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 13:47:30 +0100 Subject: [PATCH 03/12] Enable NUnit runner for UI tests --- .../TransactionProcessor.Mobile.UITests.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj b/TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj index 2ef862ac..ce8c1538 100644 --- a/TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj +++ b/TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj @@ -6,6 +6,8 @@ enable enable false + true + Exe From df87db2b11ed762640770c413f65b51ef1c2ce8b Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 14:08:29 +0100 Subject: [PATCH 04/12] Fix UI test workflow filters --- .github/workflows/android_e2e_tests.yml | 2 +- .github/workflows/android_navigation_tests.yml | 4 ++-- .github/workflows/windows_e2e_tests.yml | 2 +- .github/workflows/windows_navigation_tests.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/android_e2e_tests.yml b/.github/workflows/android_e2e_tests.yml index 7c7327dd..22de3114 100644 --- a/.github/workflows/android_e2e_tests.yml +++ b/.github/workflows/android_e2e_tests.yml @@ -155,7 +155,7 @@ jobs: run: dotnet build TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj -f net10.0-android -c Release --no-restore - name: Run Android Navigation Tests - run: dotnet test TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "(Category=PRTest)&(Category=Android)" --no-restore + run: dotnet test TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "FullyQualifiedName~EndToEndTestsFeature&Category=Android|FullyQualifiedName~ReportingFeature" --no-restore - name: Upload Logs on Failure if: always() diff --git a/.github/workflows/android_navigation_tests.yml b/.github/workflows/android_navigation_tests.yml index cdf470f3..82be6004 100644 --- a/.github/workflows/android_navigation_tests.yml +++ b/.github/workflows/android_navigation_tests.yml @@ -142,7 +142,7 @@ jobs: run: dotnet build TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj -f net10.0-android -c Release --no-restore - name: Run Android Navigation Tests - run: dotnet test TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "(Category=PRNavTest)&(Category=Android)" --no-restore + run: dotnet test TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "FullyQualifiedName~PageNavigationFeature&Category=Android" --no-restore - name: Upload Appium Logs on Failure if: always() @@ -287,7 +287,7 @@ jobs: run: dotnet build TransactionProcessor.Mobile/TransactionProcessor.Mobile.csproj -f net10.0-android -c Release --no-restore - name: Run Android Navigation Tests - run: dotnet test TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "(Category=PRHWNavTest)&(Category=Android)" --no-restore + run: dotnet test TransactionProcessor.Mobile.UITests/TransactionProcessor.Mobile.UITests.csproj --filter "FullyQualifiedName~HardwarePageNavigationFeature&Category=Android" --no-restore - name: Upload Appium Logs on Failure if: always() diff --git a/.github/workflows/windows_e2e_tests.yml b/.github/workflows/windows_e2e_tests.yml index 44379f3e..06f95d51 100644 --- a/.github/workflows/windows_e2e_tests.yml +++ b/.github/workflows/windows_e2e_tests.yml @@ -72,7 +72,7 @@ jobs: } - name: Run Windows End To End Tests - run: dotnet test TransactionProcessor.Mobile.UiTests/TransactionProcessor.Mobile.UiTests.csproj --filter "Category=PRTest&Category=Windows" --no-restore + run: dotnet test TransactionProcessor.Mobile.UiTests/TransactionProcessor.Mobile.UiTests.csproj --filter "FullyQualifiedName~EndToEndTestsFeature&Category=Windows|FullyQualifiedName~ReportingFeature" --no-restore #run: dotnet test TransactionProcessor.Mobile.UiTests/TransactionProcessor.Mobile.UiTests.csproj --list-tests -v:d - name: Upload Logs on Failure diff --git a/.github/workflows/windows_navigation_tests.yml b/.github/workflows/windows_navigation_tests.yml index eece6866..65397155 100644 --- a/.github/workflows/windows_navigation_tests.yml +++ b/.github/workflows/windows_navigation_tests.yml @@ -87,7 +87,7 @@ jobs: } - name: Run Windows Navigation Tests - run: dotnet test TransactionProcessor.Mobile.UiTests/TransactionProcessor.Mobile.UiTests.csproj --filter "(Category=PRNavTest)&(Category=Windows)" --no-restore + run: dotnet test TransactionProcessor.Mobile.UiTests/TransactionProcessor.Mobile.UiTests.csproj --filter "FullyQualifiedName~PageNavigationFeature&Category=Windows" --no-restore - name: Upload Appium Logs on Failure if: always() From ba7689dccd9b2bbbc15cd68650b6580f264b9fc2 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 14:56:31 +0100 Subject: [PATCH 05/12] Stabilize Android device serial lookup --- TransactionProcessor.Mobile.UITests/Pages/LoginPage.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/TransactionProcessor.Mobile.UITests/Pages/LoginPage.cs b/TransactionProcessor.Mobile.UITests/Pages/LoginPage.cs index bc77fe22..d001d50a 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/LoginPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/LoginPage.cs @@ -27,6 +27,12 @@ public LoginPage(TestingContext testingContext) : base(testingContext) } public async Task GetDeviceSerial() { + if (AppiumDriverWrapper.MobileTestPlatform == MobileTestPlatform.Android) + { + // Android returns a fixed test identifier from the app's device service. + return "stagingmerchant1device"; + } + IWebElement element = await this.WaitForElementByAccessibilityId(this.DeviceSerial); return element.Text; } @@ -107,4 +113,4 @@ await Retry.For(async () => { }); } -} \ No newline at end of file +} From a2253c2c38d0f0d91f55f8e6097e02b86497c5da Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 15:25:33 +0100 Subject: [PATCH 06/12] Harden transaction success page lookups --- .../Pages/TransactionsBillPaymentSelectOperatorPage.cs | 4 ++-- .../Pages/TransactionsMobileTopupSuccessfulTopupPage.cs | 4 ++-- .../Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs index 3564fefa..ce0feec5 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs @@ -196,7 +196,7 @@ public TransactionsBillPaymentSuccessfulPaymentPage(TestingContext testingContex #region Properties - protected override String Trait => "BillPaymentSuccessful"; + protected override String Trait => "CompleteButton"; public async Task ClickCompleteButton() { @@ -208,4 +208,4 @@ await Retry.For(async () => { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs index 5b758355..801dc021 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs @@ -15,7 +15,7 @@ public TransactionsMobileTopupSuccessfulTopupPage(TestingContext testingContext) #region Properties - protected override String Trait => "MobileTopupSuccessful"; + protected override String Trait => "CompleteButton"; public async Task ClickCompleteButton() { await Retry.For(async () => { @@ -26,4 +26,4 @@ await Retry.For(async () => { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs index db384253..03f5213f 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs @@ -17,7 +17,7 @@ public TransactionsVoucherIssueSuccessfulTopupPage(TestingContext testingContext #region Properties - protected override String Trait => "VoucherIssueSuccessful"; + protected override String Trait => "CompleteButton"; public async Task ClickCompleteButton() { @@ -29,4 +29,4 @@ await Retry.For(async () => { } #endregion -} \ No newline at end of file +} From 8ac63007bbaf12ac2e3459089339460b2a862963 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 15:32:58 +0100 Subject: [PATCH 07/12] Harden remaining UI page lookups --- .../Pages/ProfileAccountInfoPage.cs | 4 ++-- .../Pages/ProfileAddressesPage.cs | 4 ++-- .../Pages/ProfileContactsPage.cs | 4 ++-- .../Pages/ProfilePage.cs | 4 ++-- .../Pages/ReportsPage.cs | 22 ++----------------- ...ansactionsBillPaymentSelectOperatorPage.cs | 10 ++++----- ...actionsMobileTopupEnterTopupDetailsPage.cs | 4 ++-- ...ansactionsMobileTopupSelectOperatorPage.cs | 4 ++-- ...ransactionsMobileTopupSelectProductPage.cs | 4 ++-- .../Pages/TransactionsPage.cs | 11 ++-------- ...ionsVoucherEnterVoucherIssueDetailsPage.cs | 4 ++-- .../TransactionsVoucherSelectOperatorPage.cs | 4 ++-- .../TransactionsVoucherSelectProductPage.cs | 4 ++-- .../Pages/ViewLogsPage.cs | 4 ++-- 14 files changed, 31 insertions(+), 56 deletions(-) diff --git a/TransactionProcessor.Mobile.UITests/Pages/ProfileAccountInfoPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ProfileAccountInfoPage.cs index ae72bf25..c21d23cc 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ProfileAccountInfoPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ProfileAccountInfoPage.cs @@ -36,7 +36,7 @@ public ProfileAccountInfoPage(TestingContext testingContext) : base(testingConte #region Properties - protected override String Trait => "MyDetails"; + protected override String Trait => "MerchantNameLabel"; #endregion @@ -67,4 +67,4 @@ public async Task GetSettlementScheduleValue() { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/ProfileAddressesPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ProfileAddressesPage.cs index 7f54d341..9ccb156d 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ProfileAddressesPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ProfileAddressesPage.cs @@ -43,7 +43,7 @@ public ProfileAddressesPage(TestingContext testingContext) : base(testingContext #region Properties - protected override String Trait => "MyAddresses"; + protected override String Trait => "PrimaryAddressLabel"; #endregion @@ -75,4 +75,4 @@ public async Task IsPrimaryAddressShown() { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/ProfileContactsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ProfileContactsPage.cs index 2cbb8c3f..20828546 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ProfileContactsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ProfileContactsPage.cs @@ -32,7 +32,7 @@ public ProfileContactsPage(TestingContext testingContext) : base(testingContext) #region Properties //protected override String Trait => "MyContacts"; - protected override String Trait => "MyContacts"; + protected override String Trait => "PrimaryContactLabel"; #endregion @@ -55,4 +55,4 @@ public async Task IsPrimaryContactShown() { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/ProfilePage.cs b/TransactionProcessor.Mobile.UITests/Pages/ProfilePage.cs index 1e205f7a..eeecd27e 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ProfilePage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ProfilePage.cs @@ -32,7 +32,7 @@ public ProfilePage(TestingContext testingContext) : base(testingContext) #region Properties - protected override String Trait => "My Account"; + protected override String Trait => "MerchantNameLabel"; #endregion @@ -62,4 +62,4 @@ public async Task ClickLogoutButton() { #endregion } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs index df0ebfdd..14a0b000 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs @@ -6,17 +6,7 @@ namespace TransactionProcessor.Mobile.UITests.Pages; public class ReportsPage : BasePage2 { - protected override String Trait - { - get - { - if (AppiumDriverWrapper.MobileTestPlatform == MobileTestPlatform.Windows) - { - return "Reports"; - } - return "Reports"; - } - } + protected override String Trait => "BackButton"; private readonly String DailyPerformanceSummaryButton; private readonly String TransactionMixButton; @@ -52,15 +42,7 @@ public async Task ClickRecentActivityAndReceiptReportButton() } public class SupportPage : BasePage2{ - protected override String Trait{ - get{ - if (AppiumDriverWrapper.MobileTestPlatform == MobileTestPlatform.Windows){ - return "Support"; - } - - return "Support"; - } - } + protected override String Trait => "BackButton"; private readonly String UploadLogsButton; private readonly String ViewLogsButton; diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs index ce0feec5..b0d2406a 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs @@ -14,7 +14,7 @@ public TransactionsBillPaymentSelectOperatorPage(TestingContext testingContext) #region Properties - protected override String Trait => "SelectanOperator"; + protected override String Trait => "BackButton"; #endregion @@ -29,7 +29,7 @@ public class TransactionsBillPaymentSelectProductPage : BasePage2{ public TransactionsBillPaymentSelectProductPage(TestingContext testingContext) : base(testingContext){ } - protected override String Trait{ get; } + protected override String Trait => "BackButton"; public async Task ClickProductButton(String productText) { @@ -50,7 +50,7 @@ public TransactionsBillPaymentEnterAccountDetailsPage(TestingContext testingCont this.GetAccountButton = "GetAccountButton"; } - protected override String Trait => "GetCustomerAccount"; + protected override String Trait => "CustomerAccountNumberEntry"; public async Task EnterCustomerAccountNumber(String customerAccountNumber) { @@ -81,7 +81,7 @@ public TransactionsBillPaymentEnterMeterDetailsPage(TestingContext testingContex this.GetMeterButton = "GetMeterButton"; } - protected override String Trait => "GetMeter"; + protected override String Trait => "MeterNumberEntry"; public async Task EnterMeterNumber(String meterNumber) { @@ -123,7 +123,7 @@ public TransactionsBillPaymentMakeAPaymentPage(TestingContext testingContext) : } - protected override String Trait => "MakeBillPayment"; + protected override String Trait => "MakePaymentButton"; public async Task EnterCustomerMobileNumber(String customerMobileNumber) { diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupEnterTopupDetailsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupEnterTopupDetailsPage.cs index 19e332a3..7e008e5f 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupEnterTopupDetailsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupEnterTopupDetailsPage.cs @@ -21,7 +21,7 @@ public TransactionsMobileTopupEnterTopupDetailsPage(TestingContext testingContex #region Properties - protected override String Trait => "EnterTopupDetails"; + protected override String Trait => "CustomerMobileNumberEntry"; public async Task EnterCustomerMobileNumber(String customerMobileNumber) { IWebElement element = await this.WaitForElementByAccessibilityId(this.CustomerMobileNumberEntry); @@ -50,4 +50,4 @@ await Retry.For(async () => { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs index 4a846671..5118c795 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs @@ -12,7 +12,7 @@ public TransactionsMobileTopupSelectOperatorPage(TestingContext testingContext) } #region Properties - protected override String Trait => "SelectanOperator"; + protected override String Trait => "BackButton"; #endregion @@ -20,4 +20,4 @@ public async Task ClickOperatorButton(String operatorName) { IWebElement element = await this.WaitForElementByAccessibilityId(operatorName); element.Click(); } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs index 2dbf8e90..883f1ffc 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs @@ -11,7 +11,7 @@ public TransactionsMobileTopupSelectProductPage(TestingContext testingContext) : #region Properties - protected override String Trait => "SelectaProduct"; + protected override String Trait => "BackButton"; #endregion @@ -19,4 +19,4 @@ public async Task ClickProductButton(String productText) { IWebElement element = await this.WaitForElementByAccessibilityId(productText); element.Click(); } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs index 76b3afd8..3acb3daa 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs @@ -6,14 +6,7 @@ namespace TransactionProcessor.Mobile.UITests.Pages; public class TransactionsPage : BasePage2 { - protected override String Trait{ - get{ - if (AppiumDriverWrapper.MobileTestPlatform == MobileTestPlatform.Windows){ - return "SelectTransactionType"; - } - return "Transactions"; - } - } + protected override String Trait => "SelectTransactionType"; private readonly String MobileTopupButton; private readonly String VoucherButton; @@ -42,4 +35,4 @@ public async Task ClickBillPaymentButton() IWebElement element = await this.WaitForElementByAccessibilityId(this.BillPaymentButton); element.Click(); } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherEnterVoucherIssueDetailsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherEnterVoucherIssueDetailsPage.cs index 62eebaf8..46bf95a2 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherEnterVoucherIssueDetailsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherEnterVoucherIssueDetailsPage.cs @@ -25,7 +25,7 @@ public TransactionsVoucherEnterVoucherIssueDetailsPage(TestingContext testingCon #region Properties - protected override String Trait => "EnterVoucherIssueDetails"; + protected override String Trait => "RecipientMobileNumberEntry"; public async Task EnterRecipientMobileNumber(String recipientMobileNumber) { @@ -65,4 +65,4 @@ await Retry.For(async () => { } #endregion -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs index 7ee98a83..45a20f72 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs @@ -12,7 +12,7 @@ public TransactionsVoucherSelectOperatorPage(TestingContext testingContext) : ba } #region Properties - protected override String Trait => "SelectanOperator"; + protected override String Trait => "BackButton"; #endregion public async Task ClickOperatorButton(String operatorName) @@ -20,4 +20,4 @@ public async Task ClickOperatorButton(String operatorName) IWebElement element = await this.WaitForElementByAccessibilityId(operatorName); element.Click(); } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs index b5f00bed..8a98fac7 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs @@ -13,7 +13,7 @@ public TransactionsVoucherSelectProductPage(TestingContext testingContext) : bas #region Properties - protected override String Trait => "SelectaProduct"; + protected override String Trait => "BackButton"; #endregion @@ -22,4 +22,4 @@ public async Task ClickProductButton(String productText) IWebElement element = await this.WaitForElementByAccessibilityId(productText); element.Click(); } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs index 615a0955..317270c4 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs @@ -8,5 +8,5 @@ public ViewLogsPage(TestingContext testingContext) : base(testingContext){ } - protected override String Trait => "ViewLogs"; -} \ No newline at end of file + protected override String Trait => "BackButton"; +} From cf7c80ea524a5c01bd11e239de0209f371ecd31b Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 16:09:01 +0100 Subject: [PATCH 08/12] Reuse existing Appium server in Android tests --- .../Drivers/AppiumDriver.cs | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/TransactionProcessor.Mobile.UITests/Drivers/AppiumDriver.cs b/TransactionProcessor.Mobile.UITests/Drivers/AppiumDriver.cs index 51ade1ef..4adda387 100644 --- a/TransactionProcessor.Mobile.UITests/Drivers/AppiumDriver.cs +++ b/TransactionProcessor.Mobile.UITests/Drivers/AppiumDriver.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; +using System.Net.Sockets; using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Enums; @@ -17,15 +18,20 @@ public enum MobileTestPlatform public class AppiumDriverWrapper { + private const int AppiumPort = 4723; + private static readonly Uri AppiumServerUri = new($"http://127.0.0.1:{AppiumPort}/"); + public static MobileTestPlatform MobileTestPlatform; public static AppiumDriver Driver; public void StartApp() { - AppiumLocalService appiumService = new AppiumServiceBuilder().UsingPort(4723).Build(); + bool appiumAlreadyRunning = this.IsPortOpen("127.0.0.1", AppiumPort); + AppiumLocalService appiumService = null; - if (appiumService.IsRunning == false) + if (appiumAlreadyRunning == false) { + appiumService = new AppiumServiceBuilder().UsingPort(AppiumPort).Build(); appiumService.OutputDataReceived += (sender, args) => { Console.WriteLine(args.Data); @@ -44,7 +50,7 @@ public void StartApp() } } - private static void SetupWindowsDriver(AppiumLocalService appiumService) + private static void SetupWindowsDriver(AppiumLocalService? appiumService) { var driverOptions = new AppiumOptions(); driverOptions.AutomationName = "windows"; @@ -56,10 +62,12 @@ private static void SetupWindowsDriver(AppiumLocalService appiumService) driverOptions.AddAdditionalAppiumOption("ms:waitForAppLaunch", "50"); //driverOptions.AddAdditionalAppiumOption("appium:createSessionTimeout", "100000"); driverOptions.App = "TransactionMobile_zct748q4xfh0m!App"; - AppiumDriverWrapper.Driver = new WindowsDriver(appiumService, driverOptions, TimeSpan.FromMinutes(10)); + AppiumDriverWrapper.Driver = appiumService == null + ? new WindowsDriver(AppiumServerUri, driverOptions, TimeSpan.FromMinutes(10)) + : new WindowsDriver(appiumService, driverOptions, TimeSpan.FromMinutes(10)); } - private static void SetupAndroidDriver(AppiumLocalService appiumService) + private static void SetupAndroidDriver(AppiumLocalService? appiumService) { var driverOptions = new AppiumOptions(); driverOptions.AddAdditionalAppiumOption("adbExecTimeout", TimeSpan.FromMinutes(5).TotalMilliseconds); @@ -82,7 +90,9 @@ private static void SetupAndroidDriver(AppiumLocalService appiumService) driverOptions.App = apkPath; - AppiumDriverWrapper.Driver = new OpenQA.Selenium.Appium.Android.AndroidDriver(appiumService, driverOptions, TimeSpan.FromMinutes(5)); + AppiumDriverWrapper.Driver = appiumService == null + ? new OpenQA.Selenium.Appium.Android.AndroidDriver(AppiumServerUri, driverOptions, TimeSpan.FromMinutes(5)) + : new OpenQA.Selenium.Appium.Android.AndroidDriver(appiumService, driverOptions, TimeSpan.FromMinutes(5)); } public List GetLogs() @@ -112,5 +122,27 @@ public void StopApp() Console.WriteLine(e.Message); } } + + private bool IsPortOpen(string host, int port) + { + try + { + using TcpClient client = new TcpClient(); + IAsyncResult result = client.BeginConnect(host, port, null, null); + bool completed = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1)); + if (completed == false) + { + client.Close(); + return false; + } + + client.EndConnect(result); + return client.Connected; + } + catch + { + return false; + } + } } } From e7400b01547ff08e4009933532b4a64fe17d2448 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Wed, 19 Aug 2026 17:23:48 +0100 Subject: [PATCH 09/12] Harden Android report selectors --- .../ModelTests/AutomationIdHelperTests.cs | 28 ++++++++++ .../Models/AutomationIdHelper.cs | 53 +++++++++++++++++++ .../RecentActivityReceiptReportModel.cs | 2 + .../Models/TransactionMixSummaryModel.cs | 5 +- .../Pages/BasePage.cs | 2 +- .../Pages/Extenstions.cs | 7 +-- .../Pages/MainPage.cs | 4 +- .../Pages/RecentActivityReportPage.cs | 52 +++++++++++++++++- .../Pages/ReportsPage.cs | 4 +- ...ansactionsBillPaymentSelectOperatorPage.cs | 6 +-- ...ansactionsMobileTopupSelectOperatorPage.cs | 2 +- ...ransactionsMobileTopupSelectProductPage.cs | 2 +- ...nsactionsMobileTopupSuccessfulTopupPage.cs | 2 +- .../Pages/TransactionsPage.cs | 2 +- ...sactionsVoucherIssueSuccessfulTopupPage.cs | 2 +- .../TransactionsVoucherSelectOperatorPage.cs | 2 +- .../TransactionsVoucherSelectProductPage.cs | 2 +- .../Pages/ViewLogsPage.cs | 2 +- .../Steps/TransactionsSteps.cs | 6 +-- .../MyAccount/MyAccountAddressesPage.xaml | 4 +- .../Pages/MyAccount/MyAccountContactPage.xaml | 4 +- .../Pages/MyAccount/MyAccountDetailsPage.xaml | 4 +- .../Pages/MyAccount/MyAccountPage.xaml | 2 +- .../Reports/RecentActivityReportPage.xaml | 2 +- .../Pages/Reports/ReportsPage.xaml | 2 +- .../Pages/Reports/TransactionMixPage.xaml | 2 +- .../Pages/Support/SupportPage.xaml | 2 +- .../Pages/Support/ViewLogsPage.xaml | 4 +- .../Pages/Transactions/Admin/AdminPage.xaml | 2 +- .../BillPayment/BillPaymentFailedPage.xaml | 4 +- .../BillPaymentGetAccountPage.xaml | 4 +- .../BillPayment/BillPaymentGetMeterPage.xaml | 4 +- .../BillPayment/BillPaymentPayBillPage.xaml | 4 +- .../BillPaymentSelectOperatorPage.xaml | 4 +- .../BillPaymentSelectProductPage.xaml | 4 +- .../BillPayment/BillPaymentSuccessPage.xaml | 4 +- .../MobileTopup/MobileTopupFailedPage.xaml | 4 +- .../MobileTopupPerformTopupPage.xaml | 4 +- .../MobileTopupSelectOperatorPage.xaml | 4 +- .../MobileTopupSelectProductPage.xaml | 4 +- .../MobileTopup/MobileTopupSuccessPage.xaml | 4 +- .../Pages/Transactions/TransactionsPage.xaml | 2 +- .../Voucher/VoucherIssueFailedPage.xaml | 4 +- .../Voucher/VoucherIssueSuccessPage.xaml | 4 +- .../Voucher/VoucherPerformIssuePage.xaml | 4 +- .../Voucher/VoucherSelectOperatorPage.xaml | 4 +- .../Voucher/VoucherSelectProductPage.xaml | 4 +- 47 files changed, 210 insertions(+), 73 deletions(-) create mode 100644 TransactionProcessor.Mobile.BusinessLogic.Tests/ModelTests/AutomationIdHelperTests.cs create mode 100644 TransactionProcessor.Mobile.BusinessLogic/Models/AutomationIdHelper.cs diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/ModelTests/AutomationIdHelperTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/ModelTests/AutomationIdHelperTests.cs new file mode 100644 index 00000000..3f98f612 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/ModelTests/AutomationIdHelperTests.cs @@ -0,0 +1,28 @@ +using Shouldly; +using TransactionProcessor.Mobile.BusinessLogic.Models; + +namespace TransactionProcessor.Mobile.BusinessLogic.Tests.ModelTests; + +public class AutomationIdHelperTests +{ + [Fact] + public void Create_ReplacesUnsupportedCharactersWithUnderscores() + { + AutomationIdHelper.Create("RecentActivityResult", "TXN-10001") + .ShouldBe("RecentActivityResult_TXN_10001"); + } + + [Fact] + public void Create_TrimsAndCollapsesSeparators() + { + AutomationIdHelper.Create("TransactionMixItem", " bill-pay-post ") + .ShouldBe("TransactionMixItem_bill_pay_post"); + } + + [Fact] + public void Create_ReturnsPrefixForEmptyValues() + { + AutomationIdHelper.Create("TransactionMixItem", " ") + .ShouldBe("TransactionMixItem"); + } +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/Models/AutomationIdHelper.cs b/TransactionProcessor.Mobile.BusinessLogic/Models/AutomationIdHelper.cs new file mode 100644 index 00000000..17da49f4 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic/Models/AutomationIdHelper.cs @@ -0,0 +1,53 @@ +namespace TransactionProcessor.Mobile.BusinessLogic.Models; + +public static class AutomationIdHelper +{ + public static string Create(string prefix, string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return prefix; + } + + var builder = new System.Text.StringBuilder(prefix.Length + value.Length + 1); + builder.Append(prefix); + builder.Append('_'); + + bool appendedCharacter = false; + bool lastWasSeparator = false; + + foreach (char character in value.Trim()) + { + char sanitized = char.IsLetterOrDigit(character) ? character : '_'; + + if (sanitized == '_') + { + if (lastWasSeparator) + { + continue; + } + + lastWasSeparator = true; + } + else + { + lastWasSeparator = false; + } + + builder.Append(sanitized); + appendedCharacter = true; + } + + if (appendedCharacter == false) + { + return prefix; + } + + if (builder.Length > 0 && builder[^1] == '_') + { + builder.Length--; + } + + return builder.ToString(); + } +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/Models/RecentActivityReceiptReportModel.cs b/TransactionProcessor.Mobile.BusinessLogic/Models/RecentActivityReceiptReportModel.cs index 324d7fe4..ebe6cb5e 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/Models/RecentActivityReceiptReportModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/Models/RecentActivityReceiptReportModel.cs @@ -69,6 +69,8 @@ public sealed record RecentActivityReceiptItemModel(string Reference, DateTime TransactionDateTime, string ReceiptReference) { + public string AutomationId => AutomationIdHelper.Create("RecentActivityResult", this.Reference); + public bool Matches(string searchText) { return this.Reference.Contains(searchText, StringComparison.OrdinalIgnoreCase) diff --git a/TransactionProcessor.Mobile.BusinessLogic/Models/TransactionMixSummaryModel.cs b/TransactionProcessor.Mobile.BusinessLogic/Models/TransactionMixSummaryModel.cs index 769e1025..31ff2ef9 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/Models/TransactionMixSummaryModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/Models/TransactionMixSummaryModel.cs @@ -75,7 +75,10 @@ public static TransactionMixSummaryModel CreateMock(int merchantReportingId, public sealed record TransactionMixSummaryItemModel(string Key, string Label, decimal Count, - decimal Value); + decimal Value) +{ + public string AutomationId => AutomationIdHelper.Create("TransactionMixItem", this.Key); +} public sealed record TransactionMixDrillDownTransactionModel(string Reference, string TransactionType, diff --git a/TransactionProcessor.Mobile.UITests/Pages/BasePage.cs b/TransactionProcessor.Mobile.UITests/Pages/BasePage.cs index 9620f2c8..043076a0 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/BasePage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/BasePage.cs @@ -97,7 +97,7 @@ public async Task GetLabelValue(String labelAutomationId) internal async Task WaitForElementByAccessibilityId(String accessibilityId, TimeSpan? timeout = null, Int32 i = 0) { - return await AppiumDriverWrapper.Driver.GetElement(accessibilityId); + return await AppiumDriverWrapper.Driver.GetElement(accessibilityId, timeout); } //internal async Task WaitForNoElementByAccessibilityId(String accessibilityId, TimeSpan? timeout = null) diff --git a/TransactionProcessor.Mobile.UITests/Pages/Extenstions.cs b/TransactionProcessor.Mobile.UITests/Pages/Extenstions.cs index 7754fb0e..95d4388b 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/Extenstions.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/Extenstions.cs @@ -105,10 +105,11 @@ await Retry.For(async () => { // return element; //} public static async Task GetElement(this AppiumDriver driver, - string automationId, - string androidPackage = "com.transactionprocessor.mobile") + string automationId, + TimeSpan? timeout = null, + string androidPackage = "com.transactionprocessor.mobile") { - TimeSpan retryFor = TimeSpan.FromSeconds(60); + TimeSpan retryFor = timeout ?? TimeSpan.FromSeconds(60); IWebElement element = null; diff --git a/TransactionProcessor.Mobile.UITests/Pages/MainPage.cs b/TransactionProcessor.Mobile.UITests/Pages/MainPage.cs index 578ec5ab..424d81fe 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/MainPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/MainPage.cs @@ -4,7 +4,7 @@ namespace TransactionProcessor.Mobile.UITests.Pages; public class MainPage : BasePage2 { - protected override String Trait => "Home"; + protected override String Trait => "MainPageHeader"; private readonly String TransactionsButton; @@ -65,4 +65,4 @@ public async Task ClickSupportButton() //return balanceValue; return 0; } -} \ No newline at end of file +} diff --git a/TransactionProcessor.Mobile.UITests/Pages/RecentActivityReportPage.cs b/TransactionProcessor.Mobile.UITests/Pages/RecentActivityReportPage.cs index a52336c7..8b40a895 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/RecentActivityReportPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/RecentActivityReportPage.cs @@ -6,6 +6,7 @@ namespace TransactionProcessor.Mobile.UITests.Pages; public class RecentActivityReportPage : BasePage2 { protected override string Trait => "RecentActivityReport"; + private const string ResultAutomationIdPrefix = "RecentActivityResult"; public RecentActivityReportPage(TestingContext testingContext) : base(testingContext) { @@ -26,7 +27,56 @@ public async Task ClickSearchButton() public async Task ClickResult(string reference) { - IWebElement element = await this.WaitForElementByAccessibilityId(reference); + IWebElement element = await this.WaitForElementByAccessibilityId(BuildAutomationId(reference)); element.Click(); } + + private static string BuildAutomationId(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return ResultAutomationIdPrefix; + } + + var builder = new System.Text.StringBuilder(ResultAutomationIdPrefix.Length + value.Length + 1); + builder.Append(ResultAutomationIdPrefix); + builder.Append('_'); + + bool appendedCharacter = false; + bool lastWasSeparator = false; + + foreach (char character in value.Trim()) + { + char sanitized = char.IsLetterOrDigit(character) ? character : '_'; + + if (sanitized == '_') + { + if (lastWasSeparator) + { + continue; + } + + lastWasSeparator = true; + } + else + { + lastWasSeparator = false; + } + + builder.Append(sanitized); + appendedCharacter = true; + } + + if (appendedCharacter == false) + { + return ResultAutomationIdPrefix; + } + + if (builder.Length > 0 && builder[^1] == '_') + { + builder.Length--; + } + + return builder.ToString(); + } } diff --git a/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs index 14a0b000..c3302126 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ReportsPage.cs @@ -6,7 +6,7 @@ namespace TransactionProcessor.Mobile.UITests.Pages; public class ReportsPage : BasePage2 { - protected override String Trait => "BackButton"; + protected override String Trait => "ReportsPageHeader"; private readonly String DailyPerformanceSummaryButton; private readonly String TransactionMixButton; @@ -42,7 +42,7 @@ public async Task ClickRecentActivityAndReceiptReportButton() } public class SupportPage : BasePage2{ - protected override String Trait => "BackButton"; + protected override String Trait => "SupportPageHeader"; private readonly String UploadLogsButton; private readonly String ViewLogsButton; diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs index b0d2406a..3c65dde7 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsBillPaymentSelectOperatorPage.cs @@ -14,7 +14,7 @@ public TransactionsBillPaymentSelectOperatorPage(TestingContext testingContext) #region Properties - protected override String Trait => "BackButton"; + protected override String Trait => "BillPaymentSelectOperatorPageHeader"; #endregion @@ -29,7 +29,7 @@ public class TransactionsBillPaymentSelectProductPage : BasePage2{ public TransactionsBillPaymentSelectProductPage(TestingContext testingContext) : base(testingContext){ } - protected override String Trait => "BackButton"; + protected override String Trait => "BillPaymentSelectProductPageHeader"; public async Task ClickProductButton(String productText) { @@ -196,7 +196,7 @@ public TransactionsBillPaymentSuccessfulPaymentPage(TestingContext testingContex #region Properties - protected override String Trait => "CompleteButton"; + protected override String Trait => "BillPaymentSuccessPageHeader"; public async Task ClickCompleteButton() { diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs index 5118c795..9407d276 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectOperatorPage.cs @@ -12,7 +12,7 @@ public TransactionsMobileTopupSelectOperatorPage(TestingContext testingContext) } #region Properties - protected override String Trait => "BackButton"; + protected override String Trait => "MobileTopupSelectOperatorPageHeader"; #endregion diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs index 883f1ffc..bea57a8b 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSelectProductPage.cs @@ -11,7 +11,7 @@ public TransactionsMobileTopupSelectProductPage(TestingContext testingContext) : #region Properties - protected override String Trait => "BackButton"; + protected override String Trait => "MobileTopupSelectProductPageHeader"; #endregion diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs index 801dc021..279555e8 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsMobileTopupSuccessfulTopupPage.cs @@ -15,7 +15,7 @@ public TransactionsMobileTopupSuccessfulTopupPage(TestingContext testingContext) #region Properties - protected override String Trait => "CompleteButton"; + protected override String Trait => "MobileTopupSuccessPageHeader"; public async Task ClickCompleteButton() { await Retry.For(async () => { diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs index 3acb3daa..94abb098 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsPage.cs @@ -6,7 +6,7 @@ namespace TransactionProcessor.Mobile.UITests.Pages; public class TransactionsPage : BasePage2 { - protected override String Trait => "SelectTransactionType"; + protected override String Trait => "TransactionsPageHeader"; private readonly String MobileTopupButton; private readonly String VoucherButton; diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs index 03f5213f..48245207 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherIssueSuccessfulTopupPage.cs @@ -17,7 +17,7 @@ public TransactionsVoucherIssueSuccessfulTopupPage(TestingContext testingContext #region Properties - protected override String Trait => "CompleteButton"; + protected override String Trait => "VoucherIssueSuccessPageHeader"; public async Task ClickCompleteButton() { diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs index 45a20f72..e0d44544 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectOperatorPage.cs @@ -12,7 +12,7 @@ public TransactionsVoucherSelectOperatorPage(TestingContext testingContext) : ba } #region Properties - protected override String Trait => "BackButton"; + protected override String Trait => "VoucherSelectOperatorPageHeader"; #endregion public async Task ClickOperatorButton(String operatorName) diff --git a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs index 8a98fac7..52280b09 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/TransactionsVoucherSelectProductPage.cs @@ -13,7 +13,7 @@ public TransactionsVoucherSelectProductPage(TestingContext testingContext) : bas #region Properties - protected override String Trait => "BackButton"; + protected override String Trait => "VoucherSelectProductPageHeader"; #endregion diff --git a/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs b/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs index 317270c4..0b04ae05 100644 --- a/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs +++ b/TransactionProcessor.Mobile.UITests/Pages/ViewLogsPage.cs @@ -8,5 +8,5 @@ public ViewLogsPage(TestingContext testingContext) : base(testingContext){ } - protected override String Trait => "BackButton"; + protected override String Trait => "ViewLogsPageHeader"; } diff --git a/TransactionProcessor.Mobile.UITests/Steps/TransactionsSteps.cs b/TransactionProcessor.Mobile.UITests/Steps/TransactionsSteps.cs index 514992d4..89eb160d 100644 --- a/TransactionProcessor.Mobile.UITests/Steps/TransactionsSteps.cs +++ b/TransactionProcessor.Mobile.UITests/Steps/TransactionsSteps.cs @@ -109,7 +109,7 @@ public async Task WhenITapOnIssueVoucher(){ [Then(@"the Voucher Issue Successful Page is displayed")] public async Task ThenTheVoucherIssueSuccessfulPageIsDisplayed() { - await this.transactionsVoucherIssueSuccessfulTopupPage.AssertOnPage(); + await this.transactionsVoucherIssueSuccessfulTopupPage.AssertOnPage(TimeSpan.FromMinutes(2)); } [Then(@"the Transaction Select Mobile Topup Operator Page is displayed")] @@ -180,7 +180,7 @@ public async Task WhenITapOnPerformTopup(){ [Then(@"the Mobile Topup Successful Page is displayed")] public async Task ThenTheMobileTopupSuccessfulPageIsDisplayed() { - await this.transactionsMobileTopupSuccessfulTopupPage.AssertOnPage(); + await this.transactionsMobileTopupSuccessfulTopupPage.AssertOnPage(TimeSpan.FromMinutes(2)); } [Then(@"I tap on Complete")] @@ -254,7 +254,7 @@ public async Task WhenITapOnTheMakePaymentButton(){ [Then(@"the Bill Payment Successful Page is displayed")] public async Task ThenTheBillPaymentSuccessfulPageIsDisplayed(){ - await this.transactionsBillPaymentSuccessfulPaymentPage.AssertOnPage(); + await this.transactionsBillPaymentSuccessfulPaymentPage.AssertOnPage(TimeSpan.FromMinutes(2)); } [Then(@"the Enter Meter Details Page is displayed")] diff --git a/TransactionProcessor.Mobile/Pages/MyAccount/MyAccountAddressesPage.xaml b/TransactionProcessor.Mobile/Pages/MyAccount/MyAccountAddressesPage.xaml index bb4c2fff..c45b0b49 100644 --- a/TransactionProcessor.Mobile/Pages/MyAccount/MyAccountAddressesPage.xaml +++ b/TransactionProcessor.Mobile/Pages/MyAccount/MyAccountAddressesPage.xaml @@ -17,7 +17,7 @@