Skip to content

Commit c134bad

Browse files
committed
Harden CLI/sdk e2e tests
1 parent b4480b5 commit c134bad

File tree

4 files changed

+81
-73
lines changed

4 files changed

+81
-73
lines changed

cli/src/__tests__/e2e/cli-ui.test.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const TIMEOUT_MS = 25000
1515
const sdkBuilt = isSDKBuilt()
1616

1717
if (!sdkBuilt) {
18-
throw new Error('CLI UI tests require SDK to be built. Run: cd sdk && bun run build')
18+
describe.skip('CLI UI Tests', () => {
19+
test('skipped because SDK is not built', () => {})
20+
})
1921
}
2022

2123
let cliEnv: Record<string, string> = {}
@@ -99,6 +101,7 @@ describe('CLI UI Tests', () => {
99101

100102
const text = await session.text()
101103
expect(text).toContain('--agent')
104+
expect(text).toContain('--help')
102105
} finally {
103106
session.close()
104107
}
@@ -151,10 +154,10 @@ describe('CLI UI Tests', () => {
151154

152155
try {
153156
// Commander should show an error for invalid flags
154-
await session.waitForText('error', { timeout: 10000 })
157+
await session.waitForText(/unknown option|error/i, { timeout: 10000 })
155158

156159
const text = await session.text()
157-
expect(text.toLowerCase()).toContain('error')
160+
expect(text.toLowerCase()).toContain('unknown')
158161
} finally {
159162
session.close()
160163
}
@@ -170,12 +173,12 @@ describe('CLI UI Tests', () => {
170173
const session = await launchCLI({ args: [] })
171174

172175
try {
173-
// Wait for something to render - either the login modal or the main UI
174-
// The CLI should render some UI within the timeout
175-
await sleep(3000)
176+
await session.waitForText(
177+
/codebuff|login|directory|will run commands/i,
178+
{ timeout: 15000 },
179+
)
176180

177181
const text = await session.text()
178-
// Should have rendered something - either login prompt or welcome message
179182
expect(text.length).toBeGreaterThan(0)
180183
} finally {
181184
await session.press(['ctrl', 'c'])
@@ -191,11 +194,10 @@ describe('CLI UI Tests', () => {
191194
const session = await launchCLI({ args: ['--agent', 'ask'] })
192195

193196
try {
194-
// CLI should start without errors
195-
await sleep(2000)
197+
await session.waitForText(/ask|codebuff|login/i, { timeout: 15000 })
196198

197199
const text = await session.text()
198-
expect(text.length).toBeGreaterThan(0)
200+
expect(text.toLowerCase()).not.toContain('unknown option')
199201
} finally {
200202
await session.press(['ctrl', 'c'])
201203
session.close()
@@ -210,8 +212,9 @@ describe('CLI UI Tests', () => {
210212
const session = await launchCLI({ args: ['--clear-logs'] })
211213

212214
try {
213-
// CLI should start without errors
214-
await sleep(2000)
215+
await session.waitForText(/codebuff|login|directory/i, {
216+
timeout: 15000,
217+
})
215218

216219
const text = await session.text()
217220
expect(text.length).toBeGreaterThan(0)
@@ -450,5 +453,3 @@ describe('CLI UI Tests', () => {
450453
)
451454
})
452455
})
453-
454-

cli/src/__tests__/e2e/full-stack.test.ts

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ function isDockerAvailable(): boolean {
3636

3737
const dockerAvailable = isDockerAvailable()
3838

39-
if (!sdkBuilt) {
40-
throw new Error('E2E tests require SDK to be built. Run: cd sdk && bun run build')
41-
}
42-
43-
if (!dockerAvailable) {
44-
throw new Error('E2E tests require Docker to be running')
39+
if (!sdkBuilt || !dockerAvailable) {
40+
const reason = !sdkBuilt
41+
? 'SDK not built (run: cd sdk && bun run build)'
42+
: 'Docker not running'
43+
describe.skip(`E2E skipped: ${reason}`, () => {
44+
test('skipped', () => {})
45+
})
4546
}
4647

4748
describe('E2E: Chat Interaction', () => {
@@ -64,13 +65,13 @@ describe('E2E: Chat Interaction', () => {
6465
async () => {
6566
const session = await ctx.createSession()
6667

67-
// Wait for CLI to render
68-
await sleep(5000)
69-
68+
await session.cli.waitForText(/codebuff|login|directory|will run/i, {
69+
timeout: 15000,
70+
})
7071
const text = await session.cli.text()
71-
// Should show Codebuff branding or welcome message
7272
const hasWelcome =
7373
text.toLowerCase().includes('codebuff') ||
74+
text.toLowerCase().includes('login') ||
7475
text.includes('Directory') ||
7576
text.includes('will run commands')
7677
expect(hasWelcome).toBe(true)
@@ -83,14 +84,11 @@ describe('E2E: Chat Interaction', () => {
8384
async () => {
8485
const session = await ctx.createSession()
8586

86-
await sleep(5000)
87-
8887
// Type a test message
8988
await session.cli.type('Hello from e2e test')
90-
await sleep(500)
91-
92-
const text = await session.cli.text()
93-
expect(text).toContain('Hello from e2e test')
89+
await session.cli.waitForText('Hello from e2e test', {
90+
timeout: 10000,
91+
})
9492
},
9593
TIMEOUT_MS,
9694
)
@@ -100,24 +98,14 @@ describe('E2E: Chat Interaction', () => {
10098
async () => {
10199
const session = await ctx.createSession()
102100

103-
await sleep(5000)
104-
105101
// Type and send a message
106102
await session.cli.type('What is 2+2?')
107103
await sleep(300)
108104
await session.cli.press('enter')
109105

110-
// Wait for status to appear
111-
await sleep(2000)
112-
113-
const text = await session.cli.text()
114-
// Should show some status indicator
115-
const hasStatus =
116-
text.includes('thinking') ||
117-
text.includes('working') ||
118-
text.includes('connecting') ||
119-
text.includes('What is 2+2?') // Message should at least appear
120-
expect(hasStatus).toBe(true)
106+
await session.cli.waitForText(/thinking|working|connecting|2\+2/i, {
107+
timeout: 15000,
108+
})
121109
},
122110
TIMEOUT_MS,
123111
)
@@ -143,17 +131,13 @@ describe('E2E: Slash Commands', () => {
143131
async () => {
144132
const session = await ctx.createSession()
145133

146-
await sleep(5000)
147-
148134
// Type /new and press enter
149135
await session.cli.type('/new')
150136
await sleep(300)
151137
await session.cli.press('enter')
152-
await sleep(1500)
153-
154-
const text = await session.cli.text()
155-
// CLI should still be running
156-
expect(text.length).toBeGreaterThan(0)
138+
await session.cli.waitForText(/\/new|conversation/i, {
139+
timeout: 10000,
140+
})
157141
},
158142
TIMEOUT_MS,
159143
)
@@ -163,22 +147,11 @@ describe('E2E: Slash Commands', () => {
163147
async () => {
164148
const session = await ctx.createSession()
165149

166-
await sleep(5000)
167-
168150
// Type /usage and press enter
169151
await session.cli.type('/usage')
170152
await sleep(300)
171153
await session.cli.press('enter')
172-
await sleep(2000)
173-
174-
const text = await session.cli.text()
175-
// Should show some credit-related information
176-
const hasUsageInfo =
177-
text.toLowerCase().includes('credit') ||
178-
text.toLowerCase().includes('usage') ||
179-
text.includes('1000') || // Test user has 1000 credits
180-
text.includes('/usage')
181-
expect(hasUsageInfo).toBe(true)
154+
await session.cli.waitForText(/credit|usage|1000/i, { timeout: 15000 })
182155
},
183156
TIMEOUT_MS,
184157
)
@@ -188,8 +161,6 @@ describe('E2E: Slash Commands', () => {
188161
async () => {
189162
const session = await ctx.createSession()
190163

191-
await sleep(5000)
192-
193164
// Type / to trigger suggestions
194165
await session.cli.type('/')
195166
await sleep(1000)
@@ -884,5 +855,3 @@ describe('E2E: Error Scenarios', () => {
884855
TIMEOUT_MS,
885856
)
886857
})
887-
888-

sdk/e2e/features/knowledge-files.e2e.test.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,27 @@ import {
1818

1919
describe('Features: Knowledge Files', () => {
2020
let client: CodebuffClient
21+
let apiKey: string | null = null
2122

2223
beforeAll(() => {
24+
apiKey = process.env.CODEBUFF_API_KEY ?? null
25+
if (!apiKey) {
26+
// Skip gracefully if no API key is configured
27+
test.skip('CODEBUFF_API_KEY is required for knowledge files e2e')
28+
return
29+
}
2330
client = new CodebuffClient({ apiKey: getApiKey() })
2431
})
2532

2633
beforeEach(async () => {
34+
if (!apiKey) return
2735
await ensureBackendConnection()
2836
})
2937

3038
test(
3139
'agent uses injected knowledge files',
3240
async () => {
41+
if (!apiKey) return
3342
const collector = new EventCollector()
3443

3544
const result = await client.run({
@@ -43,15 +52,20 @@ describe('Features: Knowledge Files', () => {
4352

4453
if (isAuthError(result.output)) return
4554

46-
if (result.output.type === 'error') return
47-
expect(collector.hasEventType('finish')).toBe(true)
55+
expect(result.output.type).not.toBe('error')
56+
const responseText = collector.getFullText().toUpperCase()
57+
expect(
58+
responseText.includes('PINEAPPLE42') ||
59+
responseText.includes('PINEAPPLE'),
60+
).toBe(true)
4861
},
4962
DEFAULT_TIMEOUT,
5063
)
5164

5265
test(
5366
'multiple knowledge files are accessible',
5467
async () => {
68+
if (!apiKey) return
5569
const collector = new EventCollector()
5670

5771
const result = await client.run({
@@ -68,8 +82,12 @@ describe('Features: Knowledge Files', () => {
6882

6983
if (isAuthError(result.output)) return
7084

71-
if (result.output.type === 'error') return
72-
expect(collector.hasEventType('finish')).toBe(true)
85+
expect(result.output.type).not.toBe('error')
86+
const responseText = collector.getFullText().toLowerCase()
87+
expect(
88+
responseText.includes('innovation') ||
89+
responseText.includes('integrity'),
90+
).toBe(true)
7391
},
7492
DEFAULT_TIMEOUT,
7593
)

sdk/e2e/features/project-files.e2e.test.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,26 @@ import {
1919

2020
describe('Features: Project Files', () => {
2121
let client: CodebuffClient
22+
let apiKey: string | null = null
2223

2324
beforeAll(() => {
25+
apiKey = process.env.CODEBUFF_API_KEY ?? null
26+
if (!apiKey) {
27+
test.skip('CODEBUFF_API_KEY is required for project files e2e')
28+
return
29+
}
2430
client = new CodebuffClient({ apiKey: getApiKey() })
2531
})
2632

2733
beforeEach(async () => {
34+
if (!apiKey) return
2835
await ensureBackendConnection()
2936
})
3037

3138
test(
3239
'agent can reference injected project files',
3340
async () => {
41+
if (!apiKey) return
3442
const collector = new EventCollector()
3543

3644
const result = await client.run({
@@ -42,15 +50,22 @@ describe('Features: Project Files', () => {
4250

4351
if (isAuthError(result.output)) return
4452

45-
if (result.output.type === 'error') return
46-
expect(collector.hasEventType('finish')).toBe(true)
53+
expect(result.output.type).not.toBe('error')
54+
const responseText = collector.getFullText().toLowerCase()
55+
expect(
56+
responseText.includes('index') ||
57+
responseText.includes('calculator') ||
58+
responseText.includes('package.json') ||
59+
responseText.includes('readme'),
60+
).toBe(true)
4761
},
4862
DEFAULT_TIMEOUT,
4963
)
5064

5165
test(
5266
'agent can analyze content of project files',
5367
async () => {
68+
if (!apiKey) return
5469
const collector = new EventCollector()
5570

5671
const result = await client.run({
@@ -63,7 +78,12 @@ describe('Features: Project Files', () => {
6378
if (isAuthError(result.output)) return
6479

6580
expect(result.output.type).not.toBe('error')
66-
expect(collector.hasEventType('finish')).toBe(true)
81+
const responseText = collector.getFullText().toLowerCase()
82+
expect(
83+
responseText.includes('calculator') ||
84+
responseText.includes('add') ||
85+
responseText.includes('result'),
86+
).toBe(true)
6787
},
6888
DEFAULT_TIMEOUT,
6989
)

0 commit comments

Comments
 (0)