test(playwright): Site Calls status-filter + empty-state edge cases (Wave 4)
This commit is contained in:
+104
@@ -387,4 +387,108 @@ public class SiteCallsPageTests
|
||||
await SiteCallDataSeeder.DeleteByTargetPrefixAsync(targetPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Status dropdown filters the grid to the selected lifecycle status.
|
||||
/// Seeds two rows sharing a per-run Target prefix — one <c>Parked</c>, one
|
||||
/// <c>Delivered</c> — then filters status=Parked: the Parked marker row
|
||||
/// surfaces (with a <c>Parked</c> status badge) while the Delivered marker
|
||||
/// row is excluded even though both share the prefix. This mirrors
|
||||
/// <see cref="FilterNarrowing_ChannelFilterShrinksGrid"/> but exercises the
|
||||
/// STATUS axis rather than the channel axis.
|
||||
/// <para>
|
||||
/// <c>SourceSite</c> is <c>site-a</c> (a permitted site) on both rows —
|
||||
/// <c>FilterPermittedAsync</c> drops rows whose source site is not permitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task StatusFilter_NarrowsToSelectedStatus()
|
||||
{
|
||||
Skip.IfNot(await SiteCallDataSeeder.IsAvailableAsync(), DbUnavailableSkipReason);
|
||||
|
||||
var runId = Guid.NewGuid().ToString("N");
|
||||
var targetPrefix = $"playwright-test/wave4-sc/{runId}/";
|
||||
var parkedId = Guid.NewGuid();
|
||||
var deliveredId = Guid.NewGuid();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
// Two rows sharing the prefix: one Parked, one Delivered. site-a is a
|
||||
// permitted source site (FilterPermittedAsync keeps it).
|
||||
await SiteCallDataSeeder.InsertSiteCallAsync(
|
||||
trackedOperationId: parkedId, channel: "ApiOutbound", target: targetPrefix + "parked",
|
||||
sourceSite: "site-a", status: "Parked", retryCount: 3,
|
||||
lastError: "HTTP 503 from ERP", httpStatus: 503,
|
||||
createdAtUtc: now, updatedAtUtc: now);
|
||||
await SiteCallDataSeeder.InsertSiteCallAsync(
|
||||
trackedOperationId: deliveredId, channel: "ApiOutbound", target: targetPrefix + "delivered",
|
||||
sourceSite: "site-a", status: "Delivered", retryCount: 0,
|
||||
createdAtUtc: now, updatedAtUtc: now, httpStatus: 200, terminalAtUtc: now);
|
||||
|
||||
var page = await _fixture.NewAuthenticatedPageAsync();
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{SiteCallsUrl}");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// Filter status=Parked and scope to the exact Parked marker — the row
|
||||
// surfaces and its status badge reads Parked.
|
||||
await page.Locator("#sc-status").SelectOptionAsync("Parked");
|
||||
await SetSearchKeywordAsync(page, targetPrefix + "parked");
|
||||
await page.ClickAsync("[data-test='site-calls-query']");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
var parkedRow = page.Locator("tbody tr", new() { HasText = targetPrefix + "parked" });
|
||||
await Assertions.Expect(parkedRow).ToBeVisibleAsync();
|
||||
await Assertions.Expect(parkedRow.Locator("span.badge:has-text('Parked')")).ToBeVisibleAsync();
|
||||
|
||||
// Same status=Parked filter, now searching the Delivered marker: the
|
||||
// Delivered row is excluded by the status filter, so no row carries
|
||||
// its marker. The retrying ToHaveCount waits out the re-render.
|
||||
await SetSearchKeywordAsync(page, targetPrefix + "delivered");
|
||||
await page.ClickAsync("[data-test='site-calls-query']");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
await Assertions.Expect(
|
||||
page.Locator("tbody tr").Filter(new() { HasText = targetPrefix + "delivered" }))
|
||||
.ToHaveCountAsync(0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await SiteCallDataSeeder.DeleteByTargetPrefixAsync(targetPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the filters match no rows the grid renders the empty-state card
|
||||
/// rather than a table. A per-run GUID Target is searched (exact match), so
|
||||
/// nothing can match — guaranteed empty without seeding. Asserts both the
|
||||
/// absence of data rows and the empty-state literal.
|
||||
/// <para>
|
||||
/// The empty-state literal lives in <c>div.card > div.card-body…</c>, but
|
||||
/// the filter card also uses <c>.card-body</c>, so a bare <c>.card-body</c>
|
||||
/// locator is ambiguous under strict mode. We assert the literal via
|
||||
/// <see cref="IPage.GetByText(string,PageGetByTextOptions)"/> instead.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task EmptyState_NoMatch_ShowsEmptyCard()
|
||||
{
|
||||
Skip.IfNot(await SiteCallDataSeeder.IsAvailableAsync(), DbUnavailableSkipReason);
|
||||
|
||||
var page = await _fixture.NewAuthenticatedPageAsync();
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}{SiteCallsUrl}");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// A per-run GUID target matches nothing (exact match → guaranteed empty).
|
||||
await SetSearchKeywordAsync(page, $"playwright-test/wave4-sc-empty/{Guid.NewGuid():N}/none");
|
||||
await page.ClickAsync("[data-test='site-calls-query']");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// No data rows, and the empty-state literal renders. GetByText avoids the
|
||||
// strict-mode ambiguity of the shared .card-body class.
|
||||
await Assertions.Expect(page.Locator("tbody tr")).ToHaveCountAsync(0);
|
||||
await Assertions.Expect(
|
||||
page.GetByText("No cached calls match the current filters."))
|
||||
.ToBeVisibleAsync();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user