Files
openworker/surfaces/gui/e2e/board.spec.ts
T

162 lines
7.8 KiB
TypeScript

// Agent teams: the board in the session UI — the rail section (grouped by state,
// blocked on top, active work only) and the expanded overlay: a quiet list over
// the store's RAW states (In progress / Awaiting review / Queued — no computed
// interpretation layer, no row buttons) plus a detail pane with the item's merged
// event timeline. Verdicts flow through the pane: Mark done / Request changes….
// The fake agent files items on "plan the work"; transitions round-trip through
// the mocked /board endpoints as the user.
import { expect } from "@playwright/test";
import { test } from "./fixtures";
async function planTheWork(page: import("@playwright/test").Page) {
await page.goto("/");
await page.getByPlaceholder(/Ask the coworker/).fill("plan the work");
await page.getByRole("button", { name: "Send" }).click();
await expect(page.getByText(/filed 5 work items/)).toBeVisible();
}
// Seventeenth pass: every drawer section starts collapsed — expanding the Board
// section is now an explicit step wherever a test reads the rail's rows.
async function openBoardSection(page: import("@playwright/test").Page) {
await page.getByTestId("rail-toggle-board").click();
await expect(page.getByTestId("board-rail")).toBeVisible();
}
test("plain sessions carry zero board chrome", async ({ page }) => {
await page.goto("/");
await page.getByPlaceholder(/Ask the coworker/).fill("hello");
await page.getByRole("button", { name: "Send" }).click();
await expect(page.getByText("Echo: hello")).toBeVisible();
await expect(page.getByTestId("board-rail")).toHaveCount(0);
await expect(page.getByTestId("rail-toggle-board")).toHaveCount(0);
});
test("filed items appear grouped in the rail, blocked on top, queued items listed", async ({
page,
}) => {
await planTheWork(page);
// collapsed by default: the header chip is the maximum signal
await expect(page.getByTestId("board-rail")).toHaveCount(0);
await expect(page.getByTestId("rail-toggle-board")).toContainText("1 blocked · 1 review");
await openBoardSection(page);
const rail = page.getByTestId("board-rail");
await expect(rail).toBeVisible();
const groups = rail.locator(".board-group");
await expect(groups.first()).toHaveText("Blocked");
await expect(rail).toContainText("Queued");
await expect(rail.getByText("Secrets — git history, both repos")).toBeVisible();
});
test("the overlay lists raw-state sections; verdicts flow through the detail pane", async ({
page,
}) => {
await planTheWork(page);
await page.getByTestId("board-expand").click();
const overlay = page.getByTestId("board-overlay");
await expect(overlay).toBeVisible();
// the owner's sections, nothing computed — and no buttons in the rows
await expect(overlay).toContainText("In progress");
await expect(overlay).toContainText("Awaiting review");
await expect(overlay).toContainText("Queued");
await expect(overlay.getByRole("button", { name: "Mark done" })).toHaveCount(0);
// a blocked row carries the blocker as a plain fact under In progress
await expect(page.getByTestId("board-item-4")).toContainText(
"cloud-posture · blocked: need tfvars for staging",
);
// review verdict from the pane
await page.getByTestId("board-item-5").click();
const detail = page.getByTestId("board-detail");
await detail.getByRole("button", { name: "Mark done" }).click();
await expect(page.getByTestId("overlay-finished-toggle")).toHaveText("1 finished · show");
// queued items are removed from their pane (maps to canceled underneath)
await page.getByTestId("board-item-1").click();
await detail.getByRole("button", { name: "Remove" }).click();
await expect(page.getByTestId("overlay-finished-toggle")).toHaveText("2 finished · show");
await page.keyboard.press("Escape");
await expect(page.getByTestId("board-overlay")).toHaveCount(0);
});
test("finished items leave the rail; a quiet toggle reveals them", async ({ page }) => {
await planTheWork(page);
await openBoardSection(page);
const rail = page.getByTestId("board-rail");
await expect(rail.getByText("Report rollup")).toBeVisible(); // review = active
await page.getByTestId("board-expand").click();
await page.getByTestId("board-item-5").click();
await page.getByTestId("board-detail").getByRole("button", { name: "Mark done" }).click();
await page.keyboard.press("Escape");
// done vanishes from the rail — a fresh session on an old board starts calm
await expect(rail.getByText("Report rollup")).toHaveCount(0);
const toggle = page.getByTestId("board-finished-toggle");
await expect(toggle).toHaveText("1 finished · show");
await toggle.click();
await expect(rail.getByText("Report rollup")).toBeVisible();
await toggle.click();
await expect(rail.getByText("Report rollup")).toHaveCount(0);
});
test("item detail: timeline with attachment, worker link, request changes", async ({
page,
}) => {
await planTheWork(page);
await openBoardSection(page);
// a rail row deep-opens the overlay on that item's detail
await page.getByTestId("board-rail").getByText("Report rollup").click();
const detail = page.getByTestId("board-detail");
await expect(detail).toBeVisible();
await expect(detail).toContainText("#5");
await expect(detail).toContainText("Report rollup");
await expect(detail).toContainText("In review");
await expect(detail).toContainText("Done when");
// the merged timeline tells the item's whole story
await expect(detail).toContainText("security started");
await expect(detail).toContainText("balances reconcile against the seeded rows");
await expect(detail).toContainText("moved to in review");
// the attachment image actually loads (real bytes from the fixture)
await expect(detail.getByTestId("board-attachment")).toBeVisible();
// the assignee links to that coworker's session
await expect(detail.getByTestId("board-open-worker")).toHaveText("security ↗");
// Request changes… discloses a comment box; sending returns the item to work
await detail.getByRole("button", { name: "Request changes…" }).click();
await detail.getByPlaceholder("What needs to change?").fill("totals drift on Tom");
await detail.getByRole("button", { name: "Request changes", exact: true }).click();
await expect(detail).toContainText("In progress");
// switching rows switches the pane
await page.getByTestId("board-item-3").click();
await expect(detail).toContainText("Dependency audit — lockfiles");
});
test("Add a note is a pure append — it lands in the timeline, state untouched", async ({
page,
}) => {
await planTheWork(page);
await openBoardSection(page);
await page.getByTestId("board-rail").getByText("Report rollup").click();
const detail = page.getByTestId("board-detail");
await expect(detail).toContainText("In review");
await detail.getByTestId("board-note-input").fill("prefer the v2 endpoint for totals");
await detail.getByTestId("board-note-input").press("Enter");
// the note appears as a timeline event…
await expect(detail).toContainText("user commented");
await expect(detail).toContainText("prefer the v2 endpoint for totals");
// …and the state did NOT change (notes never transition)
await expect(detail).toContainText("In review");
await expect(detail.getByRole("button", { name: "Mark done" })).toBeVisible();
});
test("journal section lists cases once a board exists", async ({ page }) => {
await planTheWork(page);
// Journal is not a primary section — it sits behind the quiet More row.
await expect(page.getByTestId("rail-toggle-journal")).toHaveCount(0);
await page.getByTestId("rail-toggle-journal").click();
const journal = page.getByTestId("journal-list");
await expect(journal).toBeVisible();
await expect(journal).toContainText("findings");
await expect(journal).toContainText("12 entries");
// Access folds with it — the drawer keeps three primary sections.
await expect(page.getByTestId("access-section")).toBeVisible();
});