Skip to content

Commit beb406d

Browse files
committed
feat: add TUI integration tests
27 tests across 5 suites verifying the TUI harness against the real AgentCore CLI: harness self-tests, navigation flows, create wizard, add-resource flows, and deploy screen rendering. Tests use describe.skipIf(!isAvailable) to gracefully skip when node-pty is not installed. createMinimalProjectDir provides fast (~10ms) project directory setup without npm install.
1 parent df58b41 commit beb406d

6 files changed

Lines changed: 953 additions & 0 deletions

File tree

integ-tests/tui/add-flow.test.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* Integration tests for TUI add-resource flow.
3+
*
4+
* Verifies navigation into the Add Resource screen, drilling into the
5+
* Add Agent wizard, and backing out via Escape at each level.
6+
*
7+
* These tests launch the real CLI entry point against a minimal project
8+
* directory (no npm install required) and interact with the Ink-based TUI
9+
* through the headless PTY harness.
10+
*/
11+
import { TuiSession, isAvailable } from '../../src/tui-harness/index.js';
12+
import { createMinimalProjectDir } from './helpers.js';
13+
import { join } from 'path';
14+
import { afterEach, describe, expect, it } from 'vitest';
15+
16+
const CLI_ENTRY = join(process.cwd(), 'dist/cli/index.mjs');
17+
18+
describe.skipIf(!isAvailable)('TUI add-resource flow', () => {
19+
let session: TuiSession | undefined;
20+
let cleanup: (() => Promise<void>) | undefined;
21+
22+
afterEach(async () => {
23+
if (session?.alive) await session.close();
24+
session = undefined;
25+
await cleanup?.();
26+
cleanup = undefined;
27+
});
28+
29+
// ---------------------------------------------------------------------------
30+
// (a) Navigate to Add Resource screen
31+
// ---------------------------------------------------------------------------
32+
it('navigates from HelpScreen to Add Resource screen', async () => {
33+
const project = await createMinimalProjectDir();
34+
cleanup = project.cleanup;
35+
36+
session = await TuiSession.launch({
37+
command: 'node',
38+
args: [CLI_ENTRY],
39+
cwd: project.dir,
40+
});
41+
42+
// Wait for the HelpScreen to render with its command list.
43+
await session.waitFor('Commands', 10000);
44+
45+
// Type 'add' to filter the command list, then press Enter to select it.
46+
await session.sendKeys('add');
47+
await session.waitFor('add', 3000);
48+
await session.sendSpecialKey('enter');
49+
50+
// Confirm the Add Resource screen has rendered.
51+
const screen = await session.waitFor('Add Resource', 10000);
52+
const text = screen.lines.join('\n');
53+
54+
expect(text).toContain('Agent');
55+
expect(text).toContain('Memory');
56+
expect(text).toContain('Identity');
57+
});
58+
59+
// ---------------------------------------------------------------------------
60+
// (b) Navigate to Add Agent wizard
61+
// ---------------------------------------------------------------------------
62+
it('navigates from Add Resource to Add Agent wizard', async () => {
63+
const project = await createMinimalProjectDir();
64+
cleanup = project.cleanup;
65+
66+
session = await TuiSession.launch({
67+
command: 'node',
68+
args: [CLI_ENTRY],
69+
cwd: project.dir,
70+
});
71+
72+
await session.waitFor('Commands', 10000);
73+
74+
// Navigate to Add Resource screen.
75+
await session.sendKeys('add');
76+
await session.waitFor('add', 3000);
77+
await session.sendSpecialKey('enter');
78+
await session.waitFor('Add Resource', 10000);
79+
80+
// Agent is the first item in the list -- press Enter to select it.
81+
await session.sendSpecialKey('enter');
82+
83+
// Wait for the Add Agent wizard to appear. It may show "Add Agent"
84+
// as a title or prompt for "Agent name".
85+
const screen = await session.waitFor(/Add Agent|Agent name/, 10000);
86+
const text = screen.lines.join('\n');
87+
88+
// The screen should contain some form of agent name input prompt.
89+
expect(text).toMatch(/Add Agent|Agent name/);
90+
});
91+
92+
// ---------------------------------------------------------------------------
93+
// (c) Back from Add Agent to Add Resource
94+
// ---------------------------------------------------------------------------
95+
it('returns from Add Agent to Add Resource via Escape', async () => {
96+
const project = await createMinimalProjectDir();
97+
cleanup = project.cleanup;
98+
99+
session = await TuiSession.launch({
100+
command: 'node',
101+
args: [CLI_ENTRY],
102+
cwd: project.dir,
103+
});
104+
105+
await session.waitFor('Commands', 10000);
106+
107+
// Navigate: HelpScreen -> Add Resource -> Add Agent
108+
await session.sendKeys('add');
109+
await session.waitFor('add', 3000);
110+
await session.sendSpecialKey('enter');
111+
await session.waitFor('Add Resource', 10000);
112+
await session.sendSpecialKey('enter');
113+
await session.waitFor(/Add Agent|Agent name/, 10000);
114+
115+
// Press Escape to go back to Add Resource.
116+
await session.sendSpecialKey('escape');
117+
118+
const screen = await session.waitFor('Add Resource', 5000);
119+
const text = screen.lines.join('\n');
120+
expect(text).toContain('Add Resource');
121+
});
122+
123+
// ---------------------------------------------------------------------------
124+
// (d) Back from Add Resource to HelpScreen
125+
// ---------------------------------------------------------------------------
126+
it('returns from Add Resource to HelpScreen via Escape', async () => {
127+
const project = await createMinimalProjectDir();
128+
cleanup = project.cleanup;
129+
130+
session = await TuiSession.launch({
131+
command: 'node',
132+
args: [CLI_ENTRY],
133+
cwd: project.dir,
134+
});
135+
136+
await session.waitFor('Commands', 10000);
137+
138+
// Navigate to Add Resource screen.
139+
await session.sendKeys('add');
140+
await session.waitFor('add', 3000);
141+
await session.sendSpecialKey('enter');
142+
await session.waitFor('Add Resource', 10000);
143+
144+
// Press Escape to go back to HelpScreen.
145+
await session.sendSpecialKey('escape');
146+
147+
const screen = await session.waitFor('Commands', 5000);
148+
const text = screen.lines.join('\n');
149+
expect(text).toContain('Commands');
150+
});
151+
});
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/**
2+
* Integration tests for the TUI create-project wizard.
3+
*
4+
* These tests launch the agentcore CLI in a headless PTY session and drive
5+
* through the interactive project creation flow: entering a project name,
6+
* optionally adding an agent (stepping through the full agent wizard), and
7+
* verifying that the project is created successfully on disk.
8+
*
9+
* All tests are wrapped in describe.skipIf(!isAvailable) so they are
10+
* gracefully skipped when node-pty is not available.
11+
*/
12+
import { TuiSession, isAvailable } from '../../src/tui-harness/index.js';
13+
import { realpathSync } from 'fs';
14+
import { mkdtemp, rm } from 'fs/promises';
15+
import { tmpdir } from 'os';
16+
import { join } from 'path';
17+
import { afterEach, describe, expect, it } from 'vitest';
18+
19+
describe.skipIf(!isAvailable)('create wizard TUI flow', () => {
20+
const CLI_ENTRY = join(process.cwd(), 'dist/cli/index.mjs');
21+
22+
let session: TuiSession | undefined;
23+
let tempDir: string | undefined;
24+
25+
afterEach(async () => {
26+
if (session?.alive) await session.close();
27+
session = undefined;
28+
if (tempDir) {
29+
// eslint-disable-next-line @typescript-eslint/no-empty-function
30+
await rm(tempDir, { recursive: true, force: true }).catch(() => {});
31+
tempDir = undefined;
32+
}
33+
});
34+
35+
// ---------------------------------------------------------------------------
36+
// (a) Full create wizard — create project with agent
37+
// ---------------------------------------------------------------------------
38+
it('creates a project with an agent through the full wizard', async () => {
39+
tempDir = realpathSync(await mkdtemp(join(tmpdir(), 'tui-create-')));
40+
41+
session = await TuiSession.launch({
42+
command: 'node',
43+
args: [CLI_ENTRY],
44+
cwd: tempDir,
45+
cols: 120,
46+
rows: 40,
47+
});
48+
49+
// Step 1: HomeScreen — no project found
50+
await session.waitFor('No AgentCore project found', 15000);
51+
52+
// Step 2: Press enter to navigate to CreateScreen
53+
await session.sendSpecialKey('enter');
54+
55+
// Step 3: Wait for the project name prompt
56+
await session.waitFor('Project name', 10000);
57+
58+
// Step 4: Type the project name and submit
59+
await session.sendKeys('testproject');
60+
await session.sendSpecialKey('enter');
61+
62+
// Step 5: Wait for the "add agent" prompt
63+
await session.waitFor('Would you like to add an agent now?', 10000);
64+
65+
// Step 6: Select "Yes, add an agent" (first option — just press enter)
66+
await session.sendSpecialKey('enter');
67+
68+
// Step 7: Agent name prompt
69+
await session.waitFor('Agent name', 10000);
70+
71+
// Step 8: Accept default agent name
72+
await session.sendSpecialKey('enter');
73+
74+
// Step 9: Agent type selection
75+
await session.waitFor('Select agent type', 10000);
76+
77+
// Step 10: Select first option (Create new agent)
78+
await session.sendSpecialKey('enter');
79+
80+
// Step 11: Language selection
81+
await session.waitFor(/Python|Select language/, 10000);
82+
83+
// Step 12: Select Python (first option)
84+
await session.sendSpecialKey('enter');
85+
86+
// Step 13: Build type selection
87+
await session.waitFor(/build|CodeZip|Container|Direct Code Deploy/i, 10000);
88+
89+
// Step 14: Select first build type
90+
await session.sendSpecialKey('enter');
91+
92+
// Step 15: Protocol selection (HTTP, MCP, A2A)
93+
await session.waitFor(/protocol|HTTP|MCP|A2A/i, 10000);
94+
95+
// Step 16: Select first protocol
96+
await session.sendSpecialKey('enter');
97+
98+
// Step 17: Framework selection
99+
await session.waitFor(/Strands|Select.*framework/i, 10000);
100+
101+
// Step 18: Select first framework
102+
await session.sendSpecialKey('enter');
103+
104+
// Step 19: Model provider selection
105+
await session.waitFor(/Bedrock|Select.*model|model.*provider/i, 10000);
106+
107+
// Step 20: Select first model provider
108+
await session.sendSpecialKey('enter');
109+
110+
// Step 21: Network selection or API key or memory or review — the wizard
111+
// may show different steps depending on model provider choice.
112+
const nextScreen = await session.waitFor(/network|memory|api.?key|Review Configuration/i, 10000);
113+
const nextText = nextScreen.lines.join('\n');
114+
115+
// Walk through any intermediate steps until we reach Review Configuration
116+
if (/api.?key/i.test(nextText)) {
117+
await session.sendSpecialKey('enter');
118+
await session.waitFor(/network|memory|Review Configuration/i, 10000);
119+
}
120+
121+
// Keep advancing through remaining steps (network, memory) until Review
122+
const currentScreen = session.readScreen();
123+
let currentText = currentScreen.lines.join('\n');
124+
125+
while (!/Review Configuration/i.test(currentText)) {
126+
await session.sendSpecialKey('enter');
127+
const screen = await session.waitFor(/network|memory|Review Configuration/i, 10000);
128+
currentText = screen.lines.join('\n');
129+
}
130+
131+
// Step 22: Review and confirm
132+
await session.waitFor('Review Configuration', 10000);
133+
await session.sendSpecialKey('enter');
134+
135+
// Step 21: Wait for project creation to complete (generous timeout)
136+
const successScreen = await session.waitFor('Project created successfully', 30000);
137+
138+
const successText = successScreen.lines.join('\n');
139+
expect(successText).toContain('Project created successfully');
140+
}, 120_000);
141+
142+
// ---------------------------------------------------------------------------
143+
// (b) Create wizard — skip agent creation
144+
// ---------------------------------------------------------------------------
145+
it('creates a project without adding an agent', async () => {
146+
tempDir = realpathSync(await mkdtemp(join(tmpdir(), 'tui-create-skip-')));
147+
148+
session = await TuiSession.launch({
149+
command: 'node',
150+
args: [CLI_ENTRY],
151+
cwd: tempDir,
152+
cols: 120,
153+
rows: 40,
154+
});
155+
156+
// HomeScreen
157+
await session.waitFor('No AgentCore project found', 15000);
158+
159+
// Navigate to CreateScreen
160+
await session.sendSpecialKey('enter');
161+
162+
// Project name prompt
163+
await session.waitFor('Project name', 10000);
164+
165+
// Type project name and submit
166+
await session.sendKeys('skiptest');
167+
await session.sendSpecialKey('enter');
168+
169+
// Wait for the "add agent" prompt
170+
await session.waitFor('Would you like to add an agent now?', 10000);
171+
172+
// Move to "No, I'll do it later" and select it
173+
await session.sendSpecialKey('down');
174+
await session.sendSpecialKey('enter');
175+
176+
// Wait for project creation to complete
177+
const successScreen = await session.waitFor('Project created successfully', 30000);
178+
179+
const successText = successScreen.lines.join('\n');
180+
expect(successText).toContain('Project created successfully');
181+
});
182+
183+
// ---------------------------------------------------------------------------
184+
// (c) Back navigation during wizard
185+
// ---------------------------------------------------------------------------
186+
it('navigates back from CreateScreen to HelpScreen with escape', async () => {
187+
tempDir = realpathSync(await mkdtemp(join(tmpdir(), 'tui-create-back-')));
188+
189+
session = await TuiSession.launch({
190+
command: 'node',
191+
args: [CLI_ENTRY],
192+
cwd: tempDir,
193+
cols: 120,
194+
rows: 40,
195+
});
196+
197+
// HomeScreen
198+
await session.waitFor('No AgentCore project found', 15000);
199+
200+
// Navigate to CreateScreen
201+
await session.sendSpecialKey('enter');
202+
203+
// Confirm we are on the CreateScreen
204+
await session.waitFor('Project name', 10000);
205+
206+
// Press escape to go back
207+
await session.sendSpecialKey('escape');
208+
209+
// Verify we are back on HelpScreen
210+
const homeScreen = await session.waitFor('Commands', 10000);
211+
212+
const homeText = homeScreen.lines.join('\n');
213+
expect(homeText).toContain('Commands');
214+
});
215+
});

0 commit comments

Comments
 (0)