mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
|
|
test.describe("Headless Chat (Simple)", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto("/demos/headless-simple");
|
|
});
|
|
|
|
test("renders custom composer chrome instead of CopilotChat", async ({
|
|
page,
|
|
}) => {
|
|
// Heading is a hand-rolled <h1> — not a chat primitive. If the demo
|
|
// ever regressed to rendering <CopilotChat /> it would not exist here.
|
|
await expect(
|
|
page.getByRole("heading", { name: "Headless Chat (Simple)" }),
|
|
).toBeVisible();
|
|
|
|
// Empty-state helper text is rendered inside the message panel before
|
|
// any messages land.
|
|
await expect(page.getByText("No messages yet. Say hi!")).toBeVisible();
|
|
|
|
// Verbatim placeholder on the custom composer textarea (not the default
|
|
// CopilotKit "Type a message" placeholder) — this is the clearest
|
|
// structural signal the composer is hand-rolled.
|
|
await expect(
|
|
page.getByPlaceholder(
|
|
"Type a message. Ask me to 'show a card about cats'.",
|
|
),
|
|
).toBeVisible();
|
|
|
|
// The custom Send button is a plain <button>. Disabled on empty input.
|
|
const send = page.getByRole("button", { name: "Send", exact: true });
|
|
await expect(send).toBeVisible();
|
|
await expect(send).toBeDisabled();
|
|
|
|
// No default CopilotChat testids should be present — this cell is
|
|
// truly headless.
|
|
await expect(
|
|
page.locator('[data-testid="copilot-chat-input"]'),
|
|
).toHaveCount(0);
|
|
await expect(
|
|
page.locator('[data-testid="copilot-send-button"]'),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("Send button enables once textarea has content", async ({ page }) => {
|
|
const textarea = page.getByPlaceholder(
|
|
"Type a message. Ask me to 'show a card about cats'.",
|
|
);
|
|
const send = page.getByRole("button", { name: "Send", exact: true });
|
|
|
|
await expect(send).toBeDisabled();
|
|
await textarea.fill("Hello");
|
|
await expect(send).toBeEnabled();
|
|
});
|
|
|
|
test("sends a message and renders the user bubble", async ({ page }) => {
|
|
// The round-trip user bubble is the most reliable structural signal —
|
|
// it's rendered synchronously from agent.messages as soon as the send
|
|
// handler calls `agent.addMessage({ role: "user", ... })`. The assistant
|
|
// response reliability depends on the upstream LangGraph deployment
|
|
// plumbing through to aimock, so we keep this test scoped to the
|
|
// frontend composer's verified behavior.
|
|
const textarea = page.getByPlaceholder(
|
|
"Type a message. Ask me to 'show a card about cats'.",
|
|
);
|
|
await textarea.fill("hello");
|
|
await page.getByRole("button", { name: "Send", exact: true }).click();
|
|
|
|
// User bubble: self-end + bg-blue-600 are structural utility classes
|
|
// applied only to the user bubble in this cell.
|
|
const userBubble = page.locator("div.self-end.bg-blue-600").first();
|
|
await expect(userBubble).toBeVisible({ timeout: 10000 });
|
|
await expect(userBubble).toHaveText("hello");
|
|
|
|
// Empty-state text is gone after the first send.
|
|
await expect(page.getByText("No messages yet. Say hi!")).toHaveCount(0);
|
|
|
|
// Textarea clears after send.
|
|
await expect(textarea).toHaveValue("");
|
|
});
|
|
|
|
test("thinking indicator appears while agent is running", async ({
|
|
page,
|
|
}) => {
|
|
// The "Agent is thinking..." helper is rendered whenever `agent.isRunning`
|
|
// is true. Sending any message flips that bit to true for the duration of
|
|
// the round-trip, so the indicator should surface.
|
|
const textarea = page.getByPlaceholder(
|
|
"Type a message. Ask me to 'show a card about cats'.",
|
|
);
|
|
await textarea.fill("hello");
|
|
await page.getByRole("button", { name: "Send", exact: true }).click();
|
|
|
|
await expect(page.getByText("Agent is thinking...")).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
});
|
|
|
|
test("clicks the Largest continent suggestion and renders the canonical answer", async ({
|
|
page,
|
|
}) => {
|
|
// Suggestion chips are hand-rolled buttons inside a scoped container so the
|
|
// selector cannot collide with the Send button or any future composer
|
|
// controls. Clicking dispatches the configured message through send()
|
|
// imperatively (no setInput round-trip) so the user bubble appears with
|
|
// the verbatim configured prompt.
|
|
const chips = page.locator('[data-testid="headless-suggestions"]');
|
|
await expect(chips).toBeVisible();
|
|
|
|
await chips.getByRole("button", { name: "Largest continent" }).click();
|
|
|
|
// Verbatim user message rendered inside the message panel.
|
|
await expect(page.getByText("What is the largest continent?")).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
|
|
// aimock's feature-parity fixture for "What is the largest continent?"
|
|
// returns a deterministic Asia-first answer.
|
|
await expect(page.getByText(/Asia/)).toBeVisible({ timeout: 30000 });
|
|
});
|
|
});
|