test(e2e): Deployments page pushes deploy rows via SignalR; pause suppresses, Refresh restores
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
using Microsoft.Playwright;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
|
||||
|
||||
/// <summary>
|
||||
/// E2E coverage for the Deployment Status page's real-time, push-based update
|
||||
/// model (Tier 2). The page (<c>/deployment/deployments</c>) is NOT polled: in
|
||||
/// <c>OnInitializedAsync</c> it subscribes to
|
||||
/// <c>IDeploymentStatusNotifier.StatusChanged</c> — a process singleton the
|
||||
/// DeploymentManager raises on every deployment-record status write — and reloads
|
||||
/// the table on each notification, which Blazor Server pushes to the browser over
|
||||
/// its SignalR circuit. So with the page already open, a CLI <c>instance deploy</c>
|
||||
/// makes the instance's row surface with no manual Refresh: the row simply
|
||||
/// appearing IS the proof the push path works.
|
||||
///
|
||||
/// <para>
|
||||
/// Each fact mints a fresh ephemeral instance on the real <c>site-a</c> (via
|
||||
/// <see cref="DeploymentFixture.CreateInstanceAsync"/>) and deletes it in a
|
||||
/// <c>finally</c>. The minted <c>uniqueName</c> is unique, so its row can never
|
||||
/// pre-exist — the row's appearance is attributable solely to the deploy under test.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Why Fact B's negative is deterministic:</b> the Pause button toggles
|
||||
/// <c>_autoRefresh</c>, and <c>OnDeploymentStatusChanged</c> early-returns on
|
||||
/// <c>!_autoRefresh</c>, so a paused page deterministically ignores the
|
||||
/// <c>StatusChanged</c> notification. The pause click round-trips over the SignalR
|
||||
/// circuit (and we wait for the button to flip to "Resume" before deploying), so
|
||||
/// <c>_autoRefresh</c> is committed <c>false</c> server-side BEFORE the deploy fires
|
||||
/// — the suppression is not a race. Refresh calls <c>LoadDataAsync</c> directly,
|
||||
/// bypassing the pause guard, so the row surfaces on the explicit reload.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Collection("Playwright")]
|
||||
public class DeploymentsRealtimeTests : IClassFixture<DeploymentFixture>
|
||||
{
|
||||
private readonly PlaywrightFixture _pw;
|
||||
private readonly DeploymentFixture _cluster;
|
||||
|
||||
public DeploymentsRealtimeTests(PlaywrightFixture pw, DeploymentFixture cluster)
|
||||
{
|
||||
_pw = pw;
|
||||
_cluster = cluster;
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task DeployingInstance_PushesRowWithoutManualRefresh()
|
||||
{
|
||||
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
||||
|
||||
var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
|
||||
try
|
||||
{
|
||||
// Open the page FIRST so the StatusChanged subscription is live before we deploy.
|
||||
var page = await _pw.NewAuthenticatedPageAsync();
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/deployments");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await Assertions.Expect(page.Locator("h4:has-text('Deployment Status')")).ToBeVisibleAsync();
|
||||
|
||||
// Deploy over the CLI — this writes deployment records and raises StatusChanged,
|
||||
// which the page reloads on (push). No Refresh click here: the row appearing IS
|
||||
// the proof the SignalR push path works.
|
||||
await CliRunner.DeployInstanceAsync(instanceId);
|
||||
|
||||
var row = page.Locator("table tbody tr", new() { HasText = uniqueName });
|
||||
await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 25_000 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await CliRunner.DeleteInstanceAsync(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task PausedUpdates_SuppressPush_RefreshRestoresRow()
|
||||
{
|
||||
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
||||
|
||||
var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
|
||||
try
|
||||
{
|
||||
var page = await _pw.NewAuthenticatedPageAsync();
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/deployments");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// Pause — the click round-trips over the circuit, so _autoRefresh is committed
|
||||
// false before the deploy below. The button flipping to "Resume" proves it.
|
||||
await page.Locator("button[aria-label='Pause auto-refresh']").ClickAsync();
|
||||
await Assertions.Expect(page.Locator("button[aria-label='Resume auto-refresh']"))
|
||||
.ToBeVisibleAsync(new() { Timeout = 5_000 });
|
||||
|
||||
await CliRunner.DeployInstanceAsync(instanceId);
|
||||
|
||||
// Paused: StatusChanged is ignored, so the row is NOT auto-added. Settle briefly to
|
||||
// give any (erroneous) push time to manifest, then assert absence.
|
||||
var row = page.Locator("table tbody tr", new() { HasText = uniqueName });
|
||||
await page.WaitForTimeoutAsync(2_000);
|
||||
await Assertions.Expect(row).ToHaveCountAsync(0);
|
||||
|
||||
// Refresh bypasses the pause (LoadDataAsync) -> the row surfaces.
|
||||
await page.Locator("button[aria-label='Refresh deployments']").ClickAsync();
|
||||
await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 15_000 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await CliRunner.DeleteInstanceAsync(instanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user