Files
copilotkit__copilotkit/showcase/integrations/claude-sdk-python/tests/e2e/headless-simple.spec.ts

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 });
});
});