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
+}