From 3905b2e864256af65dd9f2d12cdaa16aa4db7acd Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 5 May 2026 13:13:10 +0800 Subject: [PATCH] =?UTF-8?q?test(app/e2e):=20stream=20auto-scroll=20and=20w?= =?UTF-8?q?orking-indicator=E2=86=92copy-button=20(Cluster=20G5)=20(#743)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(app/e2e): stream auto-scroll and working-indicator→copy-button (Cluster G5) Wire in the unused agent-bottom-anchor helpers and add two new agent stream UI specs: auto-scroll stays pinned to the bottom across token bursts, and the inline working-indicator transitions to a copy-button when the stream ends. Both tests use the mock provider so there is no real LLM dependency in CI. Adds three helpers to helpers/agent-stream.ts: - expectInlineWorkingIndicator - expectTurnCopyButton - expectScrolledToBottom (wraps agent-bottom-anchor) * fixup: address review blockers and nits - Replace expectScrolledToBottom passthrough with expectScrollFollowsNewContent that encapsulates readScrollMetrics → waitForContentGrowth → expectNearBottom - Remove direct agent-bottom-anchor imports from spec body (DSL leak) - Add awaitAssistantMessage before expectInlineWorkingIndicator to anchor on real content before asserting the working indicator - Add comment to expectInlineWorkingIndicator explaining why testId is used (animated spinner View has no ARIA role) --- packages/app/e2e/agent-stream-ui.spec.ts | 45 ++++++++++++++++++++++++ packages/app/e2e/helpers/agent-stream.ts | 18 ++++++++++ 2 files changed, 63 insertions(+) create mode 100644 packages/app/e2e/agent-stream-ui.spec.ts diff --git a/packages/app/e2e/agent-stream-ui.spec.ts b/packages/app/e2e/agent-stream-ui.spec.ts new file mode 100644 index 000000000..8fea036fd --- /dev/null +++ b/packages/app/e2e/agent-stream-ui.spec.ts @@ -0,0 +1,45 @@ +import { test } from "./fixtures"; +import { + awaitAssistantMessage, + expectAgentIdle, + expectInlineWorkingIndicator, + expectTurnCopyButton, + expectScrollFollowsNewContent, +} from "./helpers/agent-stream"; +import { startRunningMockAgent } from "./helpers/composer"; + +test.describe("Agent stream UI", () => { + test("auto-scroll sticks to bottom across token bursts", async ({ page }) => { + test.setTimeout(120_000); + const { client, repo } = await startRunningMockAgent(page, { + prefix: "stream-scroll-", + model: "one-minute-stream", + prompt: "Stream for auto-scroll test.", + }); + try { + await awaitAssistantMessage(page); + await expectScrollFollowsNewContent(page); + } finally { + await client.close(); + await repo.cleanup(); + } + }); + + test("working-indicator transitions to copy-button when stream ends", async ({ page }) => { + test.setTimeout(60_000); + const { client, repo } = await startRunningMockAgent(page, { + prefix: "stream-indicator-", + model: "ten-second-stream", + prompt: "Stream briefly for indicator transition test.", + }); + try { + await awaitAssistantMessage(page); + await expectInlineWorkingIndicator(page); + await expectAgentIdle(page, 30_000); + await expectTurnCopyButton(page); + } finally { + await client.close(); + await repo.cleanup(); + } + }); +}); diff --git a/packages/app/e2e/helpers/agent-stream.ts b/packages/app/e2e/helpers/agent-stream.ts index b1d3a2adf..a6086513f 100644 --- a/packages/app/e2e/helpers/agent-stream.ts +++ b/packages/app/e2e/helpers/agent-stream.ts @@ -1,4 +1,5 @@ import { expect, type Page } from "@playwright/test"; +import { readScrollMetrics, waitForContentGrowth, expectNearBottom } from "./agent-bottom-anchor"; export async function awaitAssistantMessage(page: Page, hasText?: string | RegExp): Promise { const messages = page.getByTestId("assistant-message"); @@ -15,3 +16,20 @@ export async function awaitToolCall(page: Page, toolName: string | RegExp): Prom export async function expectAgentIdle(page: Page, timeout = 30_000): Promise { await expect(page.getByRole("button", { name: /stop|cancel/i })).toHaveCount(0, { timeout }); } + +// The working indicator is an animated spinner View — no semantic ARIA role, testId is correct. +export async function expectInlineWorkingIndicator(page: Page): Promise { + await expect(page.getByTestId("turn-working-indicator")).toBeVisible({ timeout: 30_000 }); +} + +export async function expectTurnCopyButton(page: Page): Promise { + await expect(page.getByRole("button", { name: "Copy turn" }).first()).toBeVisible({ + timeout: 30_000, + }); +} + +export async function expectScrollFollowsNewContent(page: Page): Promise { + const { contentHeight } = await readScrollMetrics(page); + await waitForContentGrowth(page, contentHeight); + await expectNearBottom(page); +}