test: reproduce workbench live backfill race

This commit is contained in:
lyon
2026-06-18 18:45:07 +08:00
parent 6bc2301022
commit b24584f07c
2 changed files with 84 additions and 3 deletions
@@ -33,6 +33,8 @@ interface ScenarioState {
terminalScript: boolean;
terminalFailureScript: boolean;
staleTraceId: string | null;
liveBackfillTraceId: string | null;
liveBackfillReadyAtMs: number | null;
}
const cwd = process.cwd();
@@ -280,7 +282,9 @@ function createScenarioState(scenarioId: string): ScenarioState {
chatDelayMs: id === "submit-authority-race" ? 1_000 : 0,
terminalScript: id === "event-replay" || id === "running-to-terminal" || id === "stale-submit-restore",
terminalFailureScript: id === "stale-submit-restore",
staleTraceId
staleTraceId,
liveBackfillTraceId: null,
liveBackfillReadyAtMs: null
};
}
@@ -400,7 +404,9 @@ function acceptChatTurn(body: JsonRecord): JsonRecord {
const session = sessionById(sessionId) ?? createManualSession({ sessionId });
if (!state.sessions.includes(session)) state.sessions.unshift(session);
const threadId = typeof body.threadId === "string" && body.threadId.trim() ? body.threadId : null;
const traceId = typeof body.traceId === "string" && body.traceId.trim() ? body.traceId : `trc_${Date.now().toString(16)}`;
const traceId = state.scenarioId === "live-rest-backfill-without-terminal-sse"
? "trc_live_backfill"
: typeof body.traceId === "string" && body.traceId.trim() ? body.traceId : `trc_${Date.now().toString(16)}`;
const message = typeof body.message === "string" ? body.message : "";
const now = new Date().toISOString();
session.status = "running";
@@ -413,10 +419,47 @@ function acceptChatTurn(body: JsonRecord): JsonRecord {
{ id: `msg_${traceId}_user`, role: "user", title: "用户", text: message, status: "sent", createdAt: now, sessionId, threadId, traceId },
{ id: `msg_${traceId}_agent`, role: "agent", title: "Code Agent", text: "", status: "running", createdAt: now, sessionId, threadId, traceId, runnerTrace: { traceId, status: "running", sessionId, threadId, events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false } }
];
state.traces[traceId] = { traceId, status: "running", sessionId, threadId, events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false };
if (state.scenarioId === "live-rest-backfill-without-terminal-sse") {
state.liveBackfillTraceId = traceId;
state.liveBackfillReadyAtMs = Date.now() + 2_000;
state.traces[traceId] = liveBackfillEarlyTrace(sessionId, threadId, traceId);
} else {
state.traces[traceId] = { traceId, status: "running", sessionId, threadId, events: [], eventCount: 0, fullTraceLoaded: false, hasMore: false };
}
return { status: "running", accepted: true, running: true, terminal: false, traceId, sessionId, threadId, turnId: traceId };
}
function liveBackfillEarlyTrace(sessionId: string, threadId: string | null, traceId: string): JsonRecord {
const createdAt = new Date().toISOString();
return {
traceId,
status: "running",
sessionId,
threadId,
events: [
{ seq: 1, sourceSeq: 1, createdAt, label: "agentrun:request:accepted", type: "backend_status", status: "running" },
{ seq: 2, sourceSeq: 2, createdAt, label: "agentrun:run:created", type: "backend_status", status: "running" },
{ seq: 3, sourceSeq: 3, createdAt, label: "agentrun:command:created", type: "backend_status", status: "running" },
{ seq: 4, sourceSeq: 4, createdAt, label: "agentrun:runner-job:created", type: "backend_status", status: "running" }
],
eventCount: 4,
fullTraceLoaded: true,
hasMore: false
};
}
function liveBackfillCompletedTrace(sessionId: string, threadId: string | null, traceId: string): JsonRecord {
const createdAt = new Date().toISOString();
const finalText = "fake AgentRun completed after REST backfill without terminal SSE.";
const events = [
...(liveBackfillEarlyTrace(sessionId, threadId, traceId).events as JsonRecord[]),
{ seq: 5, sourceSeq: 21, createdAt, label: "agentrun:assistant:message", type: "assistant_message", status: "completed", message: finalText },
{ seq: 6, sourceSeq: 22, createdAt, label: "item/commandExecution:completed", type: "commandExecution", status: "completed", stdout: "OK\n" },
{ seq: 7, sourceSeq: 23, createdAt, label: "agentrun:backend:turn/completed", type: "backend_status", status: "completed", terminal: true }
];
return { traceId, status: "completed", sessionId, threadId, events, eventCount: events.length, fullTraceLoaded: true, hasMore: false, finalResponse: { text: finalText, status: "completed" } };
}
function sessionSummary(session: SessionRecord): SessionRecord {
const { messages: _messages, hidden: _hidden, ...rest } = session;
return rest as SessionRecord;
@@ -464,6 +507,13 @@ function sessionPayload(sessionId: string): JsonRecord {
}
function turnPayload(traceId: string): JsonRecord {
if (state.scenarioId === "live-rest-backfill-without-terminal-sse" && traceId === state.liveBackfillTraceId) {
const session = state.sessions.find((item) => item.lastTraceId === traceId) ?? state.sessions[0];
const sessionId = session?.sessionId ?? state.selectedSessionId;
const threadId = typeof session?.threadId === "string" ? session.threadId : null;
if (state.liveBackfillReadyAtMs && Date.now() >= state.liveBackfillReadyAtMs) return liveBackfillCompletedTrace(sessionId, threadId, traceId);
return liveBackfillEarlyTrace(sessionId, threadId, traceId);
}
const trace = state.traces[traceId] ?? { traceId, status: "unknown", events: [] };
if (state.scenarioId === "terminal-turn-stale-session-active" && traceId === "trc_completed") return { ...trace, traceId, status: "active", running: true, terminal: false, trace: { status: "completed", eventCount: trace.eventCount ?? 0 } };
const status = String(trace.status ?? "unknown");
@@ -0,0 +1,31 @@
import { expect, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
import { selectors, sessionTab } from "../fixtures/selectors";
test.describe("live REST backfill without terminal SSE", () => {
test.use({ scenarioId: "live-rest-backfill-without-terminal-sse" });
test("submitted turn catches completed AgentRun projection without switching sessions", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/workbench/sessions/ses_completed");
await page.locator(selectors.commandInput).fill("p0 1189 live backfill race: reply OK and run one command");
await expect(page.locator(selectors.commandSend)).toBeEnabled();
await page.locator(selectors.commandSend).click();
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last()).toBeVisible();
await saveScreenshot(page, testInfo, "live-backfill-submitted");
await expect.poll(async () => {
const response = await page.request.get("/v1/workbench/turns/trc_live_backfill");
return response.ok() ? ((await response.json()).turn?.status as string | undefined) : undefined;
}).toBe("completed");
await saveScreenshot(page, testInfo, "live-backfill-canonical-completed");
await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
const backfilledFinal = page.locator(`${selectors.messageCard}[data-role="agent"]`).filter({ hasText: "fake AgentRun completed after REST backfill" });
await expect(backfilledFinal).toHaveAttribute("data-status", "completed");
await expect(page.locator(`${selectors.traceTimeline}[data-status="completed"]`).last()).toBeVisible();
await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "turn");
await saveScreenshot(page, testInfo, "live-backfill-ui-completed");
});
});