test(centralui): e2e execution-tree node detail modal + docs
This commit is contained in:
@@ -516,6 +516,110 @@ public class AuditLogPageTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DoubleClickTreeNode_OpensExecutionRowModal()
|
||||
{
|
||||
// Execution-Tree Node Detail Modal feature, Task 5: double-clicking a
|
||||
// node on the /audit/execution-tree page opens ExecutionDetailModal —
|
||||
// a modal listing that execution's audit rows, with click-through to
|
||||
// each row's full <AuditEventDetail> view. We seed ONE execution with
|
||||
// TWO audit rows (so the modal opens to the list view, not straight to
|
||||
// a single-row detail), open the tree, double-click the node, walk
|
||||
// list → row → detail, then close the modal.
|
||||
if (!await AuditDataSeeder.IsAvailableAsync())
|
||||
{
|
||||
throw new InvalidOperationException("MSSQL unavailable; see FilterNarrowing test for setup instructions.");
|
||||
}
|
||||
|
||||
var runId = Guid.NewGuid().ToString("N");
|
||||
var targetPrefix = $"playwright-test/exec-node-modal/{runId}/";
|
||||
var executionId = Guid.NewGuid();
|
||||
var inboundEventId = Guid.NewGuid();
|
||||
var outboundEventId = Guid.NewGuid();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
// Two rows sharing the same ExecutionId — an inbound request and an
|
||||
// outbound call it made. The shared ExecutionId makes the tree node
|
||||
// multi-row, so the modal lands on the list view.
|
||||
await AuditDataSeeder.InsertAuditEventAsync(
|
||||
eventId: inboundEventId,
|
||||
occurredAtUtc: now,
|
||||
channel: "ApiInbound",
|
||||
kind: "InboundRequest",
|
||||
status: "Delivered",
|
||||
target: targetPrefix + "inbound",
|
||||
executionId: executionId,
|
||||
httpStatus: 200,
|
||||
durationMs: 9);
|
||||
|
||||
await AuditDataSeeder.InsertAuditEventAsync(
|
||||
eventId: outboundEventId,
|
||||
occurredAtUtc: now,
|
||||
channel: "ApiOutbound",
|
||||
kind: "ApiCall",
|
||||
status: "Delivered",
|
||||
target: targetPrefix + "outbound",
|
||||
executionId: executionId,
|
||||
httpStatus: 200,
|
||||
durationMs: 21);
|
||||
|
||||
var page = await _fixture.NewAuthenticatedPageAsync();
|
||||
|
||||
// Open the execution tree directly for the seeded execution.
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/audit/execution-tree?executionId={executionId}");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// The seeded execution renders as a tree node.
|
||||
var nodeBody = page.Locator($"[data-test='tree-node-{executionId}'] .execution-tree-body");
|
||||
await Assertions.Expect(nodeBody).ToBeVisibleAsync();
|
||||
|
||||
// Double-clicking the node body raises ExecutionTree's @ondblclick,
|
||||
// which is a Blazor Server (InteractiveServer) handler — it only
|
||||
// fires once the SignalR circuit is live. NetworkIdle can settle
|
||||
// before the circuit connects, so a single early DblClick can be
|
||||
// dropped. Retry the double-click until the modal appears.
|
||||
var modal = page.Locator("[data-test='execution-detail-modal']");
|
||||
for (var attempt = 0; attempt < 10 && await modal.CountAsync() == 0; attempt++)
|
||||
{
|
||||
await nodeBody.DblClickAsync();
|
||||
try
|
||||
{
|
||||
await modal.WaitForAsync(new() { State = WaitForSelectorState.Visible, Timeout = 1000 });
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
// Circuit not connected yet — loop and re-issue the dblclick.
|
||||
}
|
||||
}
|
||||
|
||||
await Assertions.Expect(modal).ToBeVisibleAsync();
|
||||
|
||||
// The modal opens on the list view — one button per audit row.
|
||||
var inboundRow = page.Locator($"[data-test='execution-detail-row-{inboundEventId}']");
|
||||
var outboundRow = page.Locator($"[data-test='execution-detail-row-{outboundEventId}']");
|
||||
await Assertions.Expect(inboundRow).ToBeVisibleAsync();
|
||||
await Assertions.Expect(outboundRow).ToBeVisibleAsync();
|
||||
|
||||
// Clicking a row switches the modal to that row's full detail —
|
||||
// the shared <AuditEventDetail> field block renders.
|
||||
await outboundRow.ClickAsync();
|
||||
await Assertions.Expect(page.Locator("[data-test='drawer-fields']")).ToBeVisibleAsync();
|
||||
|
||||
// Closing the modal tears it down. The close click round-trips
|
||||
// over the SignalR circuit before the @if(IsOpen) block re-renders
|
||||
// away, so use the auto-retrying assertion rather than a bare
|
||||
// CountAsync.
|
||||
await page.Locator("[data-test='execution-detail-close']").ClickAsync();
|
||||
await Assertions.Expect(modal).ToHaveCountAsync(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await AuditDataSeeder.DeleteByTargetPrefixAsync(targetPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotificationsPage_RendersAuditDrillInLinkPattern()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user