Skip to content

Commit 04533ce

Browse files
aidandaly24claude
andcommitted
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. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d149b95 commit 04533ce

File tree

6 files changed

+947
-0
lines changed

6 files changed

+947
-0
lines changed

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: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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: Framework selection
93+
await session.waitFor(/Strands|Select.*framework|Select/i, 10000);
94+
95+
// Step 16: Select first framework
96+
await session.sendSpecialKey('enter');
97+
98+
// Step 17: Model provider selection
99+
await session.waitFor(/Bedrock|Select.*model|model.*provider/i, 10000);
100+
101+
// Step 18: Select first model provider
102+
await session.sendSpecialKey('enter');
103+
104+
// Step 19: Memory selection — some model providers may insert an API key
105+
// step before this. Wait for either memory or API key text.
106+
const memoryOrApiKeyScreen = await session.waitFor(/memory|api.?key|Review Configuration/i, 10000);
107+
108+
const memoryOrApiKeyText = memoryOrApiKeyScreen.lines.join('\n');
109+
110+
if (/api.?key/i.test(memoryOrApiKeyText)) {
111+
// API key step — accept default / skip
112+
await session.sendSpecialKey('enter');
113+
// Now wait for memory
114+
await session.waitFor(/memory|Review Configuration/i, 10000);
115+
}
116+
117+
// If we landed on memory selection (not yet at Review), select first option
118+
const currentScreen = session.readScreen();
119+
const currentText = currentScreen.lines.join('\n');
120+
121+
if (!/Review Configuration/i.test(currentText)) {
122+
await session.sendSpecialKey('enter');
123+
}
124+
125+
// Step 20: Review and confirm
126+
await session.waitFor('Review Configuration', 10000);
127+
await session.sendSpecialKey('enter');
128+
129+
// Step 21: Wait for project creation to complete (generous timeout)
130+
const successScreen = await session.waitFor('Project created successfully', 30000);
131+
132+
const successText = successScreen.lines.join('\n');
133+
expect(successText).toContain('Project created successfully');
134+
}, 120_000);
135+
136+
// ---------------------------------------------------------------------------
137+
// (b) Create wizard — skip agent creation
138+
// ---------------------------------------------------------------------------
139+
it('creates a project without adding an agent', async () => {
140+
tempDir = realpathSync(await mkdtemp(join(tmpdir(), 'tui-create-skip-')));
141+
142+
session = await TuiSession.launch({
143+
command: 'node',
144+
args: [CLI_ENTRY],
145+
cwd: tempDir,
146+
cols: 120,
147+
rows: 40,
148+
});
149+
150+
// HomeScreen
151+
await session.waitFor('No AgentCore project found', 15000);
152+
153+
// Navigate to CreateScreen
154+
await session.sendSpecialKey('enter');
155+
156+
// Project name prompt
157+
await session.waitFor('Project name', 10000);
158+
159+
// Type project name and submit
160+
await session.sendKeys('skiptest');
161+
await session.sendSpecialKey('enter');
162+
163+
// Wait for the "add agent" prompt
164+
await session.waitFor('Would you like to add an agent now?', 10000);
165+
166+
// Move to "No, I'll do it later" and select it
167+
await session.sendSpecialKey('down');
168+
await session.sendSpecialKey('enter');
169+
170+
// Wait for project creation to complete
171+
const successScreen = await session.waitFor('Project created successfully', 30000);
172+
173+
const successText = successScreen.lines.join('\n');
174+
expect(successText).toContain('Project created successfully');
175+
});
176+
177+
// ---------------------------------------------------------------------------
178+
// (c) Back navigation during wizard
179+
// ---------------------------------------------------------------------------
180+
it('navigates back from CreateScreen to HelpScreen with escape', async () => {
181+
tempDir = realpathSync(await mkdtemp(join(tmpdir(), 'tui-create-back-')));
182+
183+
session = await TuiSession.launch({
184+
command: 'node',
185+
args: [CLI_ENTRY],
186+
cwd: tempDir,
187+
cols: 120,
188+
rows: 40,
189+
});
190+
191+
// HomeScreen
192+
await session.waitFor('No AgentCore project found', 15000);
193+
194+
// Navigate to CreateScreen
195+
await session.sendSpecialKey('enter');
196+
197+
// Confirm we are on the CreateScreen
198+
await session.waitFor('Project name', 10000);
199+
200+
// Press escape to go back
201+
await session.sendSpecialKey('escape');
202+
203+
// Verify we are back on HelpScreen
204+
const homeScreen = await session.waitFor('Commands', 10000);
205+
206+
const homeText = homeScreen.lines.join('\n');
207+
expect(homeText).toContain('Commands');
208+
});
209+
});

0 commit comments

Comments
 (0)