133 lines
7.2 KiB
C#
133 lines
7.2 KiB
C#
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 Debug View page (<c>/deployment/debug-view</c>), which
|
|
/// live-streams a single instance's attribute values and alarm states from its
|
|
/// owning site. Two facts: the controls always render with Connect correctly
|
|
/// gated, and connecting a freshly-deployed instance resolves to a terminal state
|
|
/// without hanging.
|
|
///
|
|
/// <para>
|
|
/// <b>The Enabled-only dropdown + deploy precondition (verified against
|
|
/// <c>DebugView.razor::LoadInstancesForSite</c>):</b> the instance <c><select></c>
|
|
/// lists ONLY instances whose config state is <c>Enabled</c>
|
|
/// (<c>i.State == InstanceState.Enabled</c>). A freshly-minted instance is
|
|
/// <c>NotDeployed</c> and would never appear, so Fact B first drives the instance
|
|
/// to <c>Enabled</c> over the CLI (<c>instance deploy</c>) before it can be picked
|
|
/// in the dropdown. Both <c><select></c>s carry the <c>data-test</c> hooks
|
|
/// (<c>debug-site-select</c> / <c>debug-instance-select</c>, option value = entity
|
|
/// id); Fact A asserts those hooks are served, which doubles as proof the cluster
|
|
/// rebuild that added them actually took.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>Tolerant terminal-state assertion (validation-behavior protocol):</b> whether
|
|
/// <c>Connect</c> reaches a <b>Live</b> badge depends on the owning site returning a
|
|
/// snapshot for the instance. Reading <c>DebugView.razor::Connect</c>: it awaits
|
|
/// <c>DebugStreamService.StartStreamAsync</c> (a ClusterClient snapshot round-trip);
|
|
/// on success it flips <c>_connected = true</c> (Live badge + success toast), and on
|
|
/// any exception it surfaces a <c>Connect failed: …</c> error <c>.toast</c>. Either
|
|
/// way <c>_connecting</c> resets, so the click always resolves — it never hangs. The
|
|
/// protocol therefore first waits on a TERMINAL state (Live badge OR an error toast)
|
|
/// within a generous window. OBSERVED reality on this live cluster (4 runs): Connect
|
|
/// RELIABLY reaches <b>Live</b> in ~1s with a <c>Success: Streaming …</c> toast (never
|
|
/// an error), so — as the protocol permits — Fact B is tightened past the tolerant
|
|
/// floor to assert the Live badge, then Disconnect → Disconnected badge + selects
|
|
/// re-enabled. The tolerant OR-wait is kept as a belt-and-braces floor so a one-off
|
|
/// snapshot hiccup degrades to a clear missing-Live assertion failure, not a hang.
|
|
/// </para>
|
|
/// </summary>
|
|
[Collection("Playwright")]
|
|
public class DebugViewTests : IClassFixture<DeploymentFixture>
|
|
{
|
|
private readonly PlaywrightFixture _pw;
|
|
private readonly DeploymentFixture _cluster;
|
|
|
|
public DebugViewTests(PlaywrightFixture pw, DeploymentFixture cluster)
|
|
{
|
|
_pw = pw;
|
|
_cluster = cluster;
|
|
}
|
|
|
|
[SkippableFact]
|
|
public async Task DebugView_ControlsRender_ConnectGatedOnSelection()
|
|
{
|
|
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
|
|
|
var page = await _pw.NewAuthenticatedPageAsync();
|
|
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/debug-view");
|
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
|
|
|
await Assertions.Expect(page.Locator("h4:has-text('Debug View')")).ToBeVisibleAsync();
|
|
// The data-test hooks from Task 4 must be served (proves the rebuild took).
|
|
await Assertions.Expect(page.Locator("[data-test='debug-site-select']")).ToHaveCountAsync(1);
|
|
await Assertions.Expect(page.Locator("[data-test='debug-instance-select']")).ToHaveCountAsync(1);
|
|
|
|
// No site/instance selected -> Connect is disabled.
|
|
var connect = page.Locator("button.btn.btn-primary.btn-sm:has-text('Connect')");
|
|
await Assertions.Expect(connect).ToBeDisabledAsync();
|
|
await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Disconnected']"))
|
|
.ToBeVisibleAsync();
|
|
}
|
|
|
|
[SkippableFact]
|
|
public async Task DebugView_ConnectEnabledInstance_ResolvesAndDisconnect()
|
|
{
|
|
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
|
|
|
var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
|
|
try
|
|
{
|
|
await CliRunner.DeployInstanceAsync(instanceId); // -> Enabled, so it lists in the dropdown
|
|
|
|
var page = await _pw.NewAuthenticatedPageAsync();
|
|
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/deployment/debug-view");
|
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
|
|
|
await page.Locator("[data-test='debug-site-select']")
|
|
.SelectOptionAsync(new SelectOptionValue { Value = _cluster.SiteAId.ToString() });
|
|
var instanceOption = page.Locator("[data-test='debug-instance-select'] option",
|
|
new() { HasText = uniqueName });
|
|
await Assertions.Expect(instanceOption).ToHaveCountAsync(1, new() { Timeout = 10_000 });
|
|
await page.Locator("[data-test='debug-instance-select']")
|
|
.SelectOptionAsync(new SelectOptionValue { Value = instanceId.ToString() });
|
|
|
|
var connect = page.Locator("button.btn.btn-primary.btn-sm:has-text('Connect')");
|
|
await Assertions.Expect(connect).ToBeEnabledAsync(new() { Timeout = 10_000 });
|
|
await connect.ClickAsync();
|
|
|
|
// Outcome-tolerant terminal-state wait: Connect resolves to Live badge OR an error
|
|
// toast within a generous window rather than hanging. The OR-form is the tolerant
|
|
// floor required by the validation-behavior protocol.
|
|
//
|
|
// OBSERVED on this live cluster (4 runs): Connect RELIABLY reaches Live — the site's
|
|
// snapshot round-trip succeeds for a freshly-deployed zztest instance in ~1s and the
|
|
// page shows the Live badge plus a "Success: Streaming <uniqueName>" toast every time
|
|
// (never an error toast). Because Live is reliable, the protocol permits tightening:
|
|
// after the tolerant wait below resolves, this fact asserts the Live badge directly,
|
|
// then Disconnect → Disconnected badge + selects re-enabled. The OR-locator stays as a
|
|
// belt-and-braces floor so a one-off snapshot hiccup degrades to a clear assertion
|
|
// failure on the missing Live badge rather than a 25s hang.
|
|
var terminal = page.Locator("span.badge[aria-label='Connection state: Live'], .toast");
|
|
await Assertions.Expect(terminal.First).ToBeVisibleAsync(new() { Timeout = 25_000 });
|
|
|
|
// Tightened to the observed-reliable Live outcome.
|
|
await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Live']"))
|
|
.ToBeVisibleAsync(new() { Timeout = 25_000 });
|
|
|
|
await page.Locator("button.btn-outline-danger.btn-sm:has-text('Disconnect')").ClickAsync();
|
|
await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Disconnected']"))
|
|
.ToBeVisibleAsync(new() { Timeout = 10_000 });
|
|
await Assertions.Expect(page.Locator("[data-test='debug-site-select']")).ToBeEnabledAsync();
|
|
}
|
|
finally
|
|
{
|
|
await CliRunner.DeleteInstanceAsync(instanceId);
|
|
}
|
|
}
|
|
}
|