import { expect, test, type Page } from "@playwright/test"; // P1 exit criterion (spec §17): a multi-session conversation with // replayable step traces, end-to-end against the real backend running the // scripted provider (deploy/e2e/scenarios.toml). const OWNER_EMAIL = "owner@acme.test"; const OWNER_PASSWORD = "e2e-password"; async function signIn(page: Page) { await page.goto("/login"); await page.getByLabel("Email address").fill(OWNER_EMAIL); await page.getByLabel("Password").fill(OWNER_PASSWORD); await page.getByRole("button", { name: "Sign in" }).click(); await page.waitForURL((url) => !url.pathname.endsWith("/login"), { timeout: 15000 }); } async function openScout(page: Page) { await page.getByRole("link", { name: /Scout/ }).click(); await expect(page).toHaveURL(/\/claws\/.+\/chat\//); } /** Clicks "New" and waits for the navigation: the previous session may show * the same welcome heading, so the URL change is the reliable signal. */ async function newSession(page: Page) { const before = page.url(); await page.getByRole("button", { name: "New" }).click(); await page.waitForURL((url) => url.toString() !== before); } async function sendMessage(page: Page, text: string) { const box = page.getByLabel("Message Scout"); await box.fill(text); await box.press("Enter"); } test("welcome state appears for a fresh session", async ({ page }) => { await signIn(page); await openScout(page); await newSession(page); await expect( page.getByRole("heading", { name: /Hi, I'm Scout/ }), ).toBeVisible(); await expect( page.getByRole("button", { name: "Create a daily briefing" }), ).toBeVisible(); }); test("a message streams back a scripted reply", async ({ page }) => { await signIn(page); await openScout(page); await sendMessage(page, "hello [[scenario:hello]]"); await expect(page.getByText("hello [[scenario:hello]]")).toBeVisible(); await expect( page.getByText("Hello! I'm Scout, your research analyst."), ).toBeVisible(); }); test("tool runs render a step trace that survives reload", async ({ page }) => { await signIn(page); await openScout(page); await newSession(page); await expect( page.getByRole("heading", { name: /Hi, I'm Scout/ }), ).toBeVisible(); await sendMessage(page, "what time is it? [[scenario:tool-time]]"); await expect(page.getByText(/I checked the current time/)).toBeVisible(); const trace = page.getByRole("button", { name: /1 step/ }); await expect(trace).toBeVisible(); await trace.click(); await expect(page.getByText(/clock\.now/)).toBeVisible(); // Reload: history replays the identical transcript and trace (§13). await page.reload(); await expect(page.getByText(/I checked the current time/)).toBeVisible(); const replayedTrace = page.getByRole("button", { name: /1 step/ }); await replayedTrace.click(); await expect(page.getByText(/clock\.now/)).toBeVisible(); }); test("multiple sessions hold separate transcripts", async ({ page }) => { await signIn(page); await openScout(page); // First session. await newSession(page); await expect( page.getByRole("heading", { name: /Hi, I'm Scout/ }), ).toBeVisible(); await sendMessage(page, "first session message"); await expect(page.getByText("I received: first session message")).toBeVisible(); // Second session. await newSession(page); await expect( page.getByRole("heading", { name: /Hi, I'm Scout/ }), ).toBeVisible(); await sendMessage(page, "second session message"); await expect( page.getByText("I received: second session message"), ).toBeVisible(); await expect(page.getByText("first session message")).not.toBeVisible(); // Switch back via the sessions column (?sessions=1, §6). Rows sort most // recently active first: [current, first-session, original]; the first // non-active row is the session we just left. await page.getByRole("button", { name: "Sessions" }).click(); const column = page.getByRole("complementary", { name: "Sessions" }); await expect(column.getByRole("listitem").first()).toBeVisible(); await column .locator("button:not([aria-current])") .filter({ hasText: "Untitled" }) .first() .click(); await expect(page.getByText("I received: first session message")).toBeVisible(); }); test("the 3-step wizard creates a live claw and lands in its chat", async ({ page, }) => { await signIn(page); await page.getByRole("link", { name: "New claw" }).click(); // Step 1: identity (accent swatch + name + title). await page.getByRole("radio", { name: "Accent #65a8f9" }).click(); await page.getByLabel("Name", { exact: true }).fill("Drafter"); await page.getByLabel("Job title").fill("Writer"); await page.getByLabel("Job description").fill("You draft crisp documents."); await page.getByRole("button", { name: "Continue →" }).click(); // Step 2: access. await expect(page).toHaveURL(/step=access/); await page.getByRole("radio", { name: /Any Claw on the team/ }).click(); await page.getByRole("button", { name: "Continue →" }).click(); // Step 3: slack (optional) → explicit confirm before the agent goes live. await expect(page).toHaveURL(/step=slack/); await page.getByRole("button", { name: "Review & create" }).click(); await expect(page.getByText("Create Drafter?")).toBeVisible(); await page.getByRole("button", { name: "Create claw" }).click(); await expect(page).toHaveURL(/\/claws\/.+\/chat\//); await expect( page.getByRole("heading", { name: /Hi, I'm Drafter/ }), ).toBeVisible(); const box = page.getByLabel("Message Drafter"); await box.fill("ping"); await box.press("Enter"); await expect(page.getByText("I received: ping")).toBeVisible(); });