Skip to content

Commit 1305342

Browse files
committed
test(e2e): simplyfying e2e tests
1 parent f7a7a95 commit 1305342

17 files changed

Lines changed: 371 additions & 252 deletions

apps/vscode-e2e/src/fixtures/subtasks.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { toolResultContains } from "./tool-result"
66
const SUBTASK_PARENT_MARKER = "SUBTASK_PARENT_CANCELLATION_SMOKE"
77
const SUBTASK_CHILD_MARKER = "SUBTASK_CHILD_CALCULATOR_SMOKE"
88

9-
export const SUBTASK_CHILD_PROMPT = `${SUBTASK_CHILD_MARKER}: Ask the user exactly this follow-up question: What is the square root of 81? After the user answers, complete with only the answer.`
9+
const SUBTASK_CHILD_PROMPT = `${SUBTASK_CHILD_MARKER}: Ask the user exactly this follow-up question: What is the square root of 81? After the user answers, complete with only the answer.`
1010
export const SUBTASK_PARENT_PROMPT = `${SUBTASK_PARENT_MARKER}: Use the new_task tool exactly once. Create an ask-mode subtask with this exact message: "${SUBTASK_CHILD_PROMPT}" Do not answer directly.`
1111
export const SUBTASK_CHILD_FOLLOWUP_ANSWER = "9"
1212

