using Microsoft.Playwright;
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Monitoring;
///
/// End-to-end guard for the Health dashboard KPI tiles (/monitoring/health).
///
///
/// The Health dashboard fans out to three Akka cluster-singleton Asks every
/// 10 s (Notification Outbox, Site Call, Audit) to populate nine KPI tiles. A
/// previously-fixed bug caused those Asks to hang, leaving every tile showing
/// the em-dash degrade placeholder (—) instead of a resolved numeric value.
/// This test guards that regression: it asserts that every tile resolves to a value
/// and never shows —.
///
///
///
/// The three tile groups and their selectors:
///
/// -
/// Notification Outbox — inlined in Health.razor with no
/// data-test attribute. Each is a Bootstrap div.card whose
/// .card-body contains a value h3 and a small.text-muted
/// label. Located by the card that contains the label text, then its h3.
///
/// -
/// Audit — rendered by AuditKpiTiles.razor; each tile button
/// carries a data-test attribute: audit-kpi-volume,
/// audit-kpi-error-rate, audit-kpi-backlog.
///
/// -
/// Site Calls — rendered by SiteCallKpiTiles.razor; each tile
/// button carries a data-test attribute: site-call-kpi-buffered,
/// site-call-kpi-stuck, site-call-kpi-parked.
///
///
///
///
[Collection("Playwright")]
public class HealthDashboardTests
{
private const string HealthUrl = "/monitoring/health";
///
/// The degrade placeholder rendered when a KPI loader faults — an em-dash
/// (U+2014). A healthy tile shows a non-negative integer instead.
///
private const string DegradePlaceholder = "—"; // —
private readonly PlaywrightFixture _fixture;
public HealthDashboardTests(PlaywrightFixture fixture)
{
_fixture = fixture;
}
///
/// Asserts that all nine KPI tiles on the Health dashboard resolve to numeric
/// values and do not show the em-dash degrade placeholder (—).
///
///
/// A generous 20 s per-tile timeout is intentional: the tiles are populated
/// asynchronously after initial render as the three singleton Asks
/// complete. The Playwright web-first assertion retries within that window
/// rather than using a fixed sleep, so a fast cluster will pass quickly.
///
///
[SkippableFact]
public async Task KpiTiles_ResolveToValues_NotDegradePlaceholder()
{
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
var page = await _fixture.NewAuthenticatedPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{HealthUrl}");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
// ── Notification Outbox tiles (no data-test; inlined in Health.razor as
// plain div.card elements under the "Notification Outbox" h6 heading).
// Scope all three cards to the div.row that immediately follows the flex
// container holding the h6 — prevents false matches if a second section
// later grows cards with the same label text ("Stuck", "Parked").
//
// Health.razor structure:
//
// ← outboxSection (+sibling)
//
//
//
//
var outboxSection = page.Locator("div.d-flex:has(h6:has-text('Notification Outbox')) + div.row");
var queueDepthH3 = outboxSection.Locator("div.card", new() { HasText = "Queue Depth" }).Locator("h3");
var outboxStuckH3 = outboxSection.Locator("div.card", new() { HasText = "Stuck" }).Locator("h3");
var outboxParkedH3 = outboxSection.Locator("div.card", new() { HasText = "Parked" }).Locator("h3");
await AssertTileResolvedAsync(queueDepthH3, "Outbox Queue Depth");
await AssertTileResolvedAsync(outboxStuckH3, "Outbox Stuck");
await AssertTileResolvedAsync(outboxParkedH3, "Outbox Parked");
// ── Audit KPI tiles (AuditKpiTiles.razor — data-test on the button) ──
var auditVolume = page.Locator("[data-test='audit-kpi-volume']").Locator("h3");
var auditErrorRate = page.Locator("[data-test='audit-kpi-error-rate']").Locator("h3");
var auditBacklog = page.Locator("[data-test='audit-kpi-backlog']").Locator("h3");
await AssertTileResolvedAsync(auditVolume, "Audit Volume");
await AssertTileResolvedAsync(auditErrorRate, "Audit Error Rate");
await AssertTileResolvedAsync(auditBacklog, "Audit Backlog");
// ── Site Call KPI tiles (SiteCallKpiTiles.razor — data-test on the button) ──
var siteCallBuffered = page.Locator("[data-test='site-call-kpi-buffered']").Locator("h3");
var siteCallStuck = page.Locator("[data-test='site-call-kpi-stuck']").Locator("h3");
var siteCallParked = page.Locator("[data-test='site-call-kpi-parked']").Locator("h3");
await AssertTileResolvedAsync(siteCallBuffered, "Site Call Buffered");
await AssertTileResolvedAsync(siteCallStuck, "Site Call Stuck");
await AssertTileResolvedAsync(siteCallParked, "Site Call Parked");
}
///
/// Waits up to 20 s for to be non-empty and not
/// equal to the em-dash degrade placeholder. Uses Playwright web-first
/// assertions so the retry loop is inside the Playwright engine, not a
/// C# busy-wait.
///
private static async Task AssertTileResolvedAsync(ILocator tileH3, string tileName)
{
// The tile must be visible and contain text before we check its value.
await Assertions.Expect(tileH3)
.ToBeVisibleAsync(new() { Timeout = 20_000 });
// Primary guard: the value must NOT be the degrade placeholder.
await Assertions.Expect(tileH3)
.Not.ToHaveTextAsync(DegradePlaceholder, new() { Timeout = 20_000 });
// Secondary guard: the value must be non-empty — it should be a digit string.
await Assertions.Expect(tileH3)
.Not.ToBeEmptyAsync(new() { Timeout = 20_000 });
// Diagnostic: capture the resolved text for context in test output, but
// don't fail on any particular number (the cluster state is environment-
// dependent and any non-negative integer is valid).
var resolvedText = await tileH3.TextContentAsync();
Assert.True(
resolvedText != null && resolvedText.Trim().Length > 0,
$"KPI tile '{tileName}' resolved to null/empty content.");
Assert.False(
resolvedText!.Trim() == DegradePlaceholder,
$"KPI tile '{tileName}' shows the degrade placeholder '—' — singleton Ask likely hung or faulted.");
}
}