test(e2e): Topology move-area, move-instance, and Diff-dialog (deployed instance)
This commit is contained in:
+132
-2
@@ -6,8 +6,11 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
|
||||
|
||||
/// <summary>
|
||||
/// E2E coverage for the Topology page's area authoring surface — creating an area
|
||||
/// from the toolbar "+ Area" dialog (the site-picker variant), and the inline
|
||||
/// double-click rename on an area node's label.
|
||||
/// from the toolbar "+ Area" dialog (the site-picker variant), the inline
|
||||
/// double-click rename on an area node's label, the right-click "Move to Area…"
|
||||
/// flows for both an area (<c>MoveAreaDialog</c>) and an instance
|
||||
/// (<c>MoveInstanceDialog</c>), and the read-only "Diff" comparison
|
||||
/// (<c>DiffDialog</c>) for a deployed instance.
|
||||
///
|
||||
/// <para>
|
||||
/// Every fact opens the page through <see cref="OpenStableTopologyAsync"/>, which
|
||||
@@ -41,6 +44,26 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
|
||||
/// <see cref="CliRunner.DeleteAreaAsync"/>). A CLI read-back after each action
|
||||
/// confirms the change actually persisted, not just that the toast appeared.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Move flows: both "Move to Area…" context items open a one-<c><select></c>
|
||||
/// modal whose options carry the area name as their text (root-level areas render
|
||||
/// without indentation, so an exact visible-text match resolves). Unlike a text
|
||||
/// input, an HTML <c><select></c> fires <c>onchange</c> immediately on
|
||||
/// selection, so <see cref="ILocator.SelectOptionAsync(SelectOptionValue, FrameSelectOptionOptions)"/>
|
||||
/// commits the <c>@bind</c> directly (no <c>DispatchEventAsync</c> dance). The
|
||||
/// area dialog title is "Move area '…' to…" (<c>h6</c>) and the instance dialog
|
||||
/// "Move '…' to…" (<c>h6</c>); both succeed with a single "moved" toast.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Diff: the instance "Diff" item is <c>disabled</c> for a <c>NotDeployed</c>
|
||||
/// instance, so the Diff fact deploys first (over the CLI) to enable it. The
|
||||
/// resulting <c>DiffDialog</c> is computed centrally (no site relay), so the
|
||||
/// comparison is deterministic for a deployed instance; its title is
|
||||
/// "Deployment Diff — <uniqueName>" (<c>h5</c>, em-dash) and it closes via
|
||||
/// the footer Close button.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Collection("Playwright")]
|
||||
public class TopologyAreaTests : IClassFixture<DeploymentFixture>
|
||||
@@ -157,6 +180,113 @@ public class TopologyAreaTests : IClassFixture<DeploymentFixture>
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task MoveArea_UnderAnotherArea_ShowsMovedToast()
|
||||
{
|
||||
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
||||
|
||||
var parentName = CliRunner.UniqueName("mvpar");
|
||||
var childName = CliRunner.UniqueName("mvchild");
|
||||
var parentId = await CliRunner.CreateAreaAsync(_cluster.SiteAId, parentName);
|
||||
var childId = await CliRunner.CreateAreaAsync(_cluster.SiteAId, childName);
|
||||
try
|
||||
{
|
||||
var page = await OpenStableTopologyAsync(_pw);
|
||||
|
||||
var childRow = page.Locator("div.tv-row", new() { HasText = childName });
|
||||
await Assertions.Expect(childRow).ToBeVisibleAsync(new() { Timeout = 10_000 });
|
||||
await childRow.ScrollIntoViewIfNeededAsync();
|
||||
await childRow.ClickAsync(new() { Button = MouseButton.Right });
|
||||
await page.Locator(".dropdown-menu.show button.dropdown-item:has-text('Move to Area')").ClickAsync();
|
||||
|
||||
// MoveAreaDialog title is "Move area '<name>' to…" (h6) — distinct from the
|
||||
// instance dialog ("Move '<name>' to…"), so scope by the "Move area" text.
|
||||
var dialog = page.Locator(".modal.show:has(h6.modal-title:has-text('Move area'))");
|
||||
await Assertions.Expect(dialog).ToBeVisibleAsync();
|
||||
// Root-level area options render without indentation, so the parent's name is
|
||||
// the option's exact visible text. A <select> commits @bind on change, so the
|
||||
// selection alone flushes _targetParentId (no DispatchEventAsync needed).
|
||||
await dialog.Locator("select").SelectOptionAsync(new SelectOptionValue { Label = parentName });
|
||||
await dialog.Locator("button.btn.btn-primary.btn-sm:has-text('Move')").ClickAsync();
|
||||
|
||||
await Assertions.Expect(page.Locator(".toast")).ToHaveCountAsync(1, new() { Timeout = 15_000 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await CliRunner.DeleteAreaAsync(childId);
|
||||
await CliRunner.DeleteAreaAsync(parentId);
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task MoveInstance_ToArea_ShowsMovedToast()
|
||||
{
|
||||
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
||||
|
||||
var areaName = CliRunner.UniqueName("mvtgt");
|
||||
var areaId = await CliRunner.CreateAreaAsync(_cluster.SiteAId, areaName);
|
||||
var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
|
||||
try
|
||||
{
|
||||
var page = await OpenStableTopologyAsync(_pw);
|
||||
|
||||
var row = page.Locator("div.tv-row", new() { HasText = uniqueName });
|
||||
await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 10_000 });
|
||||
await row.ScrollIntoViewIfNeededAsync();
|
||||
await row.ClickAsync(new() { Button = MouseButton.Right });
|
||||
await page.Locator(".dropdown-menu.show button.dropdown-item:has-text('Move to Area')").ClickAsync();
|
||||
|
||||
// MoveInstanceDialog title is "Move '<name>' to…" (h6); the leading "Move '"
|
||||
// distinguishes it from the area dialog ("Move area '…'").
|
||||
var dialog = page.Locator(".modal.show:has(h6.modal-title:has-text(\"Move '\"))");
|
||||
await Assertions.Expect(dialog).ToBeVisibleAsync();
|
||||
await dialog.Locator("select").SelectOptionAsync(new SelectOptionValue { Label = areaName });
|
||||
await dialog.Locator("button.btn.btn-primary.btn-sm:has-text('Move')").ClickAsync();
|
||||
|
||||
await Assertions.Expect(page.Locator(".toast")).ToHaveCountAsync(1, new() { Timeout = 15_000 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await CliRunner.DeleteInstanceAsync(instanceId);
|
||||
await CliRunner.DeleteAreaAsync(areaId);
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task Diff_OnDeployedInstance_OpensDialog()
|
||||
{
|
||||
Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
|
||||
|
||||
var (instanceId, uniqueName) = await _cluster.CreateInstanceAsync();
|
||||
try
|
||||
{
|
||||
// Deploy leaves the instance in a state != NotDeployed, which enables the
|
||||
// otherwise-disabled "Diff" context item.
|
||||
await CliRunner.DeployInstanceAsync(instanceId);
|
||||
|
||||
var page = await OpenStableTopologyAsync(_pw);
|
||||
|
||||
var row = page.Locator("div.tv-row", new() { HasText = uniqueName });
|
||||
await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 10_000 });
|
||||
await row.ScrollIntoViewIfNeededAsync();
|
||||
await row.ClickAsync(new() { Button = MouseButton.Right });
|
||||
// :has-text('Diff') won't match "Debug View"; the menu's only "Diff" item is the diff action.
|
||||
await page.Locator(".dropdown-menu.show button.dropdown-item:has-text('Diff')").ClickAsync();
|
||||
|
||||
// DiffDialog markup is `.modal.fade.show.d-block`; `.modal.show` substring-matches.
|
||||
// Its title is an h5 (the move dialogs use h6) and reads "Deployment Diff — <uniqueName>".
|
||||
var diff = page.Locator(".modal.show:has(h5.modal-title:has-text('Deployment Diff'))");
|
||||
await Assertions.Expect(diff).ToBeVisibleAsync(new() { Timeout = 15_000 });
|
||||
await Assertions.Expect(diff.Locator("h5.modal-title")).ToContainTextAsync(uniqueName);
|
||||
await diff.Locator("button.btn.btn-secondary.btn-sm:has-text('Close')").ClickAsync();
|
||||
await Assertions.Expect(diff).ToHaveCountAsync(0, new() { Timeout = 5_000 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
await CliRunner.DeleteInstanceAsync(instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the Topology page and returns a page whose tree is stable and
|
||||
/// fully expanded: it unchecks the <c>#live-updates</c> switch (stopping the 15s
|
||||
|
||||
Reference in New Issue
Block a user