fix(centralui): stabilise Site Calls + Audit grid Playwright E2E
Three Playwright E2E failures, all test-side timing/data bugs (no
feature defects found):
- AuditGridColumnTests.ColumnOrderAndWidths_PersistAcrossReload: read
sessionStorage synchronously right after Mouse.UpAsync, racing the
async OnColumnResized/OnColumnReordered JS->.NET->JS save round-trip.
Now polls (WaitForFunctionAsync) for the storage keys and for the
reorder re-render to settle; also hardens the flaky ReorderDrag test.
- SiteCallsPageTests.FilterNarrowing_ChannelFilterShrinksGrid: the
Target-keyword #sc-search @bind committed via the Query click's own
blur, racing change vs click on the circuit so Search() sometimes
ran with a stale empty filter. Commit the value with an explicit,
fully-awaited DispatchEventAsync('change') and use the retrying
ToHaveCount assertion for the negative row checks.
- SiteCallsPageTests.RetryClickThrough_OnParkedRow: seeded SourceSite
'plant-a' is not a real cluster site (site-a/b/c), so the relay had
no ClusterClient route and only resolved on the 10s inner Ask
timeout - past the 5s toast wait. Seed a live site (site-a) for a
fast NotParked round-trip and give the toast a 15s wait.
Playwright E2E suite: 60 passed, 0 failed, 0 skipped.
This commit is contained in:
@@ -83,6 +83,36 @@ public class AuditGridColumnTests
|
||||
.EvaluateAllAsync<string[]>("els => els.map(e => e.getAttribute('data-col-key'))");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls until <paramref name="storageKey"/> has been written to
|
||||
/// <c>sessionStorage</c>. The grid persists a resize/reorder
|
||||
/// asynchronously — the browser-side drag fires a fire-and-forget
|
||||
/// JS→.NET invoke (<c>OnColumnResized</c>/<c>OnColumnReordered</c>), and
|
||||
/// the .NET handler then round-trips back through JS interop to write
|
||||
/// <c>sessionStorage</c>. A bare <c>getItem</c> immediately after the drag
|
||||
/// races that round-trip; this waits for the key to actually land.
|
||||
/// </summary>
|
||||
private static async Task WaitForStorageKeyAsync(IPage page, string storageKey)
|
||||
{
|
||||
await page.WaitForFunctionAsync(
|
||||
"key => sessionStorage.getItem(key) !== null", storageKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls until the header's first column key equals <paramref name="expectedFirstKey"/>.
|
||||
/// A drag-to-reorder re-renders the header asynchronously (the JS→.NET
|
||||
/// <c>OnColumnReordered</c> invoke is fire-and-forget), so reading the
|
||||
/// header order synchronously after <c>DragToAsync</c> can observe the
|
||||
/// pre-reorder layout. This waits for the re-render to settle.
|
||||
/// </summary>
|
||||
private static async Task WaitForFirstColumnAsync(IPage page, string expectedFirstKey)
|
||||
{
|
||||
await page.WaitForFunctionAsync(
|
||||
"key => { var th = document.querySelector('thead th[data-col-key]'); " +
|
||||
"return th && th.getAttribute('data-col-key') === key; }",
|
||||
expectedFirstKey);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task ResizeHandle_DraggingWidensColumn_AndSurvivesReload()
|
||||
{
|
||||
@@ -156,18 +186,22 @@ public class AuditGridColumnTests
|
||||
var source = page.Locator("[data-col-key='Status']");
|
||||
var target = page.Locator("[data-col-key='OccurredAtUtc']");
|
||||
await source.DragToAsync(target);
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
// The reorder re-renders the header asynchronously (fire-and-forget
|
||||
// JS→.NET invoke); wait for it to settle before reading the order.
|
||||
await WaitForFirstColumnAsync(page, "Status");
|
||||
|
||||
var afterOrder = await HeaderOrderAsync(page);
|
||||
Assert.Equal("Status", afterOrder[0]);
|
||||
Assert.True(afterOrder.ToList().IndexOf("Status") < afterOrder.ToList().IndexOf("OccurredAtUtc"),
|
||||
"Expected Status to be reordered ahead of OccurredAtUtc.");
|
||||
|
||||
// Reload: the persisted order is restored from sessionStorage.
|
||||
// Reload: the persisted order is restored from sessionStorage on
|
||||
// the grid's first render — wait for the header to reflect it.
|
||||
await page.ReloadAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await page.Locator("[data-test='filter-apply']").ClickAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await WaitForFirstColumnAsync(page, "Status");
|
||||
|
||||
var afterReload = await HeaderOrderAsync(page);
|
||||
Assert.Equal("Status", afterReload[0]);
|
||||
@@ -194,7 +228,10 @@ public class AuditGridColumnTests
|
||||
// Reorder then resize, then confirm sessionStorage carries both.
|
||||
await page.Locator("[data-col-key='Status']")
|
||||
.DragToAsync(page.Locator("[data-col-key='OccurredAtUtc']"));
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
// Wait for the reorder re-render to settle before measuring the
|
||||
// resize handle, so the handle's bounding box is read off the
|
||||
// post-reorder layout.
|
||||
await WaitForFirstColumnAsync(page, "Status");
|
||||
|
||||
var handle = page.Locator("[data-test='col-resize-Target']");
|
||||
var handleBox = await handle.BoundingBoxAsync();
|
||||
@@ -206,7 +243,15 @@ public class AuditGridColumnTests
|
||||
await page.Mouse.MoveAsync(startX + 90, startY, new MouseMoveOptions { Steps = 6 });
|
||||
await page.Mouse.UpAsync();
|
||||
|
||||
// Both keys are written under the auditGrid: namespace.
|
||||
// Both keys are written under the auditGrid: namespace — but the
|
||||
// write is asynchronous: pointer-up fires a fire-and-forget
|
||||
// OnColumnResized/OnColumnReordered JS→.NET invoke, and the .NET
|
||||
// handler then round-trips back through JS interop to call
|
||||
// auditGrid.save. Reading sessionStorage synchronously right after
|
||||
// Mouse.UpAsync races that round-trip, so poll for both keys to
|
||||
// land before asserting on them.
|
||||
await WaitForStorageKeyAsync(page, "auditGrid:columnOrder");
|
||||
await WaitForStorageKeyAsync(page, "auditGrid:columnWidths");
|
||||
var orderJson = await page.EvaluateAsync<string?>(
|
||||
"() => sessionStorage.getItem('auditGrid:columnOrder')");
|
||||
var widthsJson = await page.EvaluateAsync<string?>(
|
||||
@@ -216,11 +261,14 @@ public class AuditGridColumnTests
|
||||
Assert.NotNull(widthsJson);
|
||||
Assert.Contains("Target", widthsJson!);
|
||||
|
||||
// After a reload the restored grid reflects the stored order.
|
||||
// After a reload the restored grid reflects the stored order. The
|
||||
// restore happens on the grid's first render (LoadPersistedState →
|
||||
// StateHasChanged), so wait for the header to reflect it.
|
||||
await page.ReloadAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await page.Locator("[data-test='filter-apply']").ClickAsync();
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await WaitForFirstColumnAsync(page, "Status");
|
||||
|
||||
var restoredOrder = await HeaderOrderAsync(page);
|
||||
Assert.Equal("Status", restoredOrder[0]);
|
||||
|
||||
Reference in New Issue
Block a user