Skip to content

Commit 123c92d

Browse files
bloveclaude
andcommitted
test(examples/ag-ui): interrupt-approval e2e over the AG-UI transport
Ports examples/chat's interrupt spec: request_approval pauses the run via on_interrupt, and the app shell's <chat-interrupt-panel> (#649) renders the captured reason + Accept/Edit/Respond/Ignore. The interrupt-approval.json aimock fixture already shipped with the harness — only the spec was missing. 10/10 suite green locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ede8121 commit 123c92d

1 file changed

Lines changed: 41 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
import { test, expect } from '@playwright/test';
3+
import { openDemo } from './test-helpers';
4+
5+
const PROMPT =
6+
'I want to clean up old database backups older than 90 days. Walk me through ' +
7+
'what you would delete, and call request_approval before doing anything ' +
8+
'destructive so I can review your plan.';
9+
10+
// Mirrors examples/chat's interrupt-approval spec over the AG-UI transport:
11+
// the langgraph node calls interrupt({...}), ag-ui-langgraph emits the
12+
// on_interrupt CUSTOM event, the adapter populates agent.interrupt(), and the
13+
// app shell renders <chat-interrupt-panel> (added for parity in #649).
14+
test('interrupt approval: pause renders the interrupt panel with the captured reason', async ({
15+
page,
16+
}) => {
17+
await openDemo(page);
18+
19+
const input = page.getByRole('textbox', { name: /message|prompt/i });
20+
await input.fill(PROMPT);
21+
await page.getByRole('button', { name: /send/i }).click();
22+
23+
// The run stays paused until a human responds — the interrupt panel is the
24+
// durable signal, so don't wait on streaming-complete.
25+
const panel = page.locator('chat-interrupt-panel');
26+
await expect(panel).toBeAttached({ timeout: 45_000 });
27+
28+
await expect(panel).toContainText(/agent paused/i);
29+
30+
// The captured reason mentions the destructive plan — assert it plumbed
31+
// through the on_interrupt payload to the panel body.
32+
await expect.poll(
33+
async () => (await panel.innerText()).toLowerCase(),
34+
{ timeout: 30_000 },
35+
).toMatch(/approval|delete|backup/i);
36+
37+
await expect(panel.getByRole('button', { name: /accept/i })).toBeVisible();
38+
await expect(panel.getByRole('button', { name: /edit|respond/i }).first()).toBeVisible();
39+
await expect(panel.getByRole('button', { name: /ignore/i })).toBeVisible();
40+
await expect(page.locator('chat-message').filter({ has: panel })).toHaveCount(0);
41+
});

0 commit comments

Comments
 (0)