test(ui): keep reconnect session fixtures consistent

The reconnect cases supplied new titles only in a deferred sessions.list
reply while the mock Gateway retained old canonical rows. A later
sessions.describe response could correctly outrank that older list request
and leave the sidebar showing the old title.

Update the canonical fixture rows before reconnecting, defer the exact
global preview request, and release its normal response. Preserve every
assertion and reconnect/poll budget.

Validation: two-CPU Testbox pressure with one busy process; controlled
before failure 1/1 and after failure 0/1; natural baseline 0/3 and candidate
0/20 failures (220 assertions). All three original 78-file shard replays
passed 303/303, including 33/33 target cases. A fresh-main integration
also passed all 11 cases. P2 review, formatting, diff checks, typed lint,
rejecting canary, coercion and dead-export checks passed. The full UI type
graph retains two independently reproduced baseline Promise.withResolvers
errors in chat-person-reference and chat-scroll tests.

The existing browser reconnect, observer and row-retention coverage costs
44.133 seconds for all 11 cases with one worker.
Sighting: CI run 35673180468, job 106574292641.
Native Testbox proof: run 35869189032; integration: run 35877301218.
This commit is contained in:
Peter Steinberger 2026-09-23 08:19:25 -07:00
parent 359a8a35b5
commit 470c69ced0
No known key found for this signature in database

View file

@ -25,6 +25,7 @@ import {
const suite = createSessionManagementE2eSuite();
const rosterMatch = { includeGlobal: true };
const rosterPreviewMatch = { ...rosterMatch, includeLastMessage: true };
function sessionActionPresentation(button: Locator) {
return button.evaluate((element) => {
@ -506,7 +507,6 @@ suite.define(() => {
await expect.poll(() => pinnedEntry.count()).toBe(1);
const sidebarRows = page.locator(".sidebar-recent-session");
await expect.poll(() => sidebarRows.count()).toBe(3);
const initialListCount = (await gateway.getRequests("sessions.list", rosterMatch)).length;
const socketsBefore = await gateway.getSocketCount();
await gateway.setOnline(false);
@ -532,14 +532,23 @@ suite.define(() => {
await expect.poll(() => pinnedEntry.count()).toBe(1);
await captureUiProof(suite, page, "sidebar-sessions-during-client-replacement.png");
await gateway.deferNext("sessions.list", { includeLastMessage: true });
const refreshedResponse = sessionsListResponse([
sessionRow(sessionKey, "Reconnect refreshed", Date.parse("2026-07-01T16:01:00.000Z")),
sessionRow(otherSessionKeys[0], "Other A", Date.parse("2026-07-01T15:59:00.000Z")),
sessionRow(otherSessionKeys[1], "Other B", Date.parse("2026-07-01T15:58:00.000Z")),
]);
// Reconnect descriptors and roster reads must observe the same Gateway-owned rows.
await gateway.setSessionsListResponse(refreshedResponse);
await gateway.deferNext("sessions.list", rosterPreviewMatch);
const reconnectPreviewCount = (await gateway.getRequests("sessions.list", rosterPreviewMatch))
.length;
await gateway.setOnline(true);
await waitForControlUiGatewayReady(page);
await expect
.poll(async () => (await gateway.getRequests("sessions.list", rosterMatch)).length, {
.poll(async () => (await gateway.getRequests("sessions.list", rosterPreviewMatch)).length, {
timeout: 15_000,
})
.toBeGreaterThan(initialListCount);
.toBeGreaterThan(reconnectPreviewCount);
await sidebarRow.waitFor({ state: "visible" });
expect(await sidebarRows.count()).toBe(3);
for (const otherKey of otherSessionKeys) {
@ -550,12 +559,7 @@ suite.define(() => {
const firstReconnectListCount = (await gateway.getRequests("sessions.list", rosterMatch))
.length;
const refreshedResponse = sessionsListResponse([
sessionRow(sessionKey, "Reconnect refreshed", Date.parse("2026-07-01T16:01:00.000Z")),
sessionRow(otherSessionKeys[0], "Other A", Date.parse("2026-07-01T15:59:00.000Z")),
sessionRow(otherSessionKeys[1], "Other B", Date.parse("2026-07-01T15:58:00.000Z")),
]);
await gateway.resolveDeferred("sessions.list", refreshedResponse);
await gateway.resolveDeferred("sessions.list");
await expect.poll(() => sidebarRow.textContent()).toContain("Reconnect refreshed");
await expect.poll(() => sidebarRows.count()).toBe(3);
await expectRequestCountStable(
@ -599,7 +603,6 @@ suite.define(() => {
await expect.poll(() => new URL(page.url()).pathname).toBe(controlUiSessionPath(selectedKey));
await expect.poll(() => selectedRow.getAttribute("class")).toContain("--active");
const initialObserverCount = (await gateway.getRequests("sessions.subscribe")).length;
const initialListCount = (await gateway.getRequests("sessions.list", rosterMatch)).length;
const socketsBefore = await gateway.getSocketCount();
await gateway.setOnline(false);
@ -609,8 +612,21 @@ suite.define(() => {
await expect
.poll(() => gateway.getSocketCount(), { timeout: 15_000 })
.toBe(socketsBefore + 1);
await gateway.setSessionsListResponse(
sessionsListResponse([
sessionRow(firstKey, "First session", Date.parse("2026-07-01T16:00:00.000Z")),
sessionRow(
selectedKey,
"Selected session recovered",
Date.parse("2026-07-01T16:01:00.000Z"),
),
]),
);
await gateway.deferNext("sessions.subscribe");
await gateway.deferNext("sessions.list", { includeLastMessage: true });
await gateway.deferNext("sessions.list", rosterPreviewMatch);
// Capture after disconnect so late requests from the old client cannot satisfy this wait.
const reconnectPreviewCount = (await gateway.getRequests("sessions.list", rosterPreviewMatch))
.length;
await gateway.setOnline(true);
await waitForControlUiGatewayReady(page);
await expect
@ -629,23 +645,9 @@ suite.define(() => {
await gateway.resolveDeferred("sessions.subscribe", { subscribed: true });
await expect
.poll(async () =>
(await gateway.getRequests("sessions.list", rosterMatch))
.slice(initialListCount)
.some((request) => requireRecord(request.params).includeLastMessage === true),
)
.toBe(true);
await gateway.resolveDeferred(
"sessions.list",
sessionsListResponse([
sessionRow(firstKey, "First session", Date.parse("2026-07-01T16:00:00.000Z")),
sessionRow(
selectedKey,
"Selected session recovered",
Date.parse("2026-07-01T16:01:00.000Z"),
),
]),
);
.poll(async () => (await gateway.getRequests("sessions.list", rosterPreviewMatch)).length)
.toBeGreaterThan(reconnectPreviewCount);
await gateway.resolveDeferred("sessions.list");
await expect.poll(() => selectedRow.textContent()).toContain("Selected session recovered");
await expect.poll(() => selectedRow.getAttribute("class")).toContain("--active");
await expect.poll(() => page.locator(".sidebar-recent-session--active").count()).toBe(1);