diff --git a/EstateManagementUI.IntegrationTests/Common/ContractManagementPageHelper.cs b/EstateManagementUI.IntegrationTests/Common/ContractManagementPageHelper.cs new file mode 100644 index 00000000..ec922780 --- /dev/null +++ b/EstateManagementUI.IntegrationTests/Common/ContractManagementPageHelper.cs @@ -0,0 +1,661 @@ +using Microsoft.Playwright; +using Shouldly; + +namespace EstateManagementUI.IntegrationTests.Common; + +/// +/// Helper class for interacting with the Contract Management pages using Playwright +/// +public class ContractManagementPageHelper +{ + private readonly IPage _page; + private readonly string _baseUrl; + + public ContractManagementPageHelper(IPage page, string baseUrl) + { + _page = page; + _baseUrl = baseUrl; + } + + #region Navigation + + /// + /// Navigate to the Contract Management page (index) + /// + public async Task NavigateToContractManagement() + { + await _page.GotoAsync($"{_baseUrl}/contracts"); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Navigate to home page to check menu visibility + /// + public async Task NavigateToHome() + { + await _page.GotoAsync(_baseUrl); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Navigate to the Create New Contract page + /// + public async Task NavigateToCreateNewContract() + { + await _page.GotoAsync($"{_baseUrl}/contracts/new"); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Navigate to the Edit Contract page for a specific contract + /// + public async Task NavigateToEditContract(string contractId) + { + await _page.GotoAsync($"{_baseUrl}/contracts/{contractId}/edit"); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Navigate to the Edit Contract page for the first contract (using hardcoded test data ID) + /// + public async Task NavigateToEditFirstContract() + { + // Using the hardcoded test data contract ID from StubbedMediatorService + await NavigateToEditContract("44444444-4444-4444-4444-444444444444"); + } + + #endregion + + #region Menu Visibility Verification + + /// + /// Verify that Contract Management menu is not visible (Administrator role) + /// + public async Task VerifyContractManagementMenuIsNotVisible() + { + // Wait for page to be in stable state + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var menuLinkCount = await _page.Locator("text=Contract Management").CountAsync(); + menuLinkCount.ShouldBe(0, "Contract Management menu should not be visible for Administrator role"); + } + + /// + /// Verify that Contract Management menu is visible + /// + public async Task VerifyContractManagementMenuIsVisible() + { + await _page.Locator("text=Contract Management").WaitForAsync(); + var menuLinkCount = await _page.Locator("text=Contract Management").CountAsync(); + menuLinkCount.ShouldBeGreaterThan(0, "Contract Management menu should be visible"); + } + + #endregion + + #region Page Verification + + /// + /// Verify the Contract Management page is displayed + /// + public async Task VerifyContractManagementPageIsDisplayed() + { + await _page.Locator("h1:has-text('Contract Management')").WaitForAsync(); + var heading = await _page.Locator("h1:has-text('Contract Management')").TextContentAsync(); + heading.ShouldNotBeNull(); + heading.ShouldContain("Contract Management"); + } + + /// + /// Verify the page title + /// + public async Task VerifyPageTitle(string expectedTitle) + { + var title = await _page.TitleAsync(); + title.ShouldBe(expectedTitle); + } + + /// + /// Verify the View Contract page is displayed + /// + public async Task VerifyViewContractPageIsDisplayed() + { + await _page.Locator("h1:has-text('View Contract')").WaitForAsync(); + var heading = await _page.Locator("h1").TextContentAsync(); + heading.ShouldNotBeNull(); + heading.ShouldContain("View Contract"); + } + + /// + /// Verify the Create New Contract page is displayed + /// + public async Task VerifyCreateNewContractPageIsDisplayed() + { + await _page.Locator("h1:has-text('Create New Contract')").WaitForAsync(); + var heading = await _page.Locator("h1:has-text('Create New Contract')").TextContentAsync(); + heading.ShouldNotBeNull(); + heading.ShouldContain("Create New Contract"); + } + + /// + /// Verify the Edit Contract page is displayed + /// + public async Task VerifyEditContractPageIsDisplayed() + { + await _page.Locator("h1:has-text('Edit Contract')").WaitForAsync(); + var heading = await _page.Locator("h1").TextContentAsync(); + heading.ShouldNotBeNull(); + heading.ShouldContain("Edit Contract"); + } + + /// + /// Verify access denied message is displayed + /// + public async Task VerifyAccessDeniedMessageIsDisplayed() + { + await _page.Locator("h3:has-text('Access Denied')").WaitForAsync(); + var message = await _page.Locator("h3:has-text('Access Denied')").TextContentAsync(); + message.ShouldNotBeNull(); + message.ShouldContain("Access Denied"); + } + + /// + /// Verify access denied message for contract creation + /// + public async Task VerifyAccessDeniedForContractCreation() + { + await _page.Locator("text=You don't have permission to create contracts").WaitForAsync(); + } + + /// + /// Verify access denied message for contract editing + /// + public async Task VerifyAccessDeniedForContractEditing() + { + await _page.Locator("text=You don't have permission to edit contracts").WaitForAsync(); + } + + #endregion + + #region Contract List Verification + + /// + /// Verify the number of contracts displayed in the list + /// + public async Task VerifyContractCount(int expectedCount) + { + // Wait for contracts grid to be present + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + var contractCards = _page.Locator(".bg-white.rounded-lg.shadow-md.p-6.hover\\:shadow-lg"); + var count = await contractCards.CountAsync(); + count.ShouldBe(expectedCount, $"Expected {expectedCount} contracts but found {count}"); + } + + /// + /// Verify the "Add New Contract" button is visible + /// + public async Task VerifyAddNewContractButtonIsVisible() + { + await _page.Locator("#newContractButton").WaitForAsync(); + var button = await _page.Locator("#newContractButton").IsVisibleAsync(); + button.ShouldBeTrue("Add New Contract button should be visible"); + } + + /// + /// Verify the "Add New Contract" button is not visible + /// + public async Task VerifyAddNewContractButtonIsNotVisible() + { + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var buttonCount = await _page.Locator("#newContractButton").CountAsync(); + buttonCount.ShouldBe(0, "Add New Contract button should not be visible"); + } + + /// + /// Verify Edit button is not visible for contracts (Viewer role) + /// + public async Task VerifyEditButtonIsNotVisibleForContracts() + { + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var editButtonCount = await _page.Locator("button:has-text('Edit')").CountAsync(); + editButtonCount.ShouldBe(0, "Edit button should not be visible for contracts"); + } + + #endregion + + #region Contract Details Verification + + /// + /// Verify contract description on view page + /// + public async Task VerifyContractDescription(string expectedDescription) + { + var descriptionElement = _page.Locator("p.text-gray-900.font-medium").First; + var description = await descriptionElement.TextContentAsync(); + description.ShouldNotBeNull(); + description.Trim().ShouldBe(expectedDescription); + } + + /// + /// Verify contract operator on view page + /// + public async Task VerifyContractOperator(string expectedOperator) + { + var operatorElements = _page.Locator("p.text-gray-900.font-medium"); + var operatorText = await operatorElements.Nth(1).TextContentAsync(); + operatorText.ShouldNotBeNull(); + operatorText.Trim().ShouldBe(expectedOperator); + } + + /// + /// Verify the number of products in a contract + /// + public async Task VerifyContractProductCount(int expectedCount) + { + var productCards = _page.Locator(".border.border-gray-200.rounded-lg.p-4"); + var count = await productCards.CountAsync(); + count.ShouldBe(expectedCount, $"Expected {expectedCount} products but found {count}"); + } + + /// + /// Verify the first product name + /// + public async Task VerifyFirstProductName(string expectedName) + { + var productNameElement = _page.Locator("h4.text-md.font-semibold.text-gray-900").First; + var productName = await productNameElement.TextContentAsync(); + productName.ShouldNotBeNull(); + productName.Trim().ShouldBe(expectedName); + } + + /// + /// Verify the number of transaction fees for the first product + /// + public async Task VerifyFirstProductTransactionFeeCount(int expectedCount) + { + var feeCards = _page.Locator(".bg-gray-50.rounded.p-3"); + var count = await feeCards.CountAsync(); + count.ShouldBe(expectedCount, $"Expected {expectedCount} transaction fees but found {count}"); + } + + /// + /// Verify a specific transaction fee exists with expected value + /// + public async Task VerifyProductTransactionFee(string productName, string feeDescription, string expectedValue) + { + var feeCard = _page.Locator($".bg-gray-50.rounded.p-3:has-text('{feeDescription}')"); + await feeCard.WaitForAsync(); + + var feeText = await feeCard.TextContentAsync(); + feeText.ShouldNotBeNull(); + feeText.ShouldContain(feeDescription); + feeText.ShouldContain(expectedValue); + } + + #endregion + + #region Form Verification + + /// + /// Verify the contract form is displayed + /// + public async Task VerifyContractFormIsDisplayed() + { + await _page.Locator("form").WaitForAsync(); + var form = await _page.Locator("form").IsVisibleAsync(); + form.ShouldBeTrue("Contract form should be displayed"); + } + + /// + /// Verify a specific field is visible + /// + public async Task VerifyFieldIsVisible(string fieldLabel) + { + var field = _page.Locator($"label:has-text('{fieldLabel}')"); + await field.WaitForAsync(); + var isVisible = await field.IsVisibleAsync(); + isVisible.ShouldBeTrue($"{fieldLabel} field should be visible"); + } + + /// + /// Verify description field contains specific value on edit page + /// + public async Task VerifyDescriptionFieldContains(string expectedValue) + { + var descriptionInput = _page.Locator("input[type='text']").First; + var value = await descriptionInput.InputValueAsync(); + value.ShouldBe(expectedValue); + } + + /// + /// Verify operator name is displayed on edit page + /// + public async Task VerifyOperatorNameDisplayed(string expectedOperator) + { + var operatorText = await _page.Locator("p.text-gray-900.font-medium").First.TextContentAsync(); + operatorText.ShouldNotBeNull(); + operatorText.Trim().ShouldBe(expectedOperator); + } + + /// + /// Verify products section is displayed on edit page + /// + public async Task VerifyProductsSectionIsDisplayed() + { + await _page.Locator("h3:has-text('Products')").WaitForAsync(); + } + + /// + /// Verify the number of products listed on edit page + /// + public async Task VerifyProductCountOnEditPage(int expectedCount) + { + var productCards = _page.Locator(".border.border-gray-200.rounded-lg.p-4"); + var count = await productCards.CountAsync(); + count.ShouldBe(expectedCount, $"Expected {expectedCount} products but found {count}"); + } + + /// + /// Verify Add Product button is visible + /// + public async Task VerifyAddProductButtonIsVisible() + { + var button = _page.Locator("button:has-text('Add Product')"); + await button.WaitForAsync(); + var isVisible = await button.IsVisibleAsync(); + isVisible.ShouldBeTrue("Add Product button should be visible"); + } + + /// + /// Verify validation errors are displayed + /// + public async Task VerifyValidationErrorsAreDisplayed() + { + var errors = _page.Locator(".text-red-600.text-sm"); + var count = await errors.CountAsync(); + count.ShouldBeGreaterThan(0, "Validation errors should be displayed"); + } + + /// + /// Verify specific validation error is shown + /// + public async Task VerifyValidationErrorMessage(string errorMessage) + { + await _page.Locator($"text={errorMessage}").WaitForAsync(); + } + + #endregion + + #region Interaction Methods + + /// + /// Click on View button for the first contract + /// + public async Task ClickViewButtonForFirstContract() + { + var viewButton = _page.Locator("button:has-text('View')").First; + await viewButton.ClickAsync(); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Click on Edit button for the first contract + /// + public async Task ClickEditButtonForFirstContract() + { + var editButton = _page.Locator("button:has-text('Edit')").First; + await editButton.ClickAsync(); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Click on Add New Contract button + /// + public async Task ClickAddNewContractButton() + { + await _page.Locator("#newContractButton").ClickAsync(); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + } + + /// + /// Enter text in a form field + /// + public async Task EnterTextInField(string fieldLabel, string text) + { + // Find the input associated with the label + var input = _page.Locator($"label:has-text('{fieldLabel}')").Locator("..").Locator("input"); + await input.FillAsync(text); + } + + /// + /// Select an option from a dropdown + /// + public async Task SelectFromDropdown(string fieldLabel, string option) + { + var select = _page.Locator($"label:has-text('{fieldLabel}')").Locator("..").Locator("select"); + await select.SelectOptionAsync(new[] { option }); + } + + /// + /// Click on a button by its text or ID + /// + public async Task ClickButton(string buttonText) + { + var button = _page.Locator($"button:has-text('{buttonText}')").First; + await button.ClickAsync(); + await Task.Delay(500); // Wait for any state changes + } + + /// + /// Click on Create Contract button (with ID) + /// + public async Task ClickCreateContractButton() + { + await _page.Locator("#createContractButton").ClickAsync(); + await Task.Delay(500); + } + + /// + /// Verify user is redirected to Contract Management page + /// + public async Task VerifyRedirectedToContractManagementPage() + { + await _page.WaitForURLAsync("**/contracts"); + var url = _page.Url; + url.ShouldEndWith("/contracts"); + } + + /// + /// Verify contract is created successfully + /// + public async Task VerifyContractCreatedSuccessfully() + { + // After creation, we should be on the contracts list page + await VerifyRedirectedToContractManagementPage(); + } + + /// + /// Click Add Product button + /// + public async Task ClickAddProductButton() + { + var button = _page.Locator("button:has-text('Add Product')").First; + await button.ClickAsync(); + await Task.Delay(300); + } + + /// + /// Verify Add Product modal is displayed + /// + public async Task VerifyAddProductModalIsDisplayed() + { + await _page.Locator("h3:has-text('Add New Product')").WaitForAsync(); + } + + /// + /// Enter text in product name field + /// + public async Task EnterProductName(string productName) + { + var input = _page.Locator("label:has-text('Product Name')").Locator("..").Locator("input"); + await input.FillAsync(productName); + } + + /// + /// Enter text in display text field + /// + public async Task EnterDisplayText(string displayText) + { + var input = _page.Locator("label:has-text('Display Text')").Locator("..").Locator("input"); + await input.FillAsync(displayText); + } + + /// + /// Enter value in the value field + /// + public async Task EnterValue(string value) + { + var input = _page.Locator("label:has-text('Value')").Locator("..").Locator("input"); + await input.FillAsync(value); + } + + /// + /// Check the Variable Value checkbox + /// + public async Task CheckVariableValueCheckbox() + { + var checkbox = _page.Locator("input[type='checkbox']"); + await checkbox.CheckAsync(); + } + + /// + /// Click Add Product button in modal + /// + public async Task ClickAddProductButtonInModal() + { + var button = _page.Locator(".bg-white.rounded-lg.p-6").Locator("button:has-text('Add Product')"); + await button.ClickAsync(); + await Task.Delay(500); + } + + /// + /// Verify product is added successfully + /// + public async Task VerifyProductAddedSuccessfully() + { + // Wait for modal to close + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var modalCount = await _page.Locator("h3:has-text('Add New Product')").CountAsync(); + modalCount.ShouldBe(0, "Add Product modal should be closed after successful addition"); + } + + /// + /// Verify Add Product modal is closed + /// + public async Task VerifyAddProductModalIsClosed() + { + var modalCount = await _page.Locator("h3:has-text('Add New Product')").CountAsync(); + modalCount.ShouldBe(0, "Add Product modal should be closed"); + } + + /// + /// Click Add Fee button for the first product + /// + public async Task ClickAddFeeButtonForFirstProduct() + { + var button = _page.Locator("button:has-text('Add Fee')").First; + await button.ClickAsync(); + await Task.Delay(300); + } + + /// + /// Verify Add Transaction Fee modal is displayed + /// + public async Task VerifyAddTransactionFeeModalIsDisplayed() + { + await _page.Locator("h3:has-text('Add Transaction Fee')").WaitForAsync(); + } + + /// + /// Enter text in fee description field + /// + public async Task EnterFeeDescription(string description) + { + var input = _page.Locator("label:has-text('Description')").Locator("..").Locator("input"); + await input.FillAsync(description); + } + + /// + /// Select calculation type from dropdown + /// + public async Task SelectCalculationType(string calculationType) + { + var select = _page.Locator("label:has-text('Calculation Type')").Locator("..").Locator("select"); + + // Map text to value with proper error handling + var value = calculationType.ToLower() switch + { + "fixed" => "0", + "percentage" => "1", + _ => throw new ArgumentException($"Unknown calculation type: {calculationType}. Expected 'Fixed' or 'Percentage'.") + }; + await select.SelectOptionAsync(new[] { value }); + } + + /// + /// Select fee type from dropdown + /// + public async Task SelectFeeType(string feeType) + { + var select = _page.Locator("label:has-text('Fee Type')").Locator("..").Locator("select"); + + // Map text to value with proper error handling + var value = feeType.ToLower() switch + { + "merchant" => "0", + "service provider" => "1", + _ => throw new ArgumentException($"Unknown fee type: {feeType}. Expected 'Merchant' or 'Service Provider'.") + }; + await select.SelectOptionAsync(new[] { value }); + } + + /// + /// Enter fee value + /// + public async Task EnterFeeValue(string value) + { + var input = _page.Locator("label:has-text('Fee Value')").Locator("..").Locator("input"); + await input.FillAsync(value); + } + + /// + /// Click Add Fee button in modal + /// + public async Task ClickAddFeeButtonInModal() + { + var button = _page.Locator(".bg-white.rounded-lg.p-6").Locator("button:has-text('Add Fee')"); + await button.ClickAsync(); + await Task.Delay(500); + } + + /// + /// Verify transaction fee is added successfully + /// + public async Task VerifyTransactionFeeAddedSuccessfully() + { + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + var modalCount = await _page.Locator("h3:has-text('Add Transaction Fee')").CountAsync(); + modalCount.ShouldBe(0, "Add Transaction Fee modal should be closed after successful addition"); + } + + /// + /// Verify Add Transaction Fee modal is closed + /// + public async Task VerifyAddTransactionFeeModalIsClosed() + { + var modalCount = await _page.Locator("h3:has-text('Add Transaction Fee')").CountAsync(); + modalCount.ShouldBe(0, "Add Transaction Fee modal should be closed"); + } + + #endregion +} diff --git a/EstateManagementUI.IntegrationTests/Features/ContractManagement.feature b/EstateManagementUI.IntegrationTests/Features/ContractManagement.feature new file mode 100644 index 00000000..f8680164 --- /dev/null +++ b/EstateManagementUI.IntegrationTests/Features/ContractManagement.feature @@ -0,0 +1,186 @@ +Feature: Contract Management Integration Tests + As a user of the Estate Management UI + I want to manage contracts based on my role + So that I can view, create, and edit contracts according to my permissions + +Background: + Given the user navigates to the Contract Management page + +@ContractManagement @AdminRole +Scenario: Administrator user cannot see Contract Management menu + Given the user is authenticated as an "Administrator" user + When the user navigates to the home page + Then the Contract Management menu item is not visible + +@ContractManagement @AdminRole +Scenario: Administrator user cannot access Contract Management page directly + Given the user is authenticated as an "Administrator" user + When the user navigates to the Contract Management page + Then an access denied message is displayed + +@ContractManagement @EstateRole +Scenario: Estate user can see Contract Management menu + Given the user is authenticated as an "Estate" user + When the user navigates to the home page + Then the Contract Management menu item is visible + +@ContractManagement @EstateRole +Scenario: Estate user can view contracts list + Given the user is authenticated as an "Estate" user + When the user navigates to the Contract Management page + Then the Contract Management page is displayed + And the page title is "Contract Management" + And "2" contracts are displayed in the list + And the "Add New Contract" button is visible + +@ContractManagement @EstateRole +Scenario: Estate user can view contract details + Given the user is authenticated as an "Estate" user + When the user navigates to the Contract Management page + And the user clicks on the "View" button for the first contract + Then the View Contract page is displayed + And the contract description is "Standard Transaction Contract" + And the contract operator is "Safaricom" + And the contract has "2" products + And the first product is "Mobile Topup" + And the first product has "2" transaction fees + +@ContractManagement @EstateRole +Scenario: Estate user can view product transaction fee details + Given the user is authenticated as an "Estate" user + When the user navigates to the Contract Management page + And the user clicks on the "View" button for the first contract + Then the View Contract page is displayed + And the product "Mobile Topup" has a transaction fee "Merchant Commission" with value "0.50" + And the product "Mobile Topup" has a transaction fee "Service Provider Fee" with value "2.50" + +@ContractManagement @EstateRole +Scenario: Estate user can navigate to create new contract page + Given the user is authenticated as an "Estate" user + When the user navigates to the Contract Management page + And the user clicks on the "Add New Contract" button + Then the Create New Contract page is displayed + And the contract form is displayed + And the "Description" field is visible + And the "Operator" dropdown is visible + +@ContractManagement @EstateRole +Scenario: Estate user can create a new contract + Given the user is authenticated as an "Estate" user + When the user navigates to the Create New Contract page + And the user enters "Test Contract" in the "Description" field + And the user selects "Safaricom" from the "Operator" dropdown + And the user clicks on the "Create Contract" button + Then the contract is created successfully + And the user is redirected to the Contract Management page + +@ContractManagement @EstateRole +Scenario: Estate user sees validation error when creating contract without required fields + Given the user is authenticated as an "Estate" user + When the user navigates to the Create New Contract page + And the user clicks on the "Create Contract" button + Then validation errors are displayed + And the "Description is required" error is shown + And the "Operator is required" error is shown + +@ContractManagement @EstateRole +Scenario: Estate user can navigate to edit contract page + Given the user is authenticated as an "Estate" user + When the user navigates to the Contract Management page + And the user clicks on the "Edit" button for the first contract + Then the Edit Contract page is displayed + And the contract description field contains "Standard Transaction Contract" + And the operator name is displayed as "Safaricom" + +@ContractManagement @EstateRole +Scenario: Estate user can view products on edit page + Given the user is authenticated as an "Estate" user + When the user navigates to the Contract Management page + And the user clicks on the "Edit" button for the first contract + Then the Edit Contract page is displayed + And the products section is displayed + And "2" products are listed + And the "Add Product" button is visible + +@ContractManagement @EstateRole +Scenario: Estate user can add a product to contract + Given the user is authenticated as an "Estate" user + When the user navigates to the Edit Contract page for the first contract + And the user clicks on the "Add Product" button + Then the Add Product modal is displayed + When the user enters "New Product" in the product name field + And the user enters "New Product Display" in the display text field + And the user enters "100" in the value field + And the user clicks on the "Add Product" button in the modal + Then the product is added successfully + And the Add Product modal is closed + +@ContractManagement @EstateRole +Scenario: Estate user can add a variable value product to contract + Given the user is authenticated as an "Estate" user + When the user navigates to the Edit Contract page for the first contract + And the user clicks on the "Add Product" button + Then the Add Product modal is displayed + When the user enters "Variable Product" in the product name field + And the user enters "Variable Display" in the display text field + And the user checks the "Variable Value" checkbox + And the user clicks on the "Add Product" button in the modal + Then the product is added successfully + +@ContractManagement @EstateRole +Scenario: Estate user can add a transaction fee to a product + Given the user is authenticated as an "Estate" user + When the user navigates to the Edit Contract page for the first contract + And the user clicks on the "Add Fee" button for the first product + Then the Add Transaction Fee modal is displayed + When the user enters "Test Fee" in the fee description field + And the user selects "Fixed" from the calculation type dropdown + And the user selects "Merchant" from the fee type dropdown + And the user enters "5.00" in the fee value field + And the user clicks on the "Add Fee" button in the modal + Then the transaction fee is added successfully + And the Add Transaction Fee modal is closed + +@ContractManagement @ViewerRole +Scenario: Viewer user can see Contract Management menu + Given the user is authenticated as a "Viewer" user + When the user navigates to the home page + Then the Contract Management menu item is visible + +@ContractManagement @ViewerRole +Scenario: Viewer user can view contracts list + Given the user is authenticated as a "Viewer" user + When the user navigates to the Contract Management page + Then the Contract Management page is displayed + And the page title is "Contract Management" + And "2" contracts are displayed in the list + And the "Add New Contract" button is not visible + +@ContractManagement @ViewerRole +Scenario: Viewer user can view contract details + Given the user is authenticated as a "Viewer" user + When the user navigates to the Contract Management page + And the user clicks on the "View" button for the first contract + Then the View Contract page is displayed + And the contract description is "Standard Transaction Contract" + And the contract operator is "Safaricom" + And the contract has "2" products + +@ContractManagement @ViewerRole +Scenario: Viewer user cannot access create new contract page + Given the user is authenticated as a "Viewer" user + When the user navigates to the Create New Contract page directly + Then an access denied message is displayed for contract creation + +@ContractManagement @ViewerRole +Scenario: Viewer user cannot see Edit button for contracts + Given the user is authenticated as a "Viewer" user + When the user navigates to the Contract Management page + Then the Contract Management page is displayed + And the "Edit" button is not visible for contracts + +@ContractManagement @ViewerRole +Scenario: Viewer user cannot access edit contract page directly + Given the user is authenticated as a "Viewer" user + When the user navigates to the Edit Contract page directly + Then an access denied message is displayed for contract editing diff --git a/EstateManagementUI.IntegrationTests/Steps/ContractManagementSteps.cs b/EstateManagementUI.IntegrationTests/Steps/ContractManagementSteps.cs new file mode 100644 index 00000000..486da967 --- /dev/null +++ b/EstateManagementUI.IntegrationTests/Steps/ContractManagementSteps.cs @@ -0,0 +1,449 @@ +using Microsoft.Playwright; +using Reqnroll; +using EstateManagementUI.IntegrationTests.Common; + +namespace EstateManagementUI.IntegrationTests.Steps; + +/// +/// Step definitions for Contract Management integration tests +/// Links feature file scenarios to browser automation code +/// +[Binding] +public class ContractManagementSteps +{ + private readonly IPage _page; + private readonly ContractManagementPageHelper _contractHelper; + private readonly ScenarioContext _scenarioContext; + + public ContractManagementSteps(ScenarioContext scenarioContext) + { + _scenarioContext = scenarioContext; + _page = scenarioContext.ScenarioContainer.Resolve(); + + // Get base URL from environment variable or use default + var baseUrl = Environment.GetEnvironmentVariable("APP_URL") ?? "https://localhost:5001"; + _contractHelper = new ContractManagementPageHelper(_page, baseUrl); + } + + #region Navigation Steps + + [Given(@"the user navigates to the Contract Management page")] + [When(@"the user navigates to the Contract Management page")] + public async Task GivenTheUserNavigatesToTheContractManagementPage() + { + await _contractHelper.NavigateToContractManagement(); + } + + [When(@"the user navigates to the home page")] + public async Task WhenTheUserNavigatesToTheHomePage() + { + await _contractHelper.NavigateToHome(); + } + + [When(@"the user navigates to the Create New Contract page")] + [When(@"the user navigates to the Create New Contract page directly")] + [Given(@"the user navigates to the Create New Contract page")] + public async Task WhenTheUserNavigatesToTheCreateNewContractPage() + { + await _contractHelper.NavigateToCreateNewContract(); + } + + [When(@"the user navigates to the Edit Contract page directly")] + public async Task WhenTheUserNavigatesToTheEditContractPageDirectly() + { + await _contractHelper.NavigateToEditFirstContract(); + } + + [When(@"the user navigates to the Edit Contract page for the first contract")] + [Given(@"the user navigates to the Edit Contract page for the first contract")] + public async Task WhenTheUserNavigatesToTheEditContractPageForTheFirstContract() + { + await _contractHelper.NavigateToEditFirstContract(); + } + + #endregion + + #region Authentication/Role Steps + + [Given(@"the user is authenticated as an? ""(.*)"" user")] + public async Task GivenTheUserIsAuthenticatedAsAUser(string role) + { + // Store the role in scenario context for reference + _scenarioContext["UserRole"] = role; + + // Note: This step assumes the application will be started in test mode + // with the appropriate role already configured. The actual authentication + // setup will be handled when the application startup is implemented. + await Task.CompletedTask; + } + + #endregion + + #region Menu Visibility Steps + + [Then(@"the Contract Management menu item is not visible")] + public async Task ThenTheContractManagementMenuItemIsNotVisible() + { + await _contractHelper.VerifyContractManagementMenuIsNotVisible(); + } + + [Then(@"the Contract Management menu item is visible")] + public async Task ThenTheContractManagementMenuItemIsVisible() + { + await _contractHelper.VerifyContractManagementMenuIsVisible(); + } + + #endregion + + #region Page Display Steps + + [Then(@"the Contract Management page is displayed")] + public async Task ThenTheContractManagementPageIsDisplayed() + { + await _contractHelper.VerifyContractManagementPageIsDisplayed(); + } + + [Then(@"the page title is ""(.*)""")] + public async Task ThenThePageTitleIs(string expectedTitle) + { + await _contractHelper.VerifyPageTitle(expectedTitle); + } + + [Then(@"an access denied message is displayed")] + public async Task ThenAnAccessDeniedMessageIsDisplayed() + { + await _contractHelper.VerifyAccessDeniedMessageIsDisplayed(); + } + + [Then(@"an access denied message is displayed for contract creation")] + public async Task ThenAnAccessDeniedMessageIsDisplayedForContractCreation() + { + await _contractHelper.VerifyAccessDeniedForContractCreation(); + } + + [Then(@"an access denied message is displayed for contract editing")] + public async Task ThenAnAccessDeniedMessageIsDisplayedForContractEditing() + { + await _contractHelper.VerifyAccessDeniedForContractEditing(); + } + + [Then(@"the View Contract page is displayed")] + public async Task ThenTheViewContractPageIsDisplayed() + { + await _contractHelper.VerifyViewContractPageIsDisplayed(); + } + + [Then(@"the Create New Contract page is displayed")] + public async Task ThenTheCreateNewContractPageIsDisplayed() + { + await _contractHelper.VerifyCreateNewContractPageIsDisplayed(); + } + + [Then(@"the Edit Contract page is displayed")] + public async Task ThenTheEditContractPageIsDisplayed() + { + await _contractHelper.VerifyEditContractPageIsDisplayed(); + } + + #endregion + + #region Contract List Verification Steps + + [Then(@"""(.*)"" contracts are displayed in the list")] + public async Task ThenContractsAreDisplayedInTheList(int count) + { + await _contractHelper.VerifyContractCount(count); + } + + [Then(@"the ""Add New Contract"" button is visible")] + public async Task ThenTheAddNewContractButtonIsVisible() + { + await _contractHelper.VerifyAddNewContractButtonIsVisible(); + } + + [Then(@"the ""Add New Contract"" button is not visible")] + public async Task ThenTheAddNewContractButtonIsNotVisible() + { + await _contractHelper.VerifyAddNewContractButtonIsNotVisible(); + } + + [Then(@"the ""Edit"" button is not visible for contracts")] + public async Task ThenTheEditButtonIsNotVisibleForContracts() + { + await _contractHelper.VerifyEditButtonIsNotVisibleForContracts(); + } + + #endregion + + #region Contract Details Verification Steps + + [Then(@"the contract description is ""(.*)""")] + public async Task ThenTheContractDescriptionIs(string description) + { + await _contractHelper.VerifyContractDescription(description); + } + + [Then(@"the contract operator is ""(.*)""")] + public async Task ThenTheContractOperatorIs(string operatorName) + { + await _contractHelper.VerifyContractOperator(operatorName); + } + + [Then(@"the contract has ""(.*)"" products")] + public async Task ThenTheContractHasProducts(int count) + { + await _contractHelper.VerifyContractProductCount(count); + } + + [Then(@"the first product is ""(.*)""")] + public async Task ThenTheFirstProductIs(string productName) + { + await _contractHelper.VerifyFirstProductName(productName); + } + + [Then(@"the first product has ""(.*)"" transaction fees")] + public async Task ThenTheFirstProductHasTransactionFees(int count) + { + await _contractHelper.VerifyFirstProductTransactionFeeCount(count); + } + + [Then(@"the product ""(.*)"" has a transaction fee ""(.*)"" with value ""(.*)""")] + public async Task ThenTheProductHasATransactionFeeWithValue(string productName, string feeDescription, string value) + { + await _contractHelper.VerifyProductTransactionFee(productName, feeDescription, value); + } + + #endregion + + #region Form Verification Steps + + [Then(@"the contract form is displayed")] + public async Task ThenTheContractFormIsDisplayed() + { + await _contractHelper.VerifyContractFormIsDisplayed(); + } + + [Then(@"the ""(.*)"" field is visible")] + public async Task ThenTheFieldIsVisible(string fieldLabel) + { + await _contractHelper.VerifyFieldIsVisible(fieldLabel); + } + + [Then(@"the ""(.*)"" dropdown is visible")] + public async Task ThenTheDropdownIsVisible(string fieldLabel) + { + await _contractHelper.VerifyFieldIsVisible(fieldLabel); + } + + [Then(@"the contract description field contains ""(.*)""")] + public async Task ThenTheContractDescriptionFieldContains(string value) + { + await _contractHelper.VerifyDescriptionFieldContains(value); + } + + [Then(@"the operator name is displayed as ""(.*)""")] + public async Task ThenTheOperatorNameIsDisplayedAs(string operatorName) + { + await _contractHelper.VerifyOperatorNameDisplayed(operatorName); + } + + [Then(@"the products section is displayed")] + public async Task ThenTheProductsSectionIsDisplayed() + { + await _contractHelper.VerifyProductsSectionIsDisplayed(); + } + + [Then(@"""(.*)"" products are listed")] + public async Task ThenProductsAreListed(int count) + { + await _contractHelper.VerifyProductCountOnEditPage(count); + } + + [Then(@"the ""Add Product"" button is visible")] + public async Task ThenTheAddProductButtonIsVisible() + { + await _contractHelper.VerifyAddProductButtonIsVisible(); + } + + [Then(@"validation errors are displayed")] + public async Task ThenValidationErrorsAreDisplayed() + { + await _contractHelper.VerifyValidationErrorsAreDisplayed(); + } + + [Then(@"the ""(.*)"" error is shown")] + public async Task ThenTheErrorIsShown(string errorMessage) + { + await _contractHelper.VerifyValidationErrorMessage(errorMessage); + } + + #endregion + + #region Interaction Steps + + [When(@"the user clicks on the ""View"" button for the first contract")] + public async Task WhenTheUserClicksOnTheViewButtonForTheFirstContract() + { + await _contractHelper.ClickViewButtonForFirstContract(); + } + + [When(@"the user clicks on the ""Edit"" button for the first contract")] + public async Task WhenTheUserClicksOnTheEditButtonForTheFirstContract() + { + await _contractHelper.ClickEditButtonForFirstContract(); + } + + [When(@"the user clicks on the ""Add New Contract"" button")] + public async Task WhenTheUserClicksOnTheAddNewContractButton() + { + await _contractHelper.ClickAddNewContractButton(); + } + + [When(@"the user enters ""(.*)"" in the ""Description"" field")] + public async Task WhenTheUserEntersInTheDescriptionField(string text) + { + await _contractHelper.EnterTextInField("Description", text); + } + + [When(@"the user selects ""(.*)"" from the ""Operator"" dropdown")] + public async Task WhenTheUserSelectsFromTheOperatorDropdown(string option) + { + await _contractHelper.SelectFromDropdown("Operator", option); + } + + [When(@"the user clicks on the ""Create Contract"" button")] + public async Task WhenTheUserClicksOnTheCreateContractButton() + { + await _contractHelper.ClickCreateContractButton(); + } + + [Then(@"the contract is created successfully")] + public async Task ThenTheContractIsCreatedSuccessfully() + { + await _contractHelper.VerifyContractCreatedSuccessfully(); + } + + [Then(@"the user is redirected to the Contract Management page")] + public async Task ThenTheUserIsRedirectedToTheContractManagementPage() + { + await _contractHelper.VerifyRedirectedToContractManagementPage(); + } + + #endregion + + #region Add Product Steps + + [When(@"the user clicks on the ""Add Product"" button")] + public async Task WhenTheUserClicksOnTheAddProductButton() + { + await _contractHelper.ClickAddProductButton(); + } + + [Then(@"the Add Product modal is displayed")] + public async Task ThenTheAddProductModalIsDisplayed() + { + await _contractHelper.VerifyAddProductModalIsDisplayed(); + } + + [When(@"the user enters ""(.*)"" in the product name field")] + public async Task WhenTheUserEntersInTheProductNameField(string productName) + { + await _contractHelper.EnterProductName(productName); + } + + [When(@"the user enters ""(.*)"" in the display text field")] + public async Task WhenTheUserEntersInTheDisplayTextField(string displayText) + { + await _contractHelper.EnterDisplayText(displayText); + } + + [When(@"the user enters ""(.*)"" in the value field")] + public async Task WhenTheUserEntersInTheValueField(string value) + { + await _contractHelper.EnterValue(value); + } + + [When(@"the user checks the ""Variable Value"" checkbox")] + public async Task WhenTheUserChecksTheVariableValueCheckbox() + { + await _contractHelper.CheckVariableValueCheckbox(); + } + + [When(@"the user clicks on the ""Add Product"" button in the modal")] + public async Task WhenTheUserClicksOnTheAddProductButtonInTheModal() + { + await _contractHelper.ClickAddProductButtonInModal(); + } + + [Then(@"the product is added successfully")] + public async Task ThenTheProductIsAddedSuccessfully() + { + await _contractHelper.VerifyProductAddedSuccessfully(); + } + + [Then(@"the Add Product modal is closed")] + public async Task ThenTheAddProductModalIsClosed() + { + await _contractHelper.VerifyAddProductModalIsClosed(); + } + + #endregion + + #region Add Transaction Fee Steps + + [When(@"the user clicks on the ""Add Fee"" button for the first product")] + public async Task WhenTheUserClicksOnTheAddFeeButtonForTheFirstProduct() + { + await _contractHelper.ClickAddFeeButtonForFirstProduct(); + } + + [Then(@"the Add Transaction Fee modal is displayed")] + public async Task ThenTheAddTransactionFeeModalIsDisplayed() + { + await _contractHelper.VerifyAddTransactionFeeModalIsDisplayed(); + } + + [When(@"the user enters ""(.*)"" in the fee description field")] + public async Task WhenTheUserEntersInTheFeeDescriptionField(string description) + { + await _contractHelper.EnterFeeDescription(description); + } + + [When(@"the user selects ""(.*)"" from the calculation type dropdown")] + public async Task WhenTheUserSelectsFromTheCalculationTypeDropdown(string calculationType) + { + await _contractHelper.SelectCalculationType(calculationType); + } + + [When(@"the user selects ""(.*)"" from the fee type dropdown")] + public async Task WhenTheUserSelectsFromTheFeeTypeDropdown(string feeType) + { + await _contractHelper.SelectFeeType(feeType); + } + + [When(@"the user enters ""(.*)"" in the fee value field")] + public async Task WhenTheUserEntersInTheFeeValueField(string value) + { + await _contractHelper.EnterFeeValue(value); + } + + [When(@"the user clicks on the ""Add Fee"" button in the modal")] + public async Task WhenTheUserClicksOnTheAddFeeButtonInTheModal() + { + await _contractHelper.ClickAddFeeButtonInModal(); + } + + [Then(@"the transaction fee is added successfully")] + public async Task ThenTheTransactionFeeIsAddedSuccessfully() + { + await _contractHelper.VerifyTransactionFeeAddedSuccessfully(); + } + + [Then(@"the Add Transaction Fee modal is closed")] + public async Task ThenTheAddTransactionFeeModalIsClosed() + { + await _contractHelper.VerifyAddTransactionFeeModalIsClosed(); + } + + #endregion +}