From 271f70b1d24407ce5624e37c94f837e41ceb2f3d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 5 Jun 2026 10:25:16 -0400 Subject: [PATCH] test(e2e): standardize AuditLog tests on SkippableFact + skip summary log --- .../Audit/AuditLogPageTests.cs | 70 ++++++------------- .../Cluster/SkipSummaryReporter.cs | 23 ++++++ 2 files changed, 44 insertions(+), 49 deletions(-) create mode 100644 tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/SkipSummaryReporter.cs diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs index a74533ef..cbbf833a 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs @@ -1,4 +1,5 @@ using Microsoft.Playwright; +using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Audit; @@ -54,18 +55,10 @@ public class AuditLogPageTests _fixture = fixture; } - [Fact] + [SkippableFact] public async Task FilterNarrowing_ChannelFilterShrinksGrid() { - // Skip with a clear message when MSSQL is not reachable — the rest of - // the Playwright suite is UI-only and does not need the DB, so this - // surfaces a setup gap explicitly rather than as an opaque SqlException. - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException( - "AuditDataSeeder cannot reach MSSQL at localhost:1433 — bring up infra/docker-compose and docker/deploy.sh, " + - "or set SCADABRIDGE_PLAYWRIGHT_DB to a reachable connection string."); - } + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/filter-narrow/{runId}/"; @@ -119,13 +112,10 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task DrilldownDrawer_JsonPrettyPrintsRequestBody() { - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/drilldown-json/{runId}/"; @@ -184,13 +174,10 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task CopyAsCurlButton_IsVisibleAndClickableForApiInbound() { - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/curl-button/{runId}/"; @@ -239,13 +226,10 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task DrillInFromCorrelationId_LandsOnAuditLogWithFilterContext() { - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/drill-in/{runId}/"; @@ -299,18 +283,15 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task DrillInFromExecutionId_LandsOnAuditLogWithFilterContext() { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + // Mirrors the correlationId drill-in: the "View this execution" drawer // action navigates to /audit/log?executionId={ExecutionId}. We seed a row // carrying that ExecutionId, hit the deep link directly, and assert the // page deserializes the param and auto-loads the seeded row. - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } - var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/exec-drill-in/{runId}/"; var executionId = Guid.NewGuid(); @@ -357,19 +338,16 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task DrillInFromParentExecution_FiltersGridToSpawnerExecution() { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + // The drawer's "View parent execution" action navigates a routed (child) // row to /audit/log?executionId={ParentExecutionId}. We seed a spawner row // (its ExecutionId == the parent id) and a child row (ParentExecutionId // pointing at the spawner), open the child's drawer, click the action, and // assert the grid auto-loads filtered to the spawner's own rows. - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } - var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/parent-exec-drill-in/{runId}/"; var parentExecutionId = Guid.NewGuid(); @@ -437,19 +415,16 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task DrillInToExecutionChain_RendersTree_AndNodeClickFiltersGrid() { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + // Audit Log ParentExecutionId feature, Task 10: the drawer's "View // execution chain" action opens /audit/execution-tree?executionId={id}. // We seed a spawner row + a child row, open the child's drawer, click // "View execution chain", assert the tree renders BOTH executions, then // click the spawner node and assert the Audit Log grid filters to it. - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } - var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/exec-chain-tree/{runId}/"; var parentExecutionId = Guid.NewGuid(); @@ -519,9 +494,11 @@ public class AuditLogPageTests } } - [Fact] + [SkippableFact] public async Task DoubleClickTreeNode_OpensExecutionRowModal() { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + // Execution-Tree Node Detail Modal feature, Task 5: double-clicking a // node on the /audit/execution-tree page opens ExecutionDetailModal — // a modal listing that execution's audit rows, with click-through to @@ -529,11 +506,6 @@ public class AuditLogPageTests // TWO audit rows (so the modal opens to the list view, not straight to // a single-row detail), open the tree, double-click the node, walk // list → row → detail, then close the modal. - if (!await AuditDataSeeder.IsAvailableAsync()) - { - throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions."); - } - var runId = Guid.NewGuid().ToString("N"); var targetPrefix = $"playwright-test/exec-node-modal/{runId}/"; var executionId = Guid.NewGuid(); diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/SkipSummaryReporter.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/SkipSummaryReporter.cs new file mode 100644 index 00000000..775361e0 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/SkipSummaryReporter.cs @@ -0,0 +1,23 @@ +using System.Runtime.CompilerServices; + +namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; + +/// +/// Makes the suite's skip policy visible: at process exit, writes one console +/// line reporting how many cluster/DB-dependent test gates skipped because the +/// cluster was unavailable (see ). +/// Registered once via a so it runs +/// regardless of how the test host is launched (Task 3). +/// +internal static class SkipSummaryReporter +{ + [ModuleInitializer] + internal static void Init() => + AppDomain.CurrentDomain.ProcessExit += (_, _) => + { + if (ClusterAvailability.SkippedCount > 0) + { + Console.WriteLine($"[E2E] Skipped {ClusterAvailability.SkippedCount} cluster/DB-dependent test gate(s) — {ClusterAvailability.SkipReason}"); + } + }; +}