Files
copilotkit__copilotkit/showcase/integrations/google-adk/tests/e2e/gen-ui-agent.spec.ts
Alem Tuzlak 02e48162c4 test(showcase/google-adk): sync e2e specs to langgraph-python north-star
Ports 16 diverged Playwright e2e specs verbatim from langgraph-python
and adds 3 previously-missing specs (chat-customization-css,
prebuilt-sidebar, reasoning-custom). All 19 files are byte-identical
to LGP, mirroring the same approach the recent ADK parity push used
for the demo pages.

Why this matters even though D5 is the gold standard: the per-package
Playwright suites (`pnpm test:e2e`) are the local dev validation loop.
Without parity here, a contributor editing google-adk's CopilotChat
surface has no local check that matches what langgraph-python ships,
and tiny divergences between the two surfaces (missing testids, stale
selectors, wrong assertion shapes) silently accumulate until they
surface as D5 regressions in CI.
2026-05-14 20:54:21 +02:00

115 lines
4.4 KiB
TypeScript

import { test, expect } from "@playwright/test";
test.describe("Agentic Generative UI", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/demos/gen-ui-agent");
});
test("page loads with chat input", async ({ page }) => {
await expect(page.getByPlaceholder("Type a message")).toBeVisible();
});
test("sends message and gets assistant response", async ({ page }) => {
const input = page.getByPlaceholder("Type a message");
await input.fill("Hello");
await input.press("Enter");
await expect(
page.locator('[data-testid="copilot-assistant-message"]').first(),
).toBeVisible({
timeout: 30000,
});
});
test("message list container exists", async ({ page }) => {
await expect(
page.locator('[data-testid="copilot-message-list"]'),
).toBeVisible({ timeout: 10000 });
});
// Regression: every set_steps tool call used to push a brand-new card into
// the chat (one card per state-changing message), so a 7-call run produced
// 7+ stacked duplicate cards. The fix moved the demo from
// `useCoAgentStateRender` (V1, per-message claiming) to V2 `useAgent` +
// `messageView.children`, which renders a single live-updating card. This
// test pins that contract — one card, regardless of how many state updates
// arrive during the run.
test("renders a single agent-state-card that updates in place", async ({
page,
}) => {
const input = page.getByPlaceholder("Type a message");
await input.fill("Plan a product launch for a new mobile app.");
await input.press("Enter");
const card = page.locator('[data-testid="agent-state-card"]');
await expect(card).toBeVisible({ timeout: 60000 });
// Wait for at least one step to be published, then assert there is still
// only one card (not one per state update).
await expect(
page.locator('[data-testid="agent-step"]').first(),
).toBeVisible({ timeout: 60000 });
await expect(card).toHaveCount(1);
// Wait until the agent finishes the run, then re-assert single card.
// `agent.isRunning` flips to false → the card's spinner becomes a check.
await expect(card.locator(".animate-spin")).toHaveCount(0, {
timeout: 120000,
});
await expect(card).toHaveCount(1);
});
test("eventually marks every step as completed", async ({ page }) => {
const input = page.getByPlaceholder("Type a message");
await input.fill("Plan a product launch for a new mobile app.");
await input.press("Enter");
// Wait for the run to settle (no in-progress markers anywhere on the page).
await expect(
page.locator('[data-testid="agent-step"][data-status="in_progress"]'),
).toHaveCount(0, { timeout: 120000 });
const steps = page.locator('[data-testid="agent-step"]');
const total = await steps.count();
expect(total).toBeGreaterThan(0);
// Every step must end in `completed` — guards against the "step 3 stuck"
// regression where the agent terminated without flipping the last step.
const completed = page.locator(
'[data-testid="agent-step"][data-status="completed"]',
);
await expect(completed).toHaveCount(total);
});
// Regression: the aimock fixture used to emit a single set_steps tool call
// with all three steps already `completed`, so the card mounted in its
// final state with no sequential animation. The pill's whole point is the
// pending → in_progress → completed progression spelled out in the
// backend's SYSTEM_PROMPT, which requires a 7-call chain of set_steps
// emissions threaded via toolCallId. This test pins that we observe at
// least one step in `pending` state during the run — proof that the chain
// is starting from the all-pending leg, not short-circuiting to all-done.
test("steps animate through pending before completing (no fixture short-circuit)", async ({
page,
}) => {
await page.getByRole("button", { name: /Plan a product launch/i }).click();
await expect(page.locator('[data-testid="agent-state-card"]')).toBeVisible({
timeout: 60000,
});
// At least one step must be observed in `pending` state. With the
// short-circuit bug the steps went straight to `completed` and this
// count would always be 0.
await expect
.poll(
() =>
page
.locator('[data-testid="agent-step"][data-status="pending"]')
.count(),
{ intervals: [250], timeout: 30000 },
)
.toBeGreaterThan(0);
});
});