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}