The phone header carries a single speech bubble: Control on Home, the assistant elsewhere. Chips above the input offer the user's most sent messages per tab, then starters; a tap or a double-click on a sent or heard message fills the input without sending.
124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
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")
|
|
})
|