Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTests.cs
T
Joseph Doherty 513fa1aa16 docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
2026-10-07 15:05:49 -04:00

146 lines
8.3 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 instance picker + deploy precondition (verified against
/// <c>DebugView.razor::LoadInstancesForSite</c>):</b> the instance
/// <c>InstancePicker</c> combobox (prefix <c>debug-instance</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 picker. The site <c>&lt;select&gt;</c> carries the <c>data-test</c> hook
/// <c>debug-site-select</c> (option value = entity id); the instance picker carries
/// the shared <c>InstancePicker</c> hooks (<c>debug-instance-picker</c>/<c>-input</c>/
/// <c>-toggle</c>/<c>-panel</c>/<c>-row</c>, row <c>data-instance-id</c> = 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 site 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;
/// <summary>Initializes the test class with the shared Playwright and cluster fixtures.</summary>
/// <param name="pw">The shared Playwright browser fixture.</param>
/// <param name="cluster">The shared deployment cluster fixture.</param>
public DebugViewTests(PlaywrightFixture pw, DeploymentFixture cluster)
{
_pw = pw;
_cluster = cluster;
}
/// <summary>Verifies that the Debug View controls render and the Connect button is gated on an instance selection.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[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 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-picker']")).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();
}
/// <summary>Verifies that a connect-enabled instance resolves and can be disconnected.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[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() });
await page.Locator("[data-test='debug-instance-input']").FillAsync(uniqueName);
var instanceRow = page.Locator(
$"[data-test='debug-instance-row'][data-instance-id='{instanceId}']");
await Assertions.Expect(instanceRow).ToBeVisibleAsync(new() { Timeout = 10_000 });
await instanceRow.ClickAsync();
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. Short timeout: the OR-floor
// above already resolved, so the Live badge is either present now or a toast
// resolved instead (the error path) — in which case this fails fast rather than
// burning the full window waiting for a badge that will never appear.
await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Live']"))
.ToBeVisibleAsync(new() { Timeout = 2_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);
}
}
}