import { expect, type Page, test } from "@playwright/test" /** * The chat on a phone: its one button in the header, which tab it opens, and * how a message gets back into the input (a double-click or double-tap, a * quick-prompt chip). * * A provider and a wren node are instance state, not something a suite should * set up, so the three endpoints the panel reads are answered here. */ test.use({ viewport: { width: 393, height: 851 }, hasTouch: true }) const SAID = "which flows are running" test.beforeEach(async ({ page }) => { await page.route("**/api/v1/ai/status", (route) => route.fulfill({ json: { available: true, model: "test" } }), ) await page.route("**/api/v1/ai/control", (route) => route.fulfill({ json: [ { id: "home.control", flow: "home", title: "Home ยท Control", prompt_msg: "home.command", watch: [{ msg: "home.command", kind: "text", node: "" }], }, ], }), ) await page.route("**/api/v1/ai/chat", (route) => route.request().method() === "GET" ? route.fulfill({ json: [ { role: "user", content: SAID }, { role: "assistant", content: "Two." }, ], }) : route.fallback(), ) }) const pressed = (page: Page, tab: string) => page.getByTestId("chat-tabs").getByRole("button", { name: tab }) test("one chat button opens Control on Home", async ({ page }) => { await page.goto("/") await expect(page.getByTestId("chat-trigger")).toHaveCount(1) // Neither the old assistant button nor Home's wider-screen one is on screen. await expect(page.getByRole("button", { name: "Assistant" })).toHaveCount(0) await expect(page.getByRole("button", { name: "Chat" })).toHaveCount(1) await page.getByTestId("chat-trigger").click() await expect(pressed(page, "Control")).toHaveAttribute("aria-pressed", "true") }) test("the same button opens the assistant elsewhere", async ({ page }) => { await page.goto("/flows") await page.getByTestId("chat-trigger").click() await expect(pressed(page, "Build")).toHaveAttribute("aria-pressed", "true") }) test("a message and a chip go back into the input", async ({ page }) => { await page.goto("/flows") await page.getByTestId("chat-trigger").click() const input = page.getByTestId("chat-input") const said = page.getByText(SAID) await said.dblclick() await expect(input).toHaveValue(SAID) await expect(input).toBeFocused() // Something typed leaves no room for the row. await expect(page.getByTestId("chat-prompts")).toHaveCount(0) await input.fill("") const box = await said.boundingBox() if (!box) throw new Error("the message is not on screen") const x = box.x + box.width / 2 const y = box.y + box.height / 2 await page.touchscreen.tap(x, y) await page.touchscreen.tap(x, y) await expect(input).toHaveValue(SAID) await input.fill("") const chip = page.getByTestId("chat-prompt").first() const text = (await chip.textContent()) ?? "" expect(text).not.toBe("") await chip.click() await expect(input).toHaveValue(text) // A chip fills the input; it does not send. await expect(page.getByText(text)).toHaveCount(0) }) test("what was sent comes back as a chip, the most used first", async ({ page, }) => { await page.route("**/api/v1/ai/chat", (route) => route.request().method() === "POST" ? route.fulfill({ contentType: "text/event-stream", body: 'data: {"type":"done"}\n\n', }) : route.fallback(), ) await page.goto("/flows") await page.getByTestId("chat-trigger").click() const input = page.getByTestId("chat-input") for (const text of ["once", "twice", "twice"]) { await input.fill(text) // Sending is refused while the last turn is still running. await expect(page.getByRole("button", { name: "Send" })).toBeEnabled() await input.press("Enter") await expect(input).toHaveValue("") } await page.reload() await page.getByTestId("chat-trigger").click() const chips = page.getByTestId("chat-prompt") await expect(chips.nth(0)).toHaveText("twice") await expect(chips.nth(1)).toHaveText("once") })