@@ -18,8 +18,11 @@ const requestContains = (req: ChatCompletionRequest, expected: string[]) => {
1818
const completionAfterAnswer = (followupId: string, completionId: string) => ({
1919
match: {
2020
predicate: (req: ChatCompletionRequest) =>
21+
// Preferred: structured tool-result message carries the followup answer.
2122
toolResultContains(req, followupId, [SUBTASK_CHILD_FOLLOWUP_ANSWER]) ||
23+
// Fallback 1: answer present alongside the tool-call ID but not in a role:tool message.
2224
requestContains(req, [followupId, SUBTASK_CHILD_FOLLOWUP_ANSWER]) ||
25+
// Fallback 2: answer arrives as a bare user message after task resume (no tool-call ID context).
2326
requestContains(req, [
2427
SUBTASK_CHILD_MARKER,
2528
`<user_message>\\n${SUBTASK_CHILD_FOLLOWUP_ANSWER}\\n</user_message>`,

apps/vscode-e2e/src/suite/subtasks.test.ts

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,87 @@ import { SUBTASK_CHILD_FOLLOWUP_ANSWER, SUBTASK_PARENT_PROMPT } from "../fixture
99
suite("Roo Code Subtasks", function () {
1010
setDefaultSuiteTimeout(this)
1111

12-
test("Should keep parent paused after subtask cancellation", async () => {
12+
// Race mitigation: skipDelegationRepair prevents removeClineFromStack from
13+
// auto-resuming the parent when the child is cancelled (Race 2).
14+
test("parent stays paused after subtask cancellation", async () => {
1315
const api = globalThis.api
1416
const asks: Record<string, ClineMessage[]> = {}
1517
const messages: Record<string, ClineMessage[]> = {}
16-
const waitForStage = async (label: string, condition: Parameters<typeof waitFor>[0]) => {
17-
try {
18-
await waitFor(condition)
19-
} catch (error) {
20-
const message = error instanceof Error ? error.message : String(error)
21-
throw new Error(`${label}: ${message}`)
18+
19+
const messageHandler = ({ taskId, message }: { taskId: string; message: ClineMessage }) => {
20+
if (message.type === "ask") {
21+
asks[taskId] = asks[taskId] || []
22+
asks[taskId].push(message)
23+
}
24+
if (message.type === "say" && message.partial === false) {
25+
messages[taskId] = messages[taskId] || []
26+
messages[taskId].push(message)
2227
}
2328
}
2429

30+
api.on(RooCodeEventName.Message, messageHandler)
31+
32+
try {
33+
const parentTaskId = await api.startNewTask({
34+
configuration: {
35+
mode: "ask",
36+
alwaysAllowModeSwitch: true,
37+
alwaysAllowSubtasks: true,
38+
autoApprovalEnabled: true,
39+
enableCheckpoints: false,
40+
},
41+
text: SUBTASK_PARENT_PROMPT,
42+
})
43+
44+
let spawnedTaskId: string | undefined
45+
await waitFor(() => {
46+
const stack = api.getCurrentTaskStack()
47+
const current = stack[stack.length - 1]
48+
if (current && current !== parentTaskId) {
49+
spawnedTaskId = current
50+
return true
51+
}
52+
return false
53+
})
54+
55+
await waitFor(
56+
() => asks[spawnedTaskId!]?.some(({ type, ask }) => type === "ask" && ask === "followup") ?? false,
57+
)
58+
59+
await api.cancelCurrentTask()
60+
61+
assert.ok(
62+
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
63+
undefined,
64+
"Parent task should not have resumed after subtask cancellation",
65+
)
66+
67+
await waitFor(() => api.getCurrentTaskStack().at(-1) === spawnedTaskId)
68+
await waitFor(
69+
() => asks[spawnedTaskId!]?.some(({ type, ask }) => type === "ask" && ask === "resume_task") ?? false,
70+
)
71+
72+
await api.clearCurrentTask()
73+
// The parent task is still in the stack; drain it so it doesn't leak into the next test.
74+
await api.clearCurrentTask()
75+
await waitFor(() => api.getCurrentTaskStack().length === 0)
76+
} finally {
77+
api.off(RooCodeEventName.Message, messageHandler)
78+
}
79+
})
80+
81+
// Race mitigation: runDelegationTransition lock + cancelledDelegationChildIds guard
82+
// ensures cancelTask() wins over a concurrent reopenParentFromDelegation() (Race 3).
83+
test("cancelled child completes in-place and does not reopen parent", async () => {
84+
const api = globalThis.api
85+
const asks: Record<string, ClineMessage[]> = {}
86+
const messages: Record<string, ClineMessage[]> = {}
87+
2588
const messageHandler = ({ taskId, message }: { taskId: string; message: ClineMessage }) => {
2689
if (message.type === "ask") {
2790
asks[taskId] = asks[taskId] || []
2891
asks[taskId].push(message)
2992
}
30-
3193
if (message.type === "say" && message.partial === false) {
3294
messages[taskId] = messages[taskId] || []
3395
messages[taskId].push(message)
@@ -64,35 +126,25 @@ suite("Roo Code Subtasks", function () {
64126
})
65127

66128
let spawnedTaskId: string | undefined
67-
await waitForStage("wait for spawned subtask", () => {
68-
const currentTaskStack = api.getCurrentTaskStack()
69-
const currentTaskId = currentTaskStack[currentTaskStack.length - 1]
70-
if (currentTaskId && currentTaskId !== parentTaskId) {
71-
spawnedTaskId = currentTaskId
129+
await waitFor(() => {
130+
const stack = api.getCurrentTaskStack()
131+
const current = stack[stack.length - 1]
132+
if (current && current !== parentTaskId) {
133+
spawnedTaskId = current
72134
return true
73135
}
74136
return false
75137
})
76-
await waitForStage(
77-
"wait for delegated child followup ask",
138+
139+
await waitFor(
78140
() => asks[spawnedTaskId!]?.some(({ type, ask }) => type === "ask" && ask === "followup") ?? false,
79141
)
80-
const cancelledChildTaskId = spawnedTaskId!
81142

143+
const cancelledChildTaskId = spawnedTaskId!
82144
await api.cancelCurrentTask()
83145

84-
assert.ok(
85-
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
86-
undefined,
87-
"Parent task should not have resumed after subtask cancellation",
88-
)
89-
90-
await waitForStage(
91-
"wait for cancelled child task to remain active",
92-
() => api.getCurrentTaskStack().at(-1) === cancelledChildTaskId,
93-
)
94-
await waitForStage(
95-
"wait for cancelled child resume ask",
146+
await waitFor(() => api.getCurrentTaskStack().at(-1) === cancelledChildTaskId)
147+
await waitFor(
96148
() =>
97149
asks[cancelledChildTaskId]?.some(({ type, ask }) => type === "ask" && ask === "resume_task") ??
98150
false,
@@ -126,7 +178,6 @@ suite("Roo Code Subtasks", function () {
126178
cancelledChildTaskId,
127179
"Cancelled child task should remain the active completed task",
128180
)
129-
130181
assert.ok(
131182
messages[parentTaskId]?.find(({ type, text }) => type === "say" && text === "Parent task resumed") ===
132183
undefined,

apps/vscode-e2e/src/suite/tools/apply-diff.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ suite("Roo Code apply_diff Tool", function () {
128128

129129
suiteTeardown(async () => {
130130
try {
131-
await globalThis.api.cancelCurrentTask()
131+
await globalThis.api.clearCurrentTask()
132132
} catch {
133133
// Task might not be running
134134
}
@@ -147,7 +147,7 @@ suite("Roo Code apply_diff Tool", function () {
147147

148148
setup(async () => {
149149
try {
150-
await globalThis.api.cancelCurrentTask()
150+
await globalThis.api.clearCurrentTask()
151151
} catch {
152152
// Task might not be running
153153
}
@@ -164,7 +164,7 @@ suite("Roo Code apply_diff Tool", function () {
164164

165165
teardown(async () => {
166166
try {
167-
await globalThis.api.cancelCurrentTask()
167+
await globalThis.api.clearCurrentTask()
168168
} catch {
169169
// Task might not be running
170170
}

apps/vscode-e2e/src/suite/tools/execute-command.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ suite("Roo Code execute_command Tool", function () {
4343

4444
suiteTeardown(async () => {
4545
try {
46-
await globalThis.api.cancelCurrentTask()
46+
await globalThis.api.clearCurrentTask()
4747
} catch {
4848
// Task might not be running
4949
}
@@ -62,7 +62,7 @@ suite("Roo Code execute_command Tool", function () {
6262

6363
setup(async () => {
6464
try {
65-
await globalThis.api.cancelCurrentTask()
65+
await globalThis.api.clearCurrentTask()
6666
} catch {
6767
// Task might not be running
6868
}
@@ -74,7 +74,7 @@ suite("Roo Code execute_command Tool", function () {
7474

7575
teardown(async () => {
7676
try {
77-
await globalThis.api.cancelCurrentTask()
77+
await globalThis.api.clearCurrentTask()
7878
} catch {
7979
// Task might not be running
8080
}

apps/vscode-e2e/src/suite/tools/list-files.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ This directory contains various files and subdirectories for testing the list_fi
129129

130130
// Clean up test files and directories after all tests
131131
suiteTeardown(async () => {
132-
// Cancel any running tasks before cleanup
132+
// Clear any running tasks before cleanup
133133
try {
134-
await globalThis.api.cancelCurrentTask()
134+
await globalThis.api.clearCurrentTask()
135135
} catch {
136136
// Task might not be running
137137
}
@@ -149,9 +149,9 @@ This directory contains various files and subdirectories for testing the list_fi
149149

150150
// Clean up before each test
151151
setup(async () => {
152-
// Cancel any previous task
152+
// Clear any previous task
153153
try {
154-
await globalThis.api.cancelCurrentTask()
154+
await globalThis.api.clearCurrentTask()
155155
} catch {
156156
// Task might not be running
157157
}
@@ -162,9 +162,9 @@ This directory contains various files and subdirectories for testing the list_fi
162162

163163
// Clean up after each test
164164
teardown(async () => {
165-
// Cancel the current task
165+
// Clear the current task
166166
try {
167-
await globalThis.api.cancelCurrentTask()
167+
await globalThis.api.clearCurrentTask()
168168
} catch {
169169
// Task might not be running
170170
}

apps/vscode-e2e/src/suite/tools/read-file.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ suite("Roo Code read_file Tool", function () {
6767

6868
// Clean up temporary directory and files after tests
6969
suiteTeardown(async () => {
70-
// Cancel any running tasks before cleanup
70+
// Clear any running tasks before cleanup
7171
try {
72-
await globalThis.api.cancelCurrentTask()
72+
await globalThis.api.clearCurrentTask()
7373
} catch {
7474
// Task might not be running
7575
}
@@ -96,9 +96,9 @@ suite("Roo Code read_file Tool", function () {
9696

9797
// Clean up before each test
9898
setup(async () => {
99-
// Cancel any previous task
99+
// Clear any previous task
100100
try {
101-
await globalThis.api.cancelCurrentTask()
101+
await globalThis.api.clearCurrentTask()
102102
} catch {
103103
// Task might not be running
104104
}
@@ -109,9 +109,9 @@ suite("Roo Code read_file Tool", function () {
109109

110110
// Clean up after each test
111111
teardown(async () => {
112-
// Cancel the current task
112+
// Clear the current task
113113
try {
114-
await globalThis.api.cancelCurrentTask()
114+
await globalThis.api.clearCurrentTask()
115115
} catch {
116116
// Task might not be running
117117
}

apps/vscode-e2e/src/suite/tools/search-files.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,9 @@ The search should find matches across different file types and provide context f
240240

241241
// Clean up after all tests
242242
suiteTeardown(async () => {
243-
// Cancel any running tasks before cleanup
243+
// Clear any running tasks before cleanup
244244
try {
245-
await globalThis.api.cancelCurrentTask()
245+
await globalThis.api.clearCurrentTask()
246246
} catch {
247247
// Task might not be running
248248
}
@@ -270,9 +270,9 @@ The search should find matches across different file types and provide context f
270270

271271
// Clean up before each test
272272
setup(async () => {
273-
// Cancel any previous task
273+
// Clear any previous task
274274
try {
275-
await globalThis.api.cancelCurrentTask()
275+
await globalThis.api.clearCurrentTask()
276276
} catch {
277277
// Task might not be running
278278
}
@@ -283,9 +283,9 @@ The search should find matches across different file types and provide context f
283283

284284
// Clean up after each test
285285
teardown(async () => {
286-
// Cancel the current task
286+
// Clear the current task
287287
try {
288-
await globalThis.api.cancelCurrentTask()
288+
await globalThis.api.clearCurrentTask()
289289
} catch {
290290
// Task might not be running
291291
}

apps/vscode-e2e/src/suite/tools/use-mcp-tool.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ suite("Roo Code use_mcp_tool Tool", function () {
190190

191191
suiteTeardown(async () => {
192192
try {
193-
await globalThis.api.cancelCurrentTask()
193+
await globalThis.api.clearCurrentTask()
194194
} catch {
195195
// Task might not be running
196196
}
@@ -201,7 +201,7 @@ suite("Roo Code use_mcp_tool Tool", function () {
201201

202202
setup(async () => {
203203
try {
204-
await globalThis.api.cancelCurrentTask()
204+
await globalThis.api.clearCurrentTask()
205205
} catch {
206206
// Task might not be running
207207
}
@@ -212,7 +212,7 @@ suite("Roo Code use_mcp_tool Tool", function () {
212212

213213
teardown(async () => {
214214
try {
215-
await globalThis.api.cancelCurrentTask()
215+
await globalThis.api.clearCurrentTask()
216216
} catch {
217217
// Task might not be running
218218
}

apps/vscode-e2e/src/suite/tools/write-to-file.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ suite("Roo Code write_to_file Tool", function () {
4343

4444
suiteTeardown(async () => {
4545
try {
46-
await globalThis.api.cancelCurrentTask()
46+
await globalThis.api.clearCurrentTask()
4747
} catch {
4848
// Task might not be running
4949
}
@@ -62,7 +62,7 @@ suite("Roo Code write_to_file Tool", function () {
6262

6363
setup(async () => {
6464
try {
65-
await globalThis.api.cancelCurrentTask()
65+
await globalThis.api.clearCurrentTask()
6666
} catch {
6767
// Task might not be running
6868
}
@@ -74,7 +74,7 @@ suite("Roo Code write_to_file Tool", function () {
7474

7575
teardown(async () => {
7676
try {
77-
await globalThis.api.cancelCurrentTask()
77+
await globalThis.api.clearCurrentTask()
7878
} catch {
7979
// Task might not be running
8080
}

0 commit comments

Comments
 (0)