diff --git a/EstateManagementUI.IntegrationTests/.gitignore b/EstateManagementUI.IntegrationTests/.gitignore
new file mode 100644
index 00000000..3eb6e160
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/.gitignore
@@ -0,0 +1,42 @@
+## Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+## Test Results
+[Tt]est[Rr]esult*/
+TestResults/
+*.trx
+*.coverage
+
+## Playwright
+.playwright/
+playwright-report/
+playwright/.cache/
+
+## Screenshots
+screenshot-*.png
+
+## Visual Studio cache/options
+.vs/
+.vscode/
+
+## Reqnroll/SpecFlow generated files
+*.feature.cs
+
+## NuGet
+*.nupkg
+*.snupkg
+packages/
+.nuget/
diff --git a/EstateManagementUI.IntegrationTests/Common/DashboardPageHelper.cs b/EstateManagementUI.IntegrationTests/Common/DashboardPageHelper.cs
new file mode 100644
index 00000000..4e413d68
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/Common/DashboardPageHelper.cs
@@ -0,0 +1,242 @@
+using Microsoft.Playwright;
+using Shouldly;
+
+namespace EstateManagementUI.IntegrationTests.Common;
+
+///
+/// Helper class for interacting with the Dashboard page using Playwright
+///
+public class DashboardPageHelper
+{
+ private readonly IPage _page;
+ private readonly string _baseUrl;
+
+ public DashboardPageHelper(IPage page, string baseUrl)
+ {
+ _page = page;
+ _baseUrl = baseUrl;
+ }
+
+ #region Navigation
+
+ ///
+ /// Navigate to the home/dashboard page
+ ///
+ public async Task NavigateToDashboard()
+ {
+ await _page.GotoAsync(_baseUrl);
+ await _page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+ }
+
+ #endregion
+
+ #region Verification Methods
+
+ ///
+ /// Verify the page title is "Dashboard"
+ ///
+ public async Task VerifyDashboardPageTitle()
+ {
+ var title = await _page.TitleAsync();
+ title.ShouldBe("Dashboard");
+ }
+
+ ///
+ /// Verify the Administrator welcome message is displayed
+ ///
+ public async Task VerifyAdministratorWelcomeMessage()
+ {
+ var heading = await _page.Locator("h2:has-text('Welcome, Administrator')").TextContentAsync();
+ heading.ShouldNotBeNull();
+ heading.ShouldContain("Welcome, Administrator");
+
+ var description = await _page.Locator("p:has-text('administrative access')").TextContentAsync();
+ description.ShouldNotBeNull();
+ description.ShouldContain("administrative access to manage system permissions");
+ }
+
+ ///
+ /// Verify that KPI cards are visible on the dashboard
+ ///
+ public async Task VerifyKpiCardsAreVisible()
+ {
+ await _page.Locator("text=Merchants with Sales (Last Hour)").WaitForAsync();
+ await _page.Locator("text=Merchants with No Sales Today").WaitForAsync();
+ await _page.Locator("text=Merchants with No Sales (7 Days)").WaitForAsync();
+ }
+
+ ///
+ /// Verify that KPI cards are NOT visible (Administrator role)
+ ///
+ public async Task VerifyKpiCardsAreNotVisible()
+ {
+ var salesLastHourCount = await _page.Locator("text=Merchants with Sales (Last Hour)").CountAsync();
+ salesLastHourCount.ShouldBe(0, "KPI cards should not be visible for Administrator role");
+ }
+
+ ///
+ /// Verify Merchant KPI values match expected hardcoded test data
+ ///
+ public async Task VerifyMerchantKpiValues(int salesLastHour, int noSalesToday, int noSales7Days)
+ {
+ // Wait for KPI cards to load
+ await _page.Locator("text=Merchants with Sales (Last Hour)").WaitForAsync();
+
+ // Verify Merchants with Sales in Last Hour
+ var salesLastHourCard = _page.Locator(".info-box").Filter(new LocatorFilterOptions
+ {
+ HasText = "Merchants with Sales (Last Hour)"
+ });
+ var salesLastHourValue = await salesLastHourCard.Locator(".info-box-number").TextContentAsync();
+ salesLastHourValue.ShouldNotBeNull();
+ int.Parse(salesLastHourValue.Trim()).ShouldBe(salesLastHour);
+
+ // Verify Merchants with No Sales Today
+ var noSalesTodayCard = _page.Locator(".info-box").Filter(new LocatorFilterOptions
+ {
+ HasText = "Merchants with No Sales Today"
+ });
+ var noSalesTodayValue = await noSalesTodayCard.Locator(".info-box-number").TextContentAsync();
+ noSalesTodayValue.ShouldNotBeNull();
+ int.Parse(noSalesTodayValue.Trim()).ShouldBe(noSalesToday);
+
+ // Verify Merchants with No Sales in Last 7 Days
+ var noSales7DaysCard = _page.Locator(".info-box").Filter(new LocatorFilterOptions
+ {
+ HasText = "Merchants with No Sales (7 Days)"
+ });
+ var noSales7DaysValue = await noSales7DaysCard.Locator(".info-box-number").TextContentAsync();
+ noSales7DaysValue.ShouldNotBeNull();
+ int.Parse(noSales7DaysValue.Trim()).ShouldBe(noSales7Days);
+ }
+
+ ///
+ /// Verify Today's Sales card is displayed
+ ///
+ public async Task VerifyTodaysSalesCardIsDisplayed()
+ {
+ await _page.Locator("h3:has-text(\"Today's Sales\")").WaitForAsync();
+ }
+
+ ///
+ /// Verify Today's Sales values
+ ///
+ public async Task VerifyTodaysSalesValues(int todayCount, decimal todayValue)
+ {
+ var salesCard = _page.Locator(".card").Filter(new LocatorFilterOptions
+ {
+ HasText = "Today's Sales"
+ });
+
+ // Wait for the card to be visible
+ await salesCard.WaitForAsync();
+
+ // Verify today's sales count
+ var todayTransactions = await salesCard.Locator("p:has-text('transactions')").First.TextContentAsync();
+ todayTransactions.ShouldNotBeNull();
+ todayTransactions.ShouldContain($"{todayCount} transactions");
+
+ // Verify today's sales value is displayed (currency format)
+ var todayValueText = await salesCard.Locator(".text-2xl.font-bold").First.TextContentAsync();
+ todayValueText.ShouldNotBeNull();
+ // Just verify value is present and formatted as currency
+ todayValueText.ShouldContain("$");
+ }
+
+ ///
+ /// Verify Failed Sales card is displayed
+ ///
+ public async Task VerifyFailedSalesCardIsDisplayed()
+ {
+ await _page.Locator("h3:has-text('Failed Sales (Low Credit)')").WaitForAsync();
+ }
+
+ ///
+ /// Verify Failed Sales values
+ ///
+ public async Task VerifyFailedSalesValues(int todayCount)
+ {
+ var failedSalesCard = _page.Locator(".card").Filter(new LocatorFilterOptions
+ {
+ HasText = "Failed Sales (Low Credit)"
+ });
+
+ // Wait for the card to be visible
+ await failedSalesCard.WaitForAsync();
+
+ // Verify today's failed sales count
+ var todayTransactions = await failedSalesCard.Locator("p:has-text('transactions')").First.TextContentAsync();
+ todayTransactions.ShouldNotBeNull();
+ todayTransactions.ShouldContain($"{todayCount} transactions");
+ }
+
+ ///
+ /// Verify comparison date selector is visible
+ ///
+ public async Task VerifyComparisonDateSelectorIsVisible()
+ {
+ await _page.Locator("label:has-text('Compare to:')").WaitForAsync();
+ await _page.Locator("#comparisonDateSelector").WaitForAsync();
+ }
+
+ ///
+ /// Verify comparison date selector is NOT visible (Administrator role)
+ ///
+ public async Task VerifyComparisonDateSelectorIsNotVisible()
+ {
+ var selectorCount = await _page.Locator("#comparisonDateSelector").CountAsync();
+ selectorCount.ShouldBe(0, "Comparison date selector should not be visible for Administrator role");
+ }
+
+ ///
+ /// Verify Recently Created Merchants section is visible
+ ///
+ public async Task VerifyRecentlyCreatedMerchantsIsVisible()
+ {
+ await _page.Locator("h3:has-text('Recently Created Merchants')").WaitForAsync();
+ }
+
+ ///
+ /// Verify Recently Created Merchants section is NOT visible (Administrator role)
+ ///
+ public async Task VerifyRecentlyCreatedMerchantsIsNotVisible()
+ {
+ var merchantsCount = await _page.Locator("h3:has-text('Recently Created Merchants')").CountAsync();
+ merchantsCount.ShouldBe(0, "Recently Created Merchants should not be visible for Administrator role");
+ }
+
+ ///
+ /// Verify that at least one merchant is displayed in the Recently Created Merchants section
+ ///
+ public async Task VerifyRecentlyCreatedMerchantsHasData()
+ {
+ var merchantsCard = _page.Locator(".card").Filter(new LocatorFilterOptions
+ {
+ HasText = "Recently Created Merchants"
+ });
+
+ await merchantsCard.WaitForAsync();
+
+ // Check that at least one merchant is displayed
+ var merchantItems = merchantsCard.Locator(".flex.items-center.justify-between");
+ var count = await merchantItems.CountAsync();
+ count.ShouldBeGreaterThan(0, "At least one merchant should be displayed");
+ }
+
+ #endregion
+
+ #region Interaction Methods
+
+ ///
+ /// Select a comparison date from the dropdown
+ ///
+ public async Task SelectComparisonDate(string dateDescription)
+ {
+ await _page.Locator("#comparisonDateSelector").SelectOptionAsync(new[] { dateDescription });
+ // Wait for dashboard to reload
+ await Task.Delay(500); // Small delay for state update
+ await _page.WaitForLoadStateAsync(LoadState.NetworkIdle);
+ }
+
+ #endregion
+}
diff --git a/EstateManagementUI.IntegrationTests/EstateManagementUI.IntegrationTests.csproj b/EstateManagementUI.IntegrationTests/EstateManagementUI.IntegrationTests.csproj
new file mode 100644
index 00000000..96310436
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/EstateManagementUI.IntegrationTests.csproj
@@ -0,0 +1,39 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+ Always
+ true
+ PreserveNewest
+
+
+
+
diff --git a/EstateManagementUI.IntegrationTests/Features/Dashboard.feature b/EstateManagementUI.IntegrationTests/Features/Dashboard.feature
new file mode 100644
index 00000000..5ba01ab3
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/Features/Dashboard.feature
@@ -0,0 +1,88 @@
+Feature: Dashboard Integration Tests
+ As a user of the Estate Management UI
+ I want to see appropriate dashboard content based on my role
+ So that I can access the information relevant to my permissions
+
+Background:
+ Given the user navigates to the Dashboard
+
+@DashboardTests @AdminRole
+Scenario: Administrator user sees limited dashboard view
+ Given the user is authenticated as an "Administrator" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the Administrator welcome message is displayed
+ And no merchant KPI cards are displayed
+ And no sales data cards are displayed
+
+@DashboardTests @EstateRole
+Scenario: Estate user sees full dashboard with merchant KPIs
+ Given the user is authenticated as an "Estate" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the merchant KPI cards are displayed
+ And the Merchants with Sales in Last Hour shows "45"
+ And the Merchants with No Sales Today shows "12"
+ And the Merchants with No Sales in Last 7 Days shows "5"
+
+@DashboardTests @EstateRole
+Scenario: Estate user sees sales data on dashboard
+ Given the user is authenticated as an "Estate" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the Today's Sales card is displayed
+ And the Today's Sales card shows "523" transactions
+ And the Today's Sales card shows a value greater than $0
+ And the Failed Sales card is displayed
+ And the Failed Sales card shows "15" transactions
+
+@DashboardTests @EstateRole
+Scenario: Estate user sees comparison date selector
+ Given the user is authenticated as an "Estate" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the comparison date selector is displayed
+
+@DashboardTests @EstateRole
+Scenario: Estate user sees recently created merchants
+ Given the user is authenticated as an "Estate" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the Recently Created Merchants section is displayed
+ And at least "1" merchant is shown in Recently Created Merchants
+
+@DashboardTests @ViewerRole
+Scenario: Viewer user sees full dashboard with merchant KPIs
+ Given the user is authenticated as a "Viewer" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the merchant KPI cards are displayed
+ And the Merchants with Sales in Last Hour shows "45"
+ And the Merchants with No Sales Today shows "12"
+ And the Merchants with No Sales in Last 7 Days shows "5"
+
+@DashboardTests @ViewerRole
+Scenario: Viewer user sees sales data on dashboard
+ Given the user is authenticated as a "Viewer" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the Today's Sales card is displayed
+ And the Today's Sales card shows "523" transactions
+ And the Today's Sales card shows a value greater than $0
+ And the Failed Sales card is displayed
+ And the Failed Sales card shows "15" transactions
+
+@DashboardTests @ViewerRole
+Scenario: Viewer user sees comparison date selector
+ Given the user is authenticated as a "Viewer" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the comparison date selector is displayed
+
+@DashboardTests @ViewerRole
+Scenario: Viewer user sees recently created merchants
+ Given the user is authenticated as a "Viewer" user
+ When the user navigates to the Dashboard
+ Then the Dashboard page is displayed
+ And the Recently Created Merchants section is displayed
+ And at least "1" merchant is shown in Recently Created Merchants
diff --git a/EstateManagementUI.IntegrationTests/Hooks/BrowserHooks.cs b/EstateManagementUI.IntegrationTests/Hooks/BrowserHooks.cs
new file mode 100644
index 00000000..732d5a08
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/Hooks/BrowserHooks.cs
@@ -0,0 +1,144 @@
+using Microsoft.Playwright;
+using Reqnroll;
+
+namespace EstateManagementUI.IntegrationTests.Hooks;
+
+///
+/// Hooks for managing browser lifecycle using Playwright
+///
+[Binding]
+public class BrowserHooks
+{
+ private static IPlaywright? _playwright;
+ private static IBrowser? _browser;
+ private readonly ScenarioContext _scenarioContext;
+
+ public BrowserHooks(ScenarioContext scenarioContext)
+ {
+ _scenarioContext = scenarioContext;
+ }
+
+ ///
+ /// Install Playwright browsers and initialize Playwright before running any tests
+ ///
+ [BeforeTestRun]
+ public static async Task BeforeTestRun()
+ {
+ // Install Playwright browsers if needed
+ var exitCode = Microsoft.Playwright.Program.Main(new[] { "install" });
+ if (exitCode != 0)
+ {
+ throw new Exception($"Playwright installation failed with exit code {exitCode}");
+ }
+
+ // Initialize Playwright
+ _playwright = await Playwright.CreateAsync();
+ }
+
+ ///
+ /// Create a new browser page for each scenario
+ ///
+ [BeforeScenario(Order = 0)]
+ public async Task BeforeScenario()
+ {
+ var page = await CreateBrowserPage();
+
+ // Register the page for this scenario
+ _scenarioContext.ScenarioContainer.RegisterInstanceAs(page);
+ }
+
+ ///
+ /// Cleanup browser page after each scenario and take screenshot on failure
+ ///
+ [AfterScenario(Order = 0)]
+ public async Task AfterScenario()
+ {
+ var page = _scenarioContext.ScenarioContainer.Resolve();
+
+ if (page != null)
+ {
+ // Take screenshot on failure
+ if (_scenarioContext.TestError != null)
+ {
+ var scenarioName = _scenarioContext.ScenarioInfo.Title.Replace(" ", "_");
+ var screenshotPath = $"screenshot-{scenarioName}-{DateTime.Now:yyyyMMddHHmmss}.png";
+ await page.ScreenshotAsync(new PageScreenshotOptions
+ {
+ Path = screenshotPath,
+ FullPage = true
+ });
+ Console.WriteLine($"Screenshot saved to: {screenshotPath}");
+ }
+
+ await page.CloseAsync();
+ }
+ }
+
+ ///
+ /// Cleanup Playwright resources after all tests complete
+ ///
+ [AfterTestRun]
+ public static async Task AfterTestRun()
+ {
+ if (_browser != null)
+ {
+ await _browser.CloseAsync();
+ _browser = null;
+ }
+
+ if (_playwright != null)
+ {
+ _playwright.Dispose();
+ _playwright = null;
+ }
+ }
+
+ ///
+ /// Create a new browser page with appropriate configuration
+ ///
+ private async Task CreateBrowserPage()
+ {
+ var browserType = Environment.GetEnvironmentVariable("Browser") ?? "Chrome";
+ var isCI = string.Equals(
+ Environment.GetEnvironmentVariable("IsCI"),
+ "true",
+ StringComparison.InvariantCultureIgnoreCase);
+
+ if (_browser == null)
+ {
+ _browser = browserType switch
+ {
+ "Firefox" => await _playwright!.Firefox.LaunchAsync(new BrowserTypeLaunchOptions
+ {
+ Headless = isCI,
+ Args = new[] { "--ignore-certificate-errors" }
+ }),
+ "WebKit" => await _playwright!.Webkit.LaunchAsync(new BrowserTypeLaunchOptions
+ {
+ Headless = isCI,
+ Args = new[] { "--ignore-certificate-errors" }
+ }),
+ _ => await _playwright!.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
+ {
+ Headless = isCI,
+ Args = new[]
+ {
+ "--ignore-certificate-errors",
+ "--no-sandbox",
+ "--disable-dev-shm-usage",
+ "--disable-gpu",
+ "--disable-extensions"
+ }
+ })
+ };
+ }
+
+ var context = await _browser.NewContextAsync(new BrowserNewContextOptions
+ {
+ IgnoreHTTPSErrors = true,
+ ViewportSize = new ViewportSize { Width = 1920, Height = 1080 }
+ });
+
+ return await context.NewPageAsync();
+ }
+}
diff --git a/EstateManagementUI.IntegrationTests/README.md b/EstateManagementUI.IntegrationTests/README.md
new file mode 100644
index 00000000..4c014be4
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/README.md
@@ -0,0 +1,138 @@
+# Estate Management UI - Dashboard Integration Tests
+
+This project contains integration tests for the Dashboard functionality of the Estate Management Blazor Server application, using Reqnroll (SpecFlow successor), Playwright, and Shouldly.
+
+## Project Structure
+
+```
+EstateManagementUI.IntegrationTests/
+├── Features/ # Reqnroll feature files (Gherkin scenarios)
+│ └── Dashboard.feature # Dashboard test scenarios for all roles
+├── Steps/ # Step definitions (links features to code)
+│ └── DashboardSteps.cs # Dashboard step implementations
+├── Hooks/ # Test lifecycle hooks
+│ └── BrowserHooks.cs # Playwright browser management
+├── Common/ # Helper classes and utilities
+│ └── DashboardPageHelper.cs # Page object for Dashboard interactions
+└── appsettings.json # Test configuration and hardcoded test data
+```
+
+## Key Components
+
+### 1. Feature File (`Features/Dashboard.feature`)
+- Defines test scenarios in Gherkin syntax (Given/When/Then)
+- Covers three user roles: Administrator, Estate, and Viewer
+- Tests dashboard visibility and data display based on role permissions
+- Uses hardcoded test data values from the application
+
+### 2. Hooks File (`Hooks/BrowserHooks.cs`)
+- Manages Playwright browser lifecycle
+- `BeforeTestRun`: Installs Playwright browsers and initializes Playwright
+- `BeforeScenario`: Creates a new browser page for each test scenario
+- `AfterScenario`: Cleans up and takes screenshots on test failures
+- `AfterTestRun`: Disposes Playwright resources
+
+### 3. Step Definitions (`Steps/DashboardSteps.cs`)
+- Links Gherkin steps from feature files to C# code
+- Uses DashboardPageHelper to interact with the browser
+- Implements Given/When/Then steps for all Dashboard scenarios
+
+### 4. Page Helper (`Common/DashboardPageHelper.cs`)
+- Encapsulates all Dashboard page interactions using Playwright
+- Provides methods for navigation, verification, and interaction
+- Uses Shouldly for assertions
+
+## Test Data
+
+The tests are designed to assert against hardcoded test data in the application's `TestMediatorService.cs`:
+
+### Merchant KPIs
+- Merchants with Sales in Last Hour: **45**
+- Merchants with No Sales Today: **12**
+- Merchants with No Sales in Last 7 Days: **5**
+
+### Today's Sales
+- Transaction Count: **523**
+- Sales Value: **$145,000.00**
+
+### Failed Sales (Low Credit)
+- Transaction Count: **15**
+- Sales Value: **$850.00**
+
+These values are defined in the application's `TestMediatorService` and can be updated when the test data is changed.
+
+## User Roles Tested
+
+### Administrator Role
+- Can only view the Dashboard with a welcome message
+- No access to merchant KPIs, sales data, or reports
+- Limited to permission management functions
+
+### Estate Role
+- Full access to all dashboard features
+- Can view all merchant KPIs
+- Can view sales data and comparisons
+- Can view recently created merchants
+- Has full CRUD permissions across the application
+
+### Viewer Role
+- View-only access to all dashboard features
+- Can view all merchant KPIs
+- Can view sales data and comparisons
+- Can view recently created merchants
+- Cannot create, edit, or delete any data
+
+## Running the Tests
+
+The tests are designed to run against a running instance of the Estate Management UI application. The application startup and configuration will be handled separately.
+
+### Prerequisites
+1. .NET 10 SDK
+2. Playwright browsers (automatically installed by the test hooks)
+
+### Configuration
+Set the following environment variables before running tests:
+- `APP_URL`: Base URL of the application (default: `https://localhost:5001`)
+- `Browser`: Browser to use - Chrome, Firefox, or WebKit (default: Chrome)
+- `IsCI`: Set to "true" to run in headless mode (default: false)
+
+### Execute Tests
+```bash
+dotnet test EstateManagementUI.IntegrationTests.csproj
+```
+
+### Filter by Role
+```bash
+# Run only Administrator role tests
+dotnet test --filter "Category=AdminRole"
+
+# Run only Estate role tests
+dotnet test --filter "Category=EstateRole"
+
+# Run only Viewer role tests
+dotnet test --filter "Category=ViewerRole"
+```
+
+## Notes
+
+- **Application Startup**: The tests assume the application is already running. Application startup logic will be implemented separately.
+- **Authentication**: The authentication/role setup is a placeholder. The actual implementation will depend on how the application is configured for testing.
+- **Test Data**: All assertions are based on hardcoded values in the application's `TestMediatorService`. Update the feature file and `appsettings.json` if test data changes.
+- **Screenshots**: On test failure, screenshots are automatically saved to the test output directory with timestamps.
+
+## Dependencies
+
+- **Reqnroll 3.2.1**: BDD framework (SpecFlow successor)
+- **Playwright 1.49.0**: Browser automation
+- **Shouldly 4.3.0**: Assertion library with readable error messages
+- **NUnit 4.4.0**: Test framework
+- **.NET 10**: Target framework
+
+## Future Enhancements
+
+When application startup is implemented:
+1. Add Docker container management for the application
+2. Add test data setup/teardown
+3. Add user authentication simulation
+4. Add role switching capabilities
+5. Add test reporting and metrics
diff --git a/EstateManagementUI.IntegrationTests/Steps/DashboardSteps.cs b/EstateManagementUI.IntegrationTests/Steps/DashboardSteps.cs
new file mode 100644
index 00000000..db148129
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/Steps/DashboardSteps.cs
@@ -0,0 +1,187 @@
+using Microsoft.Playwright;
+using Reqnroll;
+using EstateManagementUI.IntegrationTests.Common;
+
+namespace EstateManagementUI.IntegrationTests.Steps;
+
+///
+/// Step definitions for Dashboard integration tests
+/// Links feature file scenarios to browser automation code
+///
+[Binding]
+public class DashboardSteps
+{
+ private readonly IPage _page;
+ private readonly DashboardPageHelper _dashboardHelper;
+ private readonly ScenarioContext _scenarioContext;
+
+ public DashboardSteps(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";
+ _dashboardHelper = new DashboardPageHelper(_page, baseUrl);
+ }
+
+ #region Navigation Steps
+
+ [Given(@"the user navigates to the Dashboard")]
+ [When(@"the user navigates to the Dashboard")]
+ public async Task GivenTheUserNavigatesToTheDashboard()
+ {
+ await _dashboardHelper.NavigateToDashboard();
+ }
+
+ #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 Verification Steps - Common
+
+ [Then(@"the Dashboard page is displayed")]
+ public async Task ThenTheDashboardPageIsDisplayed()
+ {
+ await _dashboardHelper.VerifyDashboardPageTitle();
+ }
+
+ #endregion
+
+ #region Verification Steps - Administrator Role
+
+ [Then(@"the Administrator welcome message is displayed")]
+ public async Task ThenTheAdministratorWelcomeMessageIsDisplayed()
+ {
+ await _dashboardHelper.VerifyAdministratorWelcomeMessage();
+ }
+
+ [Then(@"no merchant KPI cards are displayed")]
+ public async Task ThenNoMerchantKpiCardsAreDisplayed()
+ {
+ await _dashboardHelper.VerifyKpiCardsAreNotVisible();
+ }
+
+ [Then(@"no sales data cards are displayed")]
+ public async Task ThenNoSalesDataCardsAreDisplayed()
+ {
+ await _dashboardHelper.VerifyComparisonDateSelectorIsNotVisible();
+ await _dashboardHelper.VerifyRecentlyCreatedMerchantsIsNotVisible();
+ }
+
+ #endregion
+
+ #region Verification Steps - Estate/Viewer Roles
+
+ [Then(@"the merchant KPI cards are displayed")]
+ public async Task ThenTheMerchantKpiCardsAreDisplayed()
+ {
+ await _dashboardHelper.VerifyKpiCardsAreVisible();
+ }
+
+ [Then(@"the Merchants with Sales in Last Hour shows ""(.*)""")]
+ public async Task ThenTheMerchantsWithSalesInLastHourShows(int expectedValue)
+ {
+ // This will be verified as part of the full KPI verification
+ _scenarioContext["ExpectedSalesLastHour"] = expectedValue;
+ }
+
+ [Then(@"the Merchants with No Sales Today shows ""(.*)""")]
+ public async Task ThenTheMerchantsWithNoSalesTodayShows(int expectedValue)
+ {
+ _scenarioContext["ExpectedNoSalesToday"] = expectedValue;
+ }
+
+ [Then(@"the Merchants with No Sales in Last 7 Days shows ""(.*)""")]
+ public async Task ThenTheMerchantsWithNoSalesInLast7DaysShows(int expectedValue)
+ {
+ _scenarioContext["ExpectedNoSales7Days"] = expectedValue;
+
+ // Now verify all KPI values
+ var salesLastHour = (int)_scenarioContext["ExpectedSalesLastHour"];
+ var noSalesToday = (int)_scenarioContext["ExpectedNoSalesToday"];
+ var noSales7Days = expectedValue;
+
+ await _dashboardHelper.VerifyMerchantKpiValues(salesLastHour, noSalesToday, noSales7Days);
+ }
+
+ [Then(@"the Today's Sales card is displayed")]
+ public async Task ThenTheTodaysSalesCardIsDisplayed()
+ {
+ await _dashboardHelper.VerifyTodaysSalesCardIsDisplayed();
+ }
+
+ [Then(@"the Today's Sales card shows ""(.*)"" transactions")]
+ public async Task ThenTheTodaysSalesCardShowsTransactions(int transactionCount)
+ {
+ // Store for later verification
+ _scenarioContext["TodaysSalesCount"] = transactionCount;
+ }
+
+ [Then(@"the Today's Sales card shows a value greater than \$(.*)")]
+ public async Task ThenTheTodaysSalesCardShowsAValueGreaterThan(decimal minimumValue)
+ {
+ // Verify sales values
+ var salesCount = (int)_scenarioContext["TodaysSalesCount"];
+ await _dashboardHelper.VerifyTodaysSalesValues(salesCount, minimumValue);
+ }
+
+ [Then(@"the Failed Sales card is displayed")]
+ public async Task ThenTheFailedSalesCardIsDisplayed()
+ {
+ await _dashboardHelper.VerifyFailedSalesCardIsDisplayed();
+ }
+
+ [Then(@"the Failed Sales card shows ""(.*)"" transactions")]
+ public async Task ThenTheFailedSalesCardShowsTransactions(int transactionCount)
+ {
+ await _dashboardHelper.VerifyFailedSalesValues(transactionCount);
+ }
+
+ [Then(@"the comparison date selector is displayed")]
+ public async Task ThenTheComparisonDateSelectorIsDisplayed()
+ {
+ await _dashboardHelper.VerifyComparisonDateSelectorIsVisible();
+ }
+
+ [Then(@"the Recently Created Merchants section is displayed")]
+ public async Task ThenTheRecentlyCreatedMerchantsSectionIsDisplayed()
+ {
+ await _dashboardHelper.VerifyRecentlyCreatedMerchantsIsVisible();
+ }
+
+ [Then(@"at least ""(.*)"" merchant is shown in Recently Created Merchants")]
+ public async Task ThenAtLeastMerchantIsShownInRecentlyCreatedMerchants(int minCount)
+ {
+ if (minCount > 0)
+ {
+ await _dashboardHelper.VerifyRecentlyCreatedMerchantsHasData();
+ }
+ }
+
+ #endregion
+
+ #region Interaction Steps
+
+ [When(@"the user selects ""(.*)"" from the comparison date selector")]
+ public async Task WhenTheUserSelectsFromTheComparisonDateSelector(string dateOption)
+ {
+ await _dashboardHelper.SelectComparisonDate(dateOption);
+ }
+
+ #endregion
+}
diff --git a/EstateManagementUI.IntegrationTests/appsettings.json b/EstateManagementUI.IntegrationTests/appsettings.json
new file mode 100644
index 00000000..0f5bae89
--- /dev/null
+++ b/EstateManagementUI.IntegrationTests/appsettings.json
@@ -0,0 +1,23 @@
+{
+ "TestSettings": {
+ "BaseUrl": "https://localhost:5001",
+ "Browser": "Chrome",
+ "Headless": false,
+ "DefaultTimeout": 30000
+ },
+ "HardcodedTestData": {
+ "MerchantKpi": {
+ "MerchantsWithSaleInLastHour": 45,
+ "MerchantsWithNoSaleToday": 12,
+ "MerchantsWithNoSaleInLast7Days": 5
+ },
+ "TodaysSales": {
+ "TodaysSalesCount": 523,
+ "TodaysSalesValue": 145000.00
+ },
+ "TodaysFailedSales": {
+ "TodaysSalesCount": 15,
+ "TodaysSalesValue": 850.00
+ }
+ }
+}
diff --git a/EstateManagementUI.sln b/EstateManagementUI.sln
index dd971587..907e4f2a 100644
--- a/EstateManagementUI.sln
+++ b/EstateManagementUI.sln
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EstateManagementUI.BlazorSe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EstateManagementUI.BlazorServer.Tests", "EstateManagementUI.BlazorServer.Tests\EstateManagementUI.BlazorServer.Tests.csproj", "{55431CBE-C879-47B9-9607-A5822DE9B856}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EstateManagementUI.IntegrationTests", "EstateManagementUI.IntegrationTests\EstateManagementUI.IntegrationTests.csproj", "{93B9C973-7DA8-325D-D9FC-6E93A3A25B25}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -45,6 +47,18 @@ Global
{55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x64.Build.0 = Release|Any CPU
{55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x86.ActiveCfg = Release|Any CPU
{55431CBE-C879-47B9-9607-A5822DE9B856}.Release|x86.Build.0 = Release|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Debug|x64.Build.0 = Debug|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Debug|x86.Build.0 = Debug|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Release|Any CPU.Build.0 = Release|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Release|x64.ActiveCfg = Release|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Release|x64.Build.0 = Release|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Release|x86.ActiveCfg = Release|Any CPU
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -52,6 +66,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{C52FE3F3-E999-4A12-A20B-0D5C6DA34AA9} = {05420FAB-1C50-4AAD-9E7C-2A86184019B7}
{55431CBE-C879-47B9-9607-A5822DE9B856} = {E7671C23-F30C-471A-A9EF-AC85DC607B55}
+ {93B9C973-7DA8-325D-D9FC-6E93A3A25B25} = {E7671C23-F30C-471A-A9EF-AC85DC607B55}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C5AE6DA4-B167-44E5-9B63-61C7E8D1BC9F}