diff --git a/EstateManagementUI.BlazorFastIntegrationTests/Common/DashboardPageHelper.cs b/EstateManagementUI.BlazorFastIntegrationTests/Common/DashboardPageHelper.cs index 9ff2dd4f..94046e7d 100644 --- a/EstateManagementUI.BlazorFastIntegrationTests/Common/DashboardPageHelper.cs +++ b/EstateManagementUI.BlazorFastIntegrationTests/Common/DashboardPageHelper.cs @@ -14,6 +14,20 @@ namespace EstateManagementUI.IntegrationTests.Common; public sealed class DashboardPageHelper { + private const string EstateManagementHeading = "Estate Management"; + private const string BackToListText = "Back to List"; + private const string LoginUsernameSelector = "#loginUsername"; + private const string LoginPasswordSelector = "#loginPassword"; + private const string SubmitLoginButtonSelector = "#submitLoginButton"; + private const string LoginButtonSelector = "#loginButton"; + private const string SelectSelector = "select"; + private const string SelectOptionSelector = "select option"; + private const string ValueAttributeName = "value"; + private const string MerchantRowSelector = "div.flex.items-center.justify-between.p-3.bg-gray-50.rounded-lg"; + private const string MerchantsPath = "/merchants"; + private const string DateFormat = "yyyy-MM-dd"; + private const string SettlementScheduleSelector = "select[name='SettlementSchedule']"; + private readonly IPage _page; private readonly TestingContext TestingContext; @@ -28,7 +42,7 @@ await RunWithFailureArtifactsAsync(async () => { await _page.GotoAsync(ResolveBaseUrl() + "/entry"); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); - await _page.GetByRole(AriaRole.Heading, new() { Name = "Estate Management" }).WaitForAsync(new LocatorWaitForOptions + await _page.GetByRole(AriaRole.Heading, new() { Name = EstateManagementHeading }).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 @@ -49,7 +63,7 @@ public async Task AssertEntryScreenVisibleAsync() { await RunWithFailureArtifactsAsync(async () => { - (await _page.GetByRole(AriaRole.Heading, new() { Name = "Estate Management" }).IsVisibleAsync()).ShouldBeTrue(); + (await _page.GetByRole(AriaRole.Heading, new() { Name = EstateManagementHeading }).IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByText("Manage estate details").IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByText("Manage estate users").IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByText("Operator Management").IsVisibleAsync()).ShouldBeTrue(); @@ -70,7 +84,7 @@ public async Task AssertEstateInfoPageVisibleAsync() { await RunWithFailureArtifactsAsync(async () => { - await _page.GetByRole(AriaRole.Heading, new() { Name = "Estate Management" }).WaitForAsync(new LocatorWaitForOptions + await _page.GetByRole(AriaRole.Heading, new() { Name = EstateManagementHeading }).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 @@ -80,15 +94,15 @@ await _page.GetByText("Comprehensive estate management and configuration").WaitF State = WaitForSelectorState.Visible, Timeout = 10000 }); - await _page.Locator("#loginButton").WaitForAsync(new LocatorWaitForOptions + await _page.Locator(LoginButtonSelector).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 }); - (await _page.GetByRole(AriaRole.Heading, new() { Name = "Estate Management" }).IsVisibleAsync()).ShouldBeTrue(); + (await _page.GetByRole(AriaRole.Heading, new() { Name = EstateManagementHeading }).IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByText("Comprehensive estate management and configuration").IsVisibleAsync()).ShouldBeTrue(); - (await _page.Locator("#loginButton").IsVisibleAsync()).ShouldBeTrue(); + (await _page.Locator(LoginButtonSelector).IsVisibleAsync()).ShouldBeTrue(); }, nameof(AssertEstateInfoPageVisibleAsync)); } @@ -168,7 +182,7 @@ await heading.WaitForAsync(new LocatorWaitForOptions (await heading.IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByRole(AriaRole.Heading, new() { Name = "Operator Details" }).IsVisibleAsync()).ShouldBeTrue(); - (await _page.GetByRole(AriaRole.Button, new() { Name = "Back to List" }).IsVisibleAsync()).ShouldBeTrue(); + (await _page.GetByRole(AriaRole.Button, new() { Name = BackToListText }).IsVisibleAsync()).ShouldBeTrue(); }, nameof(AssertOperatorViewVisibleAsync)); } @@ -176,7 +190,7 @@ public async Task BackToOperatorListFromViewAsync() { await RunWithFailureArtifactsAsync(async () => { - await _page.GetByRole(AriaRole.Button, new() { Name = "Back to List" }).ClickAsync(); + await _page.GetByRole(AriaRole.Button, new() { Name = BackToListText }).ClickAsync(); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); }, nameof(BackToOperatorListFromViewAsync)); } @@ -266,7 +280,7 @@ public async Task ClickSignInButtonAsync() { await RunWithFailureArtifactsAsync(async () => { - var entrySignInButton = _page.Locator("#loginButton"); + var entrySignInButton = _page.Locator(LoginButtonSelector); Console.WriteLine($"Sign in before click: {_page.Url}"); @@ -296,17 +310,17 @@ await RunWithFailureArtifactsAsync(async () => State = WaitForSelectorState.Visible, Timeout = 10000 }); - await _page.Locator("#loginUsername").WaitForAsync(new LocatorWaitForOptions + await _page.Locator(LoginUsernameSelector).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 }); - await _page.Locator("#loginPassword").WaitForAsync(new LocatorWaitForOptions + await _page.Locator(LoginPasswordSelector).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 }); - await _page.Locator("#submitLoginButton").WaitForAsync(new LocatorWaitForOptions + await _page.Locator(SubmitLoginButtonSelector).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 @@ -319,14 +333,14 @@ public async Task LoginAsync(string username, string password) await RunWithFailureArtifactsAsync(async () => { if (!_page.Url.Contains("/login", StringComparison.OrdinalIgnoreCase) || - !await _page.Locator("#loginUsername").IsVisibleAsync()) + !await _page.Locator(LoginUsernameSelector).IsVisibleAsync()) { await ClickSignInButtonAsync(); } - await _page.Locator("#loginUsername").FillAsync(username); - await _page.Locator("#loginPassword").FillAsync(password); - await _page.Locator("#submitLoginButton").ClickAsync(); + await _page.Locator(LoginUsernameSelector).FillAsync(username); + await _page.Locator(LoginPasswordSelector).FillAsync(password); + await _page.Locator(SubmitLoginButtonSelector).ClickAsync(); await _page.GetByRole(AriaRole.Heading, new() { Name = "Dashboard" }).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, @@ -361,7 +375,7 @@ public async Task AssertAuthenticatedLandingVisibleAsync() { await RunWithFailureArtifactsAsync(async () => { - await _page.GetByRole(AriaRole.Heading, new() { Name = "Estate Management" }).WaitForAsync(new LocatorWaitForOptions + await _page.GetByRole(AriaRole.Heading, new() { Name = EstateManagementHeading }).WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 10000 @@ -397,7 +411,7 @@ public async Task AssertEstateManagementHeadingVisibleAsync() await RunWithFailureArtifactsAsync(async () => { await WaitForEstateOverviewAsync(); - (await _page.GetByRole(AriaRole.Heading, new() { Name = "Estate Management" }).IsVisibleAsync()).ShouldBeTrue(); + (await _page.GetByRole(AriaRole.Heading, new() { Name = EstateManagementHeading }).IsVisibleAsync()).ShouldBeTrue(); }, nameof(AssertEstateManagementHeadingVisibleAsync)); } @@ -444,7 +458,7 @@ await RunWithFailureArtifactsAsync(async () => { await _page.Locator("#addOperatorButton").ClickAsync(); - var option = _page.Locator($"select option:has-text('{operatorName}')"); + var option = _page.Locator($"{SelectOptionSelector}:has-text('{operatorName}')"); await option.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Attached, @@ -453,10 +467,10 @@ await option.WaitForAsync(new LocatorWaitForOptions (await option.CountAsync()).ShouldBeGreaterThan(0); - var optionValue = await option.First.GetAttributeAsync("value"); + var optionValue = await option.First.GetAttributeAsync(ValueAttributeName); optionValue.ShouldNotBeNull(); - await _page.Locator("select").SelectOptionAsync(new[] { optionValue! }); + await _page.Locator(SelectSelector).SelectOptionAsync(new[] { optionValue! }); await _page.GetByRole(AriaRole.Button, new() { Name = "Add", Exact = true }).ClickAsync(); }, nameof(AddOperatorToEstateAsync)); } @@ -679,17 +693,17 @@ await _page.Locator("#createContractButton").WaitForAsync(new LocatorWaitForOpti Timeout = 10000 }); - var operatorOption = _page.Locator("select option").Filter(new() { HasText = operatorName }); + var operatorOption = _page.Locator(SelectOptionSelector).Filter(new() { HasText = operatorName }); await operatorOption.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Attached, Timeout = 10000 }); - var operatorValue = await operatorOption.First.GetAttributeAsync("value"); + var operatorValue = await operatorOption.First.GetAttributeAsync(ValueAttributeName); operatorValue.ShouldNotBeNull(); - await _page.Locator("select").SelectOptionAsync(new[] { operatorValue! }); + await _page.Locator(SelectSelector).SelectOptionAsync(new[] { operatorValue! }); await _page.Locator("input[placeholder='Enter contract description']").FillAsync(contractDescription); await _page.Locator("#createContractButton").ClickAsync(); await _page.Locator("#newContractButton").WaitForAsync(new LocatorWaitForOptions @@ -744,7 +758,7 @@ await heading.WaitForAsync(new LocatorWaitForOptions (await heading.IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByRole(AriaRole.Heading, new() { Name = "Contract Details" }).IsVisibleAsync()).ShouldBeTrue(); - (await _page.GetByRole(AriaRole.Button, new() { Name = "Back to List" }).IsVisibleAsync()).ShouldBeTrue(); + (await _page.GetByRole(AriaRole.Button, new() { Name = BackToListText }).IsVisibleAsync()).ShouldBeTrue(); }, nameof(AssertContractViewVisibleAsync)); } @@ -853,8 +867,8 @@ await modal.WaitForAsync(new LocatorWaitForOptions }); await modal.Locator("input[placeholder='Enter fee description']").FillAsync(feeDescription); - await modal.Locator("select").Nth(0).SelectOptionAsync(ResolveCalculationTypeOptionValue(calculationType)); - await modal.Locator("select").Nth(1).SelectOptionAsync(ResolveFeeTypeOptionValue(feeType)); + await modal.Locator(SelectSelector).Nth(0).SelectOptionAsync(ResolveCalculationTypeOptionValue(calculationType)); + await modal.Locator(SelectSelector).Nth(1).SelectOptionAsync(ResolveFeeTypeOptionValue(feeType)); await modal.Locator("input[placeholder='Enter fee value']").FillAsync(feeValue.ToString(CultureInfo.InvariantCulture)); await modal.GetByRole(AriaRole.Button, new() { Name = "Add Fee" }).ClickAsync(); @@ -878,7 +892,7 @@ public async Task BackToContractListAsync() { await RunWithFailureArtifactsAsync(async () => { - await _page.GetByRole(AriaRole.Button, new() { Name = "Back to List" }).ClickAsync(); + await _page.GetByRole(AriaRole.Button, new() { Name = BackToListText }).ClickAsync(); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); }, nameof(BackToContractListAsync)); } @@ -894,7 +908,7 @@ await RunWithFailureArtifactsAsync(async () => } else { - await _page.GotoAsync(ResolveBaseUrl() + "/merchants"); + await _page.GotoAsync(ResolveBaseUrl() + MerchantsPath); } await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); @@ -918,7 +932,7 @@ await heading.WaitForAsync(new LocatorWaitForOptions public async Task CreateMerchantAsync(string merchantName) { - await CreateMerchantAsync( + await CreateMerchantAsync(new MerchantCreationInput( merchantName, "Immediate", "1 Integration Road", @@ -929,21 +943,10 @@ await CreateMerchantAsync( "United Kingdom", "Test Contact", "test.contact@example.com", - "01234567890"); + "01234567890")); } - public async Task CreateMerchantAsync( - string merchantName, - string settlementSchedule, - string addressLine1, - string? addressLine2, - string town, - string region, - string postCode, - string country, - string contactName, - string emailAddress, - string phoneNumber) + public async Task CreateMerchantAsync(MerchantCreationInput merchant) { await RunWithFailureArtifactsAsync(async () => { @@ -959,20 +962,20 @@ await RunWithFailureArtifactsAsync(async () => await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); - await _page.Locator("input[placeholder='Enter merchant name']").FillAsync(merchantName); - await _page.Locator("select[name='SettlementSchedule']").SelectOptionAsync(settlementSchedule); - await _page.Locator("input[placeholder='Enter address line 1']").FillAsync(addressLine1); - await _page.Locator("input[placeholder='Enter address line 2 (optional)']").FillAsync(addressLine2 ?? string.Empty); - await _page.Locator("input[placeholder='Enter town']").FillAsync(town); - await _page.Locator("input[placeholder='Enter region']").FillAsync(region); - await _page.Locator("input[placeholder='Enter postcode']").FillAsync(postCode); + await _page.Locator("input[placeholder='Enter merchant name']").FillAsync(merchant.MerchantName); + await _page.Locator(SettlementScheduleSelector).SelectOptionAsync(merchant.SettlementSchedule); + await _page.Locator("input[placeholder='Enter address line 1']").FillAsync(merchant.AddressLine1); + await _page.Locator("input[placeholder='Enter address line 2 (optional)']").FillAsync(merchant.AddressLine2 ?? string.Empty); + await _page.Locator("input[placeholder='Enter town']").FillAsync(merchant.Town); + await _page.Locator("input[placeholder='Enter region']").FillAsync(merchant.Region); + await _page.Locator("input[placeholder='Enter postcode']").FillAsync(merchant.PostCode); await _page.GetByRole(AriaRole.Button, new() { Name = "Select country" }).ClickAsync(); - await _page.GetByRole(AriaRole.Button, new() { Name = country }).ClickAsync(); + await _page.GetByRole(AriaRole.Button, new() { Name = merchant.Country }).ClickAsync(); - await _page.Locator("input[placeholder='Enter contact name']").FillAsync(contactName); - await _page.Locator("input[placeholder='Enter email address']").FillAsync(emailAddress); - await _page.Locator("input[placeholder='Enter phone number']").FillAsync(phoneNumber); + await _page.Locator("input[placeholder='Enter contact name']").FillAsync(merchant.ContactName); + await _page.Locator("input[placeholder='Enter email address']").FillAsync(merchant.EmailAddress); + await _page.Locator("input[placeholder='Enter phone number']").FillAsync(merchant.PhoneNumber); await _page.Locator("#createMerchantButton").ClickAsync(); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); @@ -1029,7 +1032,7 @@ await heading.WaitForAsync(new LocatorWaitForOptions (await heading.IsVisibleAsync()).ShouldBeTrue(); (await _page.GetByRole(AriaRole.Button, new() { Name = "View Schedule" }).IsVisibleAsync()).ShouldBeTrue(); - (await _page.GetByRole(AriaRole.Button, new() { Name = "Back to List" }).IsVisibleAsync()).ShouldBeTrue(); + (await _page.GetByRole(AriaRole.Button, new() { Name = BackToListText }).IsVisibleAsync()).ShouldBeTrue(); }, nameof(AssertMerchantViewVisibleAsync)); } @@ -1099,7 +1102,7 @@ await RunWithFailureArtifactsAsync(async () => var merchantId = ExtractMerchantIdFromUrl(_page.Url); if (merchantId == Guid.Empty) { - await _page.GotoAsync(ResolveBaseUrl() + "/merchants"); + await _page.GotoAsync(ResolveBaseUrl() + MerchantsPath); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); var row = GetMerchantRow(merchantName); @@ -1113,7 +1116,7 @@ await row.WaitForAsync(new LocatorWaitForOptions } else { - await _page.GotoAsync($"{ResolveBaseUrl()}/merchants/{merchantId}/edit"); + await _page.GotoAsync($"{ResolveBaseUrl()}{MerchantsPath}/{merchantId}/edit"); } await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); @@ -1152,38 +1155,24 @@ await _page.GetByText("Enter merchant opening and closing times in HHmm format") }, nameof(AssertMerchantEditOpeningHoursVisibleAsync)); } - public async Task SaveMerchantOpeningHoursAsync( - string mondayOpening, - string mondayClosing, - string tuesdayOpening, - string tuesdayClosing, - string wednesdayOpening, - string wednesdayClosing, - string thursdayOpening, - string thursdayClosing, - string fridayOpening, - string fridayClosing, - string saturdayOpening, - string saturdayClosing, - string sundayOpening, - string sundayClosing) - { - await RunWithFailureArtifactsAsync(async () => - { - await _page.Locator("#mondayOpening").FillAsync(mondayOpening); - await _page.Locator("#mondayClosing").FillAsync(mondayClosing); - await _page.Locator("#tuesdayOpening").FillAsync(tuesdayOpening); - await _page.Locator("#tuesdayClosing").FillAsync(tuesdayClosing); - await _page.Locator("#wednesdayOpening").FillAsync(wednesdayOpening); - await _page.Locator("#wednesdayClosing").FillAsync(wednesdayClosing); - await _page.Locator("#thursdayOpening").FillAsync(thursdayOpening); - await _page.Locator("#thursdayClosing").FillAsync(thursdayClosing); - await _page.Locator("#fridayOpening").FillAsync(fridayOpening); - await _page.Locator("#fridayClosing").FillAsync(fridayClosing); - await _page.Locator("#saturdayOpening").FillAsync(saturdayOpening); - await _page.Locator("#saturdayClosing").FillAsync(saturdayClosing); - await _page.Locator("#sundayOpening").FillAsync(sundayOpening); - await _page.Locator("#sundayClosing").FillAsync(sundayClosing); + public async Task SaveMerchantOpeningHoursAsync(MerchantOpeningHoursInput openingHours) + { + await RunWithFailureArtifactsAsync(async () => + { + await _page.Locator("#mondayOpening").FillAsync(openingHours.MondayOpening); + await _page.Locator("#mondayClosing").FillAsync(openingHours.MondayClosing); + await _page.Locator("#tuesdayOpening").FillAsync(openingHours.TuesdayOpening); + await _page.Locator("#tuesdayClosing").FillAsync(openingHours.TuesdayClosing); + await _page.Locator("#wednesdayOpening").FillAsync(openingHours.WednesdayOpening); + await _page.Locator("#wednesdayClosing").FillAsync(openingHours.WednesdayClosing); + await _page.Locator("#thursdayOpening").FillAsync(openingHours.ThursdayOpening); + await _page.Locator("#thursdayClosing").FillAsync(openingHours.ThursdayClosing); + await _page.Locator("#fridayOpening").FillAsync(openingHours.FridayOpening); + await _page.Locator("#fridayClosing").FillAsync(openingHours.FridayClosing); + await _page.Locator("#saturdayOpening").FillAsync(openingHours.SaturdayOpening); + await _page.Locator("#saturdayClosing").FillAsync(openingHours.SaturdayClosing); + await _page.Locator("#sundayOpening").FillAsync(openingHours.SundayOpening); + await _page.Locator("#sundayClosing").FillAsync(openingHours.SundayClosing); await _page.Locator("#saveOpeningHoursButton").ClickAsync(); await _page.GetByText("Merchant opening hours updated successfully").WaitForAsync(new LocatorWaitForOptions @@ -1200,17 +1189,17 @@ await RunWithFailureArtifactsAsync(async () => { await _page.Locator("#addOperatorButton").ClickAsync(); - var option = _page.Locator("select option").Filter(new() { HasText = operatorName }); + var option = _page.Locator(SelectOptionSelector).Filter(new() { HasText = operatorName }); await option.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Attached, Timeout = 10000 }); - var optionValue = await option.First.GetAttributeAsync("value"); + var optionValue = await option.First.GetAttributeAsync(ValueAttributeName); optionValue.ShouldNotBeNull(); - await _page.Locator("select").SelectOptionAsync(new[] { optionValue! }); + await _page.Locator(SelectSelector).SelectOptionAsync(new[] { optionValue! }); var merchantNumberInput = _page.GetByPlaceholder("Enter merchant number"); await merchantNumberInput.WaitForAsync(new LocatorWaitForOptions @@ -1237,7 +1226,7 @@ public async Task AssertMerchantOperatorVisibleAsync(string operatorName) { await RunWithFailureArtifactsAsync(async () => { - var row = _page.Locator("div.flex.items-center.justify-between.p-3.bg-gray-50.rounded-lg") + var row = _page.Locator(MerchantRowSelector) .Filter(new() { HasText = operatorName }); await row.First.WaitForAsync(new LocatorWaitForOptions { @@ -1279,7 +1268,7 @@ await deviceInput.WaitForAsync(new LocatorWaitForOptions await deviceInput.PressAsync("Tab"); await _page.GetByRole(AriaRole.Button, new() { Name = "Add", Exact = true }).ClickAsync(); - var row = _page.Locator("div.flex.items-center.justify-between.p-3.bg-gray-50.rounded-lg") + var row = _page.Locator(MerchantRowSelector) .Filter(new() { HasText = deviceIdentifier }); await row.First.WaitForAsync(new LocatorWaitForOptions { @@ -1293,7 +1282,7 @@ public async Task AssertMerchantDeviceVisibleAsync(string deviceIdentifier) { await RunWithFailureArtifactsAsync(async () => { - var row = _page.Locator("div.flex.items-center.justify-between.p-3.bg-gray-50.rounded-lg") + var row = _page.Locator(MerchantRowSelector) .Filter(new() { HasText = deviceIdentifier }); await row.First.WaitForAsync(new LocatorWaitForOptions { @@ -1317,7 +1306,7 @@ await RunWithFailureArtifactsAsync(async () => return; } - await _page.GotoAsync($"{ResolveBaseUrl()}/merchants/{merchantId}/schedule"); + await _page.GotoAsync($"{ResolveBaseUrl()}{MerchantsPath}/{merchantId}/schedule"); }, nameof(OpenMerchantScheduleFromEditAsync)); } @@ -1435,7 +1424,7 @@ await RunWithFailureArtifactsAsync(async () => var merchantId = ExtractMerchantIdFromUrl(_page.Url); if (merchantId == Guid.Empty) { - await _page.GotoAsync(ResolveBaseUrl() + "/merchants"); + await _page.GotoAsync(ResolveBaseUrl() + MerchantsPath); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); var row = _page.Locator("tbody tr").First; @@ -1449,7 +1438,7 @@ await row.WaitForAsync(new LocatorWaitForOptions } else { - await _page.GotoAsync($"{ResolveBaseUrl()}/merchants/{merchantId}/deposit"); + await _page.GotoAsync($"{ResolveBaseUrl()}{MerchantsPath}/{merchantId}/deposit"); } await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); @@ -1460,7 +1449,7 @@ public async Task OpenMerchantDepositAsync(string merchantName) { await RunWithFailureArtifactsAsync(async () => { - await _page.GotoAsync(ResolveBaseUrl() + "/merchants"); + await _page.GotoAsync(ResolveBaseUrl() + MerchantsPath); await _page.WaitForLoadStateAsync(LoadState.DOMContentLoaded); var row = GetMerchantRow(merchantName); @@ -1528,7 +1517,7 @@ public async Task SubmitMerchantDepositAsync(decimal amount, DateTime date, stri await RunWithFailureArtifactsAsync(async () => { await _page.Locator("#depositAmount").FillAsync(amount.ToString(CultureInfo.InvariantCulture)); - await _page.Locator("#depositDate").FillAsync(date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + await _page.Locator("#depositDate").FillAsync(date.ToString(DateFormat, CultureInfo.InvariantCulture)); await _page.Locator("#depositReference").FillAsync(reference); await _page.Locator("#makeDepositButton").ClickAsync(); await _page.GetByText("Deposit recorded successfully").WaitForAsync(new LocatorWaitForOptions @@ -1565,7 +1554,7 @@ public async Task AssertHomePageVisibleAsync() await RunWithFailureArtifactsAsync(async () => { (await _page.TitleAsync()).ShouldBe("Welcome - Estate Management"); - (await _page.Locator("#loginButton").IsVisibleAsync()).ShouldBeTrue(); + (await _page.Locator(LoginButtonSelector).IsVisibleAsync()).ShouldBeTrue(); }, nameof(AssertHomePageVisibleAsync)); } @@ -1780,7 +1769,7 @@ await spinner.First.WaitForAsync(new LocatorWaitForOptions private ILocator GetAssignedOperatorRow(string operatorName) { - return _page.Locator("div.flex.items-center.justify-between.p-3.bg-gray-50.rounded-lg") + return _page.Locator(MerchantRowSelector) .Filter(new() { HasText = operatorName }); } @@ -1879,7 +1868,7 @@ private async Task SelectFirstRealOptionAsync(string selectSelector) for (var index = 0; index < optionCount; index++) { var option = options.Nth(index); - var value = await option.GetAttributeAsync("value"); + var value = await option.GetAttributeAsync(ValueAttributeName); if (string.IsNullOrWhiteSpace(value)) { continue; @@ -1911,7 +1900,7 @@ private async Task WaitForLoginScreenAsync() while (DateTime.UtcNow < deadline) { if (_page.Url.Contains("/login", StringComparison.OrdinalIgnoreCase) || - await _page.Locator("#loginUsername").IsVisibleAsync()) + await _page.Locator(LoginUsernameSelector).IsVisibleAsync()) { return; } @@ -1999,4 +1988,33 @@ private async Task CaptureDebugArtifactsAsync(string context, Exception exceptio Console.WriteLine($"Failed to capture debug artifacts: {captureException.Message}"); } } + + public sealed record MerchantOpeningHoursInput( + string MondayOpening, + string MondayClosing, + string TuesdayOpening, + string TuesdayClosing, + string WednesdayOpening, + string WednesdayClosing, + string ThursdayOpening, + string ThursdayClosing, + string FridayOpening, + string FridayClosing, + string SaturdayOpening, + string SaturdayClosing, + string SundayOpening, + string SundayClosing); + + public sealed record MerchantCreationInput( + string MerchantName, + string SettlementSchedule, + string AddressLine1, + string? AddressLine2, + string Town, + string Region, + string PostCode, + string Country, + string ContactName, + string EmailAddress, + string PhoneNumber); } diff --git a/EstateManagementUI.BlazorFastIntegrationTests/Common/LocalAppHost.cs b/EstateManagementUI.BlazorFastIntegrationTests/Common/LocalAppHost.cs index 08b1947f..c350e63c 100644 --- a/EstateManagementUI.BlazorFastIntegrationTests/Common/LocalAppHost.cs +++ b/EstateManagementUI.BlazorFastIntegrationTests/Common/LocalAppHost.cs @@ -29,7 +29,6 @@ public async Task StartAsync() var startInfo = new ProcessStartInfo { FileName = "dotnet", - Arguments = $"run --no-build --project \"{_projectPath}\"", WorkingDirectory = Path.GetDirectoryName(_projectPath)!, UseShellExecute = false, RedirectStandardOutput = true, @@ -37,6 +36,11 @@ public async Task StartAsync() CreateNoWindow = true }; + startInfo.ArgumentList.Add("run"); + startInfo.ArgumentList.Add("--no-build"); + startInfo.ArgumentList.Add("--project"); + startInfo.ArgumentList.Add(_projectPath); + startInfo.Environment["ASPNETCORE_ENVIRONMENT"] = "Test"; startInfo.Environment["AppSettings__TestMode"] = "BackedByTestDataStore"; startInfo.Environment["ASPNETCORE_URLS"] = BaseUri.ToString(); diff --git a/EstateManagementUI.BlazorFastIntegrationTests/Common/ReportingPageHelper.cs b/EstateManagementUI.BlazorFastIntegrationTests/Common/ReportingPageHelper.cs index 8fe6378d..f8e03cf6 100644 --- a/EstateManagementUI.BlazorFastIntegrationTests/Common/ReportingPageHelper.cs +++ b/EstateManagementUI.BlazorFastIntegrationTests/Common/ReportingPageHelper.cs @@ -9,6 +9,11 @@ namespace EstateManagementUI.IntegrationTests.Common; public sealed class ReportingPageHelper { + private const string TableRowsSelector = "table tbody tr"; + private const string ValueText = "Value"; + private const string ValueColumnName = ValueText; + private const string DateFormat = "yyyy-MM-dd"; + private readonly IPage _page; public ReportingPageHelper(IPage page) @@ -89,7 +94,7 @@ public async Task AssertTransactionDetailSummaryValuesAsync(DataTable table) public async Task AssertTransactionDetailRowsAsync(DataTable table) { - var rows = _page.Locator("table tbody tr"); + var rows = _page.Locator(TableRowsSelector); (await rows.CountAsync()).ShouldBe(table.Rows.Count); for (var index = 0; index < table.Rows.Count; index++) @@ -116,7 +121,7 @@ public async Task AssertMerchantSummaryValuesAsync(DataTable table) public async Task AssertMerchantSummaryRowsAsync(DataTable table) { - var rows = _page.Locator("table tbody tr"); + var rows = _page.Locator(TableRowsSelector); (await rows.CountAsync()).ShouldBe(table.Rows.Count); for (var index = 0; index < table.Rows.Count; index++) @@ -145,7 +150,7 @@ public async Task AssertOperatorSummaryValuesAsync(DataTable table) public async Task AssertOperatorSummaryRowsAsync(DataTable table) { - var rows = _page.Locator("table tbody tr"); + var rows = _page.Locator(TableRowsSelector); (await rows.CountAsync()).ShouldBe(table.Rows.Count); for (var index = 0; index < table.Rows.Count; index++) @@ -174,7 +179,7 @@ public async Task AssertProductPerformanceSummaryValuesAsync(DataTable table) public async Task AssertProductPerformanceRowsAsync(DataTable table) { - var rows = _page.Locator("table tbody tr"); + var rows = _page.Locator(TableRowsSelector); (await rows.CountAsync()).ShouldBe(table.Rows.Count); for (var index = 0; index < table.Rows.Count; index++) @@ -245,7 +250,7 @@ public async Task AssertAnalyticalChartsSummaryValuesAsync(DataTable table) foreach (var row in table.Rows) { var label = ReqnrollTableHelper.GetStringRowValue(row, "Label"); - var value = ReqnrollTableHelper.GetStringRowValue(row, "Value"); + var value = ReqnrollTableHelper.GetStringRowValue(row, ValueColumnName); if (IsMoneyLabel(label)) { @@ -261,7 +266,7 @@ public async Task AssertAnalyticalChartsSummaryValuesAsync(DataTable table) public async Task AssertAnalyticalChartsComparisonDateAsync(string comparisonDate) { var dateToSelect = ResolveComparisonDateValue(comparisonDate); - var expectedLabel = DateTime.ParseExact(dateToSelect, "yyyy-MM-dd", CultureInfo.InvariantCulture).ToString("MMM dd", CultureInfo.InvariantCulture); + var expectedLabel = DateTime.ParseExact(dateToSelect, DateFormat, CultureInfo.InvariantCulture).ToString("MMM dd", CultureInfo.InvariantCulture); await _page.Locator("#comparisonDateSelector").SelectOptionAsync(new SelectOptionValue { Value = dateToSelect }); await WaitForAnalyticalChartsReadyAsync(); @@ -289,7 +294,7 @@ public async Task AssertAnalyticalChartsChartTotalsAsync(DataTable table) volumeChart.TotalTodayCount.ShouldBe((int)today); volumeChart.TotalComparisonCount.ShouldBe((int)comparison); } - else if (chart.Equals("Value", StringComparison.OrdinalIgnoreCase)) + else if (chart.Equals(ValueText, StringComparison.OrdinalIgnoreCase)) { var valueChart = await ReadChartAsync("valueChart"); valueChart.TotalTodayValue.ShouldBe(today); @@ -318,7 +323,7 @@ public async Task AssertAnalyticalChartsHourlyPointsAsync(DataTable table) { AssertChartSeriesPoint(volumeChart, hour, today, comparison); } - else if (chart.Equals("Value", StringComparison.OrdinalIgnoreCase)) + else if (chart.Equals(ValueText, StringComparison.OrdinalIgnoreCase)) { AssertChartSeriesPoint(valueChart, hour, today, comparison); } @@ -360,7 +365,7 @@ private string ResolveBaseUrl() private static bool IsMoneyLabel(string label) { - return label.Contains("Value", StringComparison.OrdinalIgnoreCase) || + return label.Contains(ValueText, StringComparison.OrdinalIgnoreCase) || label.Contains("Amount", StringComparison.OrdinalIgnoreCase) || label.Contains("Fee", StringComparison.OrdinalIgnoreCase) || label.Contains("Settlement", StringComparison.OrdinalIgnoreCase) || @@ -461,7 +466,7 @@ private async Task AssertSummaryValuesAsync(DataTable table) foreach (var row in table.Rows) { var label = ReqnrollTableHelper.GetStringRowValue(row, "Label"); - var value = ReqnrollTableHelper.GetStringRowValue(row, "Value"); + var value = ReqnrollTableHelper.GetStringRowValue(row, ValueColumnName); if (IsMoneyLabel(label)) { @@ -500,13 +505,13 @@ private async Task AssertCellTextAsync(ILocator row, int columnIndex, string exp private static string ResolveComparisonDateValue(string comparisonDate) { - if (DateOnly.TryParseExact(comparisonDate, "yyyy-MM-dd", out var date)) + if (DateOnly.TryParseExact(comparisonDate, DateFormat, out var date)) { - return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + return date.ToString(DateFormat, CultureInfo.InvariantCulture); } var resolved = ReqnrollTableHelper.GetDateForDateString(comparisonDate, DateTime.Now); - return resolved.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + return resolved.ToString(DateFormat, CultureInfo.InvariantCulture); } private static void AssertChartSeriesPoint(ChartSeriesSnapshot chart, string hour, decimal expectedToday, decimal expectedComparison) diff --git a/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs b/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs index fe162aaf..6f65ce14 100644 --- a/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs +++ b/EstateManagementUI.BlazorServer/Common/TestAuthenticationHandler.cs @@ -12,6 +12,7 @@ namespace EstateManagementUI.BlazorServer.Common; /// public class TestAuthenticationHandler : AuthenticationHandler { + private const string TestUserRoleKey = "TestUserRole"; public static readonly string SchemeName = "TestAuthentication"; private readonly IConfiguration _configuration; private readonly IHttpContextAccessor _httpContextAccessor; @@ -32,7 +33,7 @@ protected override Task HandleAuthenticateAsync() { // Check for role switch in query parameter string? roleName = null; - if (_httpContextAccessor.HttpContext?.Request.Cookies.TryGetValue("TestUserRole", out var cookieRole) == true) + if (_httpContextAccessor.HttpContext?.Request.Cookies.TryGetValue(TestUserRoleKey, out var cookieRole) == true) { roleName = cookieRole; } @@ -41,9 +42,9 @@ protected override Task HandleAuthenticateAsync() { roleName = roleQuery.ToString(); // Store in session for persistence - _httpContextAccessor.HttpContext.Session.SetString("TestUserRole", roleName); + _httpContextAccessor.HttpContext.Session.SetString(TestUserRoleKey, roleName); _httpContextAccessor.HttpContext.Response.Cookies.Append( - "TestUserRole", + TestUserRoleKey, roleName, new CookieOptions { diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor index f24da40f..7d288300 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Contracts/Edit.razor @@ -110,7 +110,7 @@

@product.ProductName

-