test(playwright): DebugView tabs+trees — assert tabbed layout, tree panes, Alarms-tab switch on connected instance
This commit is contained in:
+134
@@ -0,0 +1,134 @@
|
||||
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 tabbed, tree-rendered Debug View
|
||||
/// (<c>/deployment/debug-view</c>) shipped by the debugview-tabs-trees feature.
|
||||
/// Where <see cref="DebugViewTests"/> proves the controls render and Connect
|
||||
/// resolves to a terminal state, this class proves the connected layout: a tab
|
||||
/// strip (Attributes / Alarms), each tab a composition <see cref="TreeView{T}"/>.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Seeding mirrors <see cref="DebugViewTests"/> exactly</b> — the Enabled-only
|
||||
/// instance <c><select></c> (verified against
|
||||
/// <c>DebugView.razor::LoadInstancesForSite</c>, <c>i.State == InstanceState.Enabled</c>)
|
||||
/// lists only Enabled instances. A freshly-minted instance is <c>NotDeployed</c>
|
||||
/// and would never appear, so the test mints an instance via the shared
|
||||
/// <see cref="DeploymentFixture"/> and drives it to <c>Enabled</c> over the CLI
|
||||
/// (<c>instance deploy</c>) before selecting it. The fixture provisions onto the
|
||||
/// real, running <c>site-a</c> so the ClusterClient snapshot round-trip resolves
|
||||
/// fast (an unknown site has no registered ClusterClient and only times out).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Tolerant terminal-state wait, then the tabs/trees assertions</b> — Connect
|
||||
/// awaits a ClusterClient snapshot round-trip; on success the page flips to the
|
||||
/// Live badge and renders the tabbed card, on failure it surfaces a
|
||||
/// <c>Connect failed: …</c> <c>.toast</c> (see <c>DebugView.razor::Connect</c>).
|
||||
/// The test first waits on a TERMINAL state (Live badge OR error toast) within a
|
||||
/// generous window so it never hangs, then asserts the Live badge directly
|
||||
/// (OBSERVED reliable on this live cluster in <see cref="DebugViewTests"/>). Only
|
||||
/// once connected does the card with the tab strip render
|
||||
/// (<c>@if (_connected && _snapshot != null)</c>), so the tabs/trees
|
||||
/// assertions are gated behind the Live badge.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Why the tree assertion is robust regardless of seeded alarms</b> — the
|
||||
/// <see cref="TreeView{T}"/> always renders its <c><ul role="tree"></c> root
|
||||
/// (with an <c>EmptyContent</c> "No alarms." / "No attributes." slot when the
|
||||
/// forest is empty), so asserting a <c>[role="tree"]</c> inside the active pane
|
||||
/// holds whether or not the fixture instance happens to carry a configured alarm
|
||||
/// or composition members. The test therefore does NOT depend on heavy alarm
|
||||
/// seeding: it proves the Alarms tab switches the visible pane and that pane hosts
|
||||
/// a tree, which is the structural contract of the tabs+trees rework.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Collection("Playwright")]
|
||||
public class DebugViewTreeTests : IClassFixture<DeploymentFixture>
|
||||
{
|
||||
private readonly PlaywrightFixture _pw;
|
||||
private readonly DeploymentFixture _cluster;
|
||||
|
||||
public DebugViewTreeTests(PlaywrightFixture pw, DeploymentFixture cluster)
|
||||
{
|
||||
_pw = pw;
|
||||
_cluster = cluster;
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task DebugView_Connected_RendersTabbedTrees_AndAlarmsTabSwitches()
|
||||
{
|
||||
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);
|
||||
|
||||
// Select the seeded site + Enabled instance (same hooks as DebugViewTests).
|
||||
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 floor: Live badge OR an error toast within a
|
||||
// generous window so the click never hangs (mirrors DebugViewTests). Then tighten
|
||||
// to the observed-reliable Live outcome — the tabbed card only renders when
|
||||
// connected, so the tabs/trees assertions below depend on reaching Live.
|
||||
var terminal = page.Locator("span.badge[aria-label='Connection state: Live'], .toast");
|
||||
await Assertions.Expect(terminal.First).ToBeVisibleAsync(new() { Timeout = 25_000 });
|
||||
await Assertions.Expect(page.Locator("span.badge[aria-label='Connection state: Live']"))
|
||||
.ToBeVisibleAsync(new() { Timeout = 2_000 });
|
||||
|
||||
// ── Tab strip renders with both tabs (the tabs+trees rework's structural hooks). ──
|
||||
var attrTab = page.Locator("[data-test='debug-tab-attributes']");
|
||||
var alarmTab = page.Locator("[data-test='debug-tab-alarms']");
|
||||
await Assertions.Expect(attrTab).ToBeVisibleAsync(new() { Timeout = 10_000 });
|
||||
await Assertions.Expect(alarmTab).ToBeVisibleAsync();
|
||||
|
||||
var attrPane = page.Locator("[data-test='debug-pane-attributes']");
|
||||
var alarmPane = page.Locator("[data-test='debug-pane-alarms']");
|
||||
|
||||
// Attributes is the default tab: its pane is visible and hosts a tree;
|
||||
// the Alarms pane is present in the DOM but hidden (d-none) until selected.
|
||||
await Assertions.Expect(attrTab).ToHaveAttributeAsync("aria-selected", "true");
|
||||
await Assertions.Expect(attrPane).ToBeVisibleAsync();
|
||||
await Assertions.Expect(attrPane.Locator("[role='tree']")).ToHaveCountAsync(1);
|
||||
await Assertions.Expect(alarmPane).ToBeHiddenAsync();
|
||||
|
||||
// ── Switch to the Alarms tab and assert the pane swaps + hosts its own tree. ──
|
||||
await alarmTab.ClickAsync();
|
||||
await Assertions.Expect(alarmTab).ToHaveAttributeAsync("aria-selected", "true");
|
||||
await Assertions.Expect(alarmPane).ToBeVisibleAsync(new() { Timeout = 10_000 });
|
||||
await Assertions.Expect(attrPane).ToBeHiddenAsync();
|
||||
// The TreeView always renders its <ul role="tree"> root (EmptyContent slot when
|
||||
// the forest is empty), so this holds whether or not the instance carries alarms.
|
||||
await Assertions.Expect(alarmPane.Locator("[role='tree']")).ToHaveCountAsync(1);
|
||||
|
||||
// Disconnect tears the card down and re-enables the selectors.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user