test(timeline): cover running state across refresh

Protect the hydration boundary where directory state must restore consistent tab, composer, and footer activity. Expose the running indicator semantically so the regression checks user-visible state instead of spinner implementation details.
This commit is contained in:
Mohamed Boudra
2026-07-31 13:01:50 +02:00
parent c2b92e2294
commit a0785d7a42
4 changed files with 61 additions and 2 deletions
+26 -1
View File
@@ -3,6 +3,7 @@ import {
awaitAssistantMessage,
expectAgentIdle,
expectInlineWorkingIndicator,
expectRunningAgentChrome,
expectTurnCopyButton,
expectScrollFollowsNewContent,
} from "./helpers/agent-stream";
@@ -18,11 +19,35 @@ import { delayCreatedAgentInitialTailResponse } from "./helpers/agent-timeline-g
import { selectModel } from "./helpers/app";
import { clickNewChat } from "./helpers/launcher";
import { expectComposerVisible, startRunningMockAgent } from "./helpers/composer";
import { openAgentRoute, seedMockAgentWorkspace } from "./helpers/mock-agent";
import {
openAgentRoute,
seedMockAgentWorkspace,
seedRunningMockAgentWorkspace,
} from "./helpers/mock-agent";
const SCROLL_AWAY_MIN_SCROLLABLE_DISTANCE = 360;
test.describe("Agent stream UI", () => {
test("keeps running agent chrome after page refresh", async ({ page }) => {
const title = "Running agent refresh";
const agent = await seedRunningMockAgentWorkspace({
repoPrefix: "stream-running-refresh-",
title,
model: "five-minute-stream",
initialPrompt: "Stay running while the page refreshes.",
});
try {
await openAgentRoute(page, agent);
await expectRunningAgentChrome(page, title);
await page.reload();
await expectRunningAgentChrome(page, title);
} finally {
await agent.cleanup();
}
});
test("auto-scroll sticks to bottom across token bursts", async ({ page }) => {
test.setTimeout(120_000);
const agent = await startRunningMockAgent(page, {
+13
View File
@@ -22,6 +22,19 @@ export async function expectInlineWorkingIndicator(page: Page): Promise<void> {
await expect(page.getByTestId("turn-working-indicator")).toBeVisible({ timeout: 30_000 });
}
export async function expectRunningAgentChrome(page: Page, title: string): Promise<void> {
const tab = page.getByRole("button", { name: title, exact: true });
await expect(tab).toBeVisible({ timeout: 30_000 });
await expect(tab.getByRole("progressbar", { name: "Agent running" })).toBeVisible({
timeout: 30_000,
});
await expect(page.getByRole("button", { name: /stop agent|canceling agent/i })).toBeVisible({
timeout: 30_000,
});
await expectInlineWorkingIndicator(page);
}
export async function expectTurnCopyButton(page: Page): Promise<void> {
await expect(page.getByRole("button", { name: "Copy turn" }).first()).toBeVisible({
timeout: 30_000,
+17
View File
@@ -53,6 +53,23 @@ export async function seedMockAgentWorkspace(
}
}
export async function seedRunningMockAgentWorkspace(
options: MockAgentOptions,
): Promise<MockAgentWorkspace> {
const agent = await seedMockAgentWorkspace(options);
try {
await agent.client.waitForAgentUpsert(
agent.agentId,
(snapshot) => snapshot.status === "running",
15_000,
);
return agent;
} catch (error) {
await agent.cleanup();
throw error;
}
}
export function buildAgentRoute(workspaceId: string, agentId: string): string {
return buildHostAgentDetailRoute(getServerId(), agentId, workspaceId);
}
@@ -162,7 +162,11 @@ export function WorkspaceTabIcon({
if (shouldShowLoader) {
return (
<View style={agentIconWrapperStyle}>
<View
style={agentIconWrapperStyle}
accessibilityRole="progressbar"
accessibilityLabel="Agent running"
>
<SyncedLoader size={size - 1} color={styles.syncedLoader.color} />
</View>
);