From 0d803941802f88f488fa8c795fe4d2cf1cf6a12d Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 5 May 2026 14:03:06 +0800 Subject: [PATCH] test(app/e2e): sessions-screen empty state (Cluster G7) (#742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(app/e2e): cover sessions-screen empty state (Cluster G7) Adds one E2E test — opens Sessions on a fresh workspace with no agents and asserts the "No sessions yet" placeholder renders. Uses `withWorkspace` fixture (no agent seeding) so the empty branch runs for the first time. Also exports `expectSessionsEmptyState` helper from archive-tab helpers for reuse in future session-related specs. * fix(app/e2e): run sessions-empty test before archive-tab agents are created The sessions screen shows global agent history for the daemon. Running the empty-state test last meant the reconciliation tests had already created 6 agents, so "No sessions yet" never rendered. Moving the describe block first ensures it runs on a clean daemon (workers:1, fullyParallel:false, archive-tab.spec.ts is first alphabetically). * refactor(app/e2e): guard sessions-empty ordering + dedup selector constant Addresses reviewer feedback on the ordering fragility: - Add NOTE comment above Sessions screen empty state describe explaining why it must remain first in the file (daemon history is global; the reconciliation tests below call createIdleAgent which would pollute it). - Add a fast-fail guard in expectSessionsEmptyState: asserts 0 agent rows with a 5s timeout so a future maintainer sees an immediately actionable failure message rather than a mysterious "No sessions yet" timeout. - Extract AGENT_ROW_SELECTOR constant to eliminate the duplicated [data-testid^="agent-row-"] string shared by getSessionRowByTitle and the new guard. * fix(app/e2e): move sessions-empty to 00-prefixed file to survive new specs agent-stream-ui.spec.ts (merged in #743) sorts before archive-tab.spec.ts and creates agents, breaking the empty-state test. Any future a*-*.spec.ts has the same risk. Fix: move the test to 00-sessions-empty.spec.ts — digit prefix sorts before all alpha-named specs, making the ordering constraint explicit at the filesystem level. Also adds a beforeAll daemon probe that fails fast with a clear message if any pre-existing agents are found, covering both ordering violations and stale daemon state from a previous run. Supporting changes: - Add fetchAgentHistory to ArchiveTabDaemonClient interface (typed as Array<{ id: string }> — enough for the count check) - Remove the test and NOTE comment from archive-tab.spec.ts - Update expectSessionsEmptyState guard comment to reference the new file --- packages/app/e2e/00-sessions-empty.spec.ts | 37 ++++++++++++++++++++++ packages/app/e2e/helpers/archive-tab.ts | 13 +++++++- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/app/e2e/00-sessions-empty.spec.ts diff --git a/packages/app/e2e/00-sessions-empty.spec.ts b/packages/app/e2e/00-sessions-empty.spec.ts new file mode 100644 index 000000000..d08e6d55b --- /dev/null +++ b/packages/app/e2e/00-sessions-empty.spec.ts @@ -0,0 +1,37 @@ +// "00-" prefix is intentional: this file must sort before every other spec. +// Sessions history is daemon-global — any agent created by a prior spec hides the empty state. +// If the beforeAll probe below fails, a spec sorted before this file is creating agents. +import { test } from "./fixtures"; +import { + connectArchiveTabDaemonClient, + expectSessionsEmptyState, + openSessions, +} from "./helpers/archive-tab"; + +test.describe("Sessions screen empty state", () => { + test.beforeAll(async () => { + const client = await connectArchiveTabDaemonClient(); + try { + const history = await client.fetchAgentHistory({ page: { limit: 1 } }); + if (history.entries.length > 0) { + throw new Error( + `Sessions empty-state precondition failed: daemon already has ${history.entries.length} agent(s). ` + + `Either a spec that sorts before 00-sessions-empty.spec.ts created agents, ` + + `or the daemon has stale history from a previous run.`, + ); + } + } finally { + await client.close().catch(() => undefined); + } + }); + + test("shows empty placeholder when there is no session history", async ({ + page, + withWorkspace, + }) => { + const workspace = await withWorkspace({ prefix: "sessions-empty-" }); + await workspace.navigateTo(); + await openSessions(page); + await expectSessionsEmptyState(page); + }); +}); diff --git a/packages/app/e2e/helpers/archive-tab.ts b/packages/app/e2e/helpers/archive-tab.ts index b3f4de8bc..3c901b2d1 100644 --- a/packages/app/e2e/helpers/archive-tab.ts +++ b/packages/app/e2e/helpers/archive-tab.ts @@ -36,6 +36,9 @@ interface ArchiveTabDaemonClient { predicate: (snapshot: { status: string }) => boolean, timeout?: number, ): Promise<{ status: string }>; + fetchAgentHistory(options?: { + page?: { limit: number }; + }): Promise<{ entries: Array<{ id: string }> }>; } function getDaemonPort(): string { @@ -265,8 +268,10 @@ export async function openSessions(page: Page): Promise { }); } +const AGENT_ROW_SELECTOR = '[data-testid^="agent-row-"]'; + function getSessionRowByTitle(page: Page, title: string) { - return page.locator('[data-testid^="agent-row-"]').filter({ hasText: title }).first(); + return page.locator(AGENT_ROW_SELECTOR).filter({ hasText: title }).first(); } export async function expectSessionRowVisible(page: Page, title: string): Promise { @@ -283,6 +288,12 @@ export async function clickSessionRow(page: Page, title: string): Promise await row.click(); } +export async function expectSessionsEmptyState(page: Page): Promise { + // Guard: if session rows appear, a prior spec polluted the shared daemon — see 00-sessions-empty.spec.ts. + await expect(page.locator(AGENT_ROW_SELECTOR)).toHaveCount(0, { timeout: 5_000 }); + await expect(page.getByText("No sessions yet")).toBeVisible({ timeout: 30_000 }); +} + export async function archiveAgentFromSessions( page: Page, input: { agentId: string; title: string },