Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/e2e/fetchWebPageTool.stest.ts
13388 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import path from 'path';
8
import { ToolName } from '../../src/extension/tools/common/toolNames';
9
import { deserializeWorkbenchState } from '../../src/platform/test/node/promptContextModel';
10
import { ssuite, stest } from '../base/stest';
11
import { generateToolTestRunner } from './toolSimTest';
12
import { shouldSkipAgentTests } from './tools.stest';
13
14
interface IFetchWebPageToolParams {
15
urls: string[];
16
query?: string;
17
}
18
19
ssuite.optional(shouldSkipAgentTests, { title: 'fetchWebPageTool', subtitle: 'toolCalling', location: 'panel' }, () => {
20
const scenarioFolder = path.join(__dirname, '..', 'test/scenarios/test-tools');
21
const getState = () => deserializeWorkbenchState(scenarioFolder, path.join(scenarioFolder, 'tools.state.json'));
22
23
stest('proper URL validation and query handling', generateToolTestRunner({
24
question: 'fetch information about React hooks from https://react.dev/reference/react',
25
scenarioFolderPath: '',
26
getState,
27
expectedToolCalls: ToolName.FetchWebPage,
28
tools: {
29
[ToolName.FetchWebPage]: true,
30
[ToolName.FindFiles]: true,
31
[ToolName.FindTextInFiles]: true,
32
[ToolName.ReadFile]: true,
33
[ToolName.EditFile]: true,
34
[ToolName.Codebase]: true,
35
[ToolName.ListDirectory]: true,
36
[ToolName.SearchWorkspaceSymbols]: true,
37
},
38
}, {
39
allowParallelToolCalls: false,
40
toolCallValidators: {
41
[ToolName.FetchWebPage]: async (toolCalls) => {
42
assert.strictEqual(toolCalls.length, 1, 'should make exactly one fetch webpage tool call');
43
const input = toolCalls[0].input as IFetchWebPageToolParams;
44
45
// Should have exactly 1 URL
46
assert.ok(Array.isArray(input.urls), 'urls should be an array');
47
assert.strictEqual(input.urls.length, 1, 'should have exactly 1 URL');
48
49
// Should be the exact URL from the question
50
const expectedUrl = 'https://react.dev/reference/react';
51
assert.strictEqual(input.urls[0], expectedUrl, `should have the exact URL: ${expectedUrl}`);
52
53
// Validate query parameter if present
54
if (input.query !== undefined) {
55
assert.ok(typeof input.query === 'string', 'query should be a string if provided');
56
assert.ok(input.query.length > 0, 'query should not be empty if provided');
57
}
58
}
59
}
60
}));
61
62
stest('multiple URLs handling', generateToolTestRunner({
63
question: 'get content from https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function about async/await',
64
scenarioFolderPath: '',
65
getState,
66
expectedToolCalls: ToolName.FetchWebPage,
67
tools: {
68
[ToolName.FetchWebPage]: true,
69
[ToolName.FindFiles]: true,
70
[ToolName.FindTextInFiles]: true,
71
[ToolName.ReadFile]: true,
72
[ToolName.EditFile]: true,
73
[ToolName.Codebase]: true,
74
[ToolName.ListDirectory]: true,
75
[ToolName.SearchWorkspaceSymbols]: true,
76
},
77
}, {
78
allowParallelToolCalls: true,
79
toolCallValidators: {
80
[ToolName.FetchWebPage]: (toolCalls) => {
81
const expectedTypescriptUrl = 'https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html';
82
const expectedMdnUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function';
83
const expectedUrls = [expectedTypescriptUrl, expectedMdnUrl];
84
85
// Allow either 1 tool call with 2 URLs or 2 tool calls with 1 URL each
86
assert.ok(toolCalls.length === 1 || toolCalls.length === 2, 'should make either 1 or 2 fetch webpage tool calls');
87
88
if (toolCalls.length === 1) {
89
// Single tool call with multiple URLs
90
const input = toolCalls[0].input as IFetchWebPageToolParams;
91
92
// Should have exactly 2 URLs
93
assert.ok(Array.isArray(input.urls), 'urls should be an array');
94
assert.strictEqual(input.urls.length, 2, 'should have exactly 2 URLs');
95
96
// Check that both expected URLs are present
97
assert.ok(input.urls.includes(expectedTypescriptUrl), `should include the TypeScript URL: ${expectedTypescriptUrl}`);
98
assert.ok(input.urls.includes(expectedMdnUrl), `should include the MDN URL: ${expectedMdnUrl}`);
99
100
// Verify no unexpected URLs
101
input.urls.forEach(url => {
102
assert.ok(expectedUrls.includes(url), `unexpected URL found: ${url}`);
103
});
104
} else {
105
// Multiple parallel tool calls with one URL each
106
const allUrls: string[] = [];
107
toolCalls.forEach(toolCall => {
108
const input = toolCall.input as IFetchWebPageToolParams;
109
assert.ok(Array.isArray(input.urls), 'urls should be an array');
110
assert.strictEqual(input.urls.length, 1, 'each tool call should have exactly 1 URL');
111
allUrls.push(input.urls[0]);
112
});
113
114
// Check that both expected URLs are present across all tool calls
115
assert.ok(allUrls.includes(expectedTypescriptUrl), `should include the TypeScript URL: ${expectedTypescriptUrl}`);
116
assert.ok(allUrls.includes(expectedMdnUrl), `should include the MDN URL: ${expectedMdnUrl}`);
117
118
// Verify no unexpected URLs
119
allUrls.forEach(url => {
120
assert.ok(expectedUrls.includes(url), `unexpected URL found: ${url}`);
121
});
122
}
123
124
// Check query parameter for any tool call that has it
125
toolCalls.forEach(toolCall => {
126
const input = toolCall.input as IFetchWebPageToolParams;
127
if (input.query) {
128
assert.ok(
129
input.query.toLowerCase().includes('async') || input.query.toLowerCase().includes('await'),
130
'query should relate to async/await when specifically requested'
131
);
132
}
133
});
134
}
135
}
136
}));
137
138
stest('query parameter extraction', generateToolTestRunner({
139
question: 'find specific information about error handling patterns from https://nodejs.org/en/docs/guides/error-handling/',
140
scenarioFolderPath: '',
141
getState,
142
expectedToolCalls: ToolName.FetchWebPage,
143
tools: {
144
[ToolName.FetchWebPage]: true,
145
[ToolName.FindFiles]: true,
146
[ToolName.FindTextInFiles]: true,
147
[ToolName.ReadFile]: true,
148
[ToolName.EditFile]: true,
149
[ToolName.Codebase]: true,
150
[ToolName.ListDirectory]: true,
151
[ToolName.SearchWorkspaceSymbols]: true,
152
},
153
}, {
154
allowParallelToolCalls: false,
155
toolCallValidators: {
156
[ToolName.FetchWebPage]: async (toolCalls) => {
157
assert.strictEqual(toolCalls.length, 1, 'should make exactly one fetch webpage tool call');
158
const input = toolCalls[0].input as IFetchWebPageToolParams;
159
160
// Should have exactly 1 URL
161
assert.ok(Array.isArray(input.urls), 'urls should be an array');
162
assert.strictEqual(input.urls.length, 1, 'should have exactly 1 URL');
163
164
// Should be the exact URL from the question
165
const expectedUrl = 'https://nodejs.org/en/docs/guides/error-handling/';
166
assert.strictEqual(input.urls[0], expectedUrl, `should have the exact URL: ${expectedUrl}`);
167
168
// Should extract meaningful query when user asks for specific information
169
assert.ok(input.query !== undefined, 'should include a query when user asks for specific information');
170
assert.ok(typeof input.query === 'string', 'query should be a string');
171
assert.ok(input.query.length > 0, 'query should not be empty');
172
173
// Query should relate to error handling since that's what was requested
174
const queryLower = input.query.toLowerCase();
175
assert.ok(
176
queryLower.includes('error') || queryLower.includes('handling') || queryLower.includes('pattern'),
177
'query should relate to error handling patterns when specifically requested'
178
);
179
}
180
}
181
}));
182
183
stest('multiple URLs boundary test with 6 URLs', generateToolTestRunner({
184
question: 'gather information from these documentation sources: https://react.dev/learn/hooks-overview, https://vuejs.org/guide/essentials/reactivity-fundamentals.html, https://angular.io/guide/component-interaction, https://svelte.dev/docs/introduction, https://solid-js.com/guides/getting-started, and https://lit.dev/docs/ about component state management',
185
scenarioFolderPath: '',
186
getState,
187
expectedToolCalls: ToolName.FetchWebPage,
188
tools: {
189
[ToolName.FetchWebPage]: true,
190
[ToolName.FindFiles]: true,
191
[ToolName.FindTextInFiles]: true,
192
[ToolName.ReadFile]: true,
193
[ToolName.EditFile]: true,
194
[ToolName.Codebase]: true,
195
[ToolName.ListDirectory]: true,
196
[ToolName.SearchWorkspaceSymbols]: true,
197
},
198
}, {
199
allowParallelToolCalls: true,
200
toolCallValidators: {
201
[ToolName.FetchWebPage]: (toolCalls) => {
202
const expectedUrls = [
203
'https://react.dev/learn/hooks-overview',
204
'https://vuejs.org/guide/essentials/reactivity-fundamentals.html',
205
'https://angular.io/guide/component-interaction',
206
'https://svelte.dev/docs/introduction',
207
'https://solid-js.com/guides/getting-started',
208
'https://lit.dev/docs/'
209
];
210
211
// Allow anywhere from 1 to 6 tool calls
212
assert.ok(toolCalls.length >= 1 && toolCalls.length <= 6, `should make between 1 and 6 fetch webpage tool calls, but got ${toolCalls.length}`);
213
214
// Collect all URLs from all tool calls
215
const allUrls: string[] = [];
216
let totalUrlCount = 0;
217
218
toolCalls.forEach((toolCall, index) => {
219
const input = toolCall.input as IFetchWebPageToolParams;
220
assert.ok(Array.isArray(input.urls), `tool call ${index + 1}: urls should be an array`);
221
assert.ok(input.urls.length >= 1, `tool call ${index + 1}: should have at least 1 URL`);
222
223
totalUrlCount += input.urls.length;
224
allUrls.push(...input.urls);
225
});
226
227
// Should have exactly 6 URLs total across all tool calls
228
assert.strictEqual(totalUrlCount, 6, 'should have exactly 6 URLs total across all tool calls');
229
assert.strictEqual(allUrls.length, 6, 'collected URLs array should have exactly 6 URLs');
230
231
// Check that all expected URLs are present
232
expectedUrls.forEach(expectedUrl => {
233
assert.ok(allUrls.includes(expectedUrl), `should include the URL: ${expectedUrl}`);
234
});
235
236
// Verify no unexpected URLs
237
allUrls.forEach(url => {
238
assert.ok(expectedUrls.includes(url), `unexpected URL found: ${url}`);
239
});
240
241
// Verify no duplicate URLs
242
const uniqueUrls = new Set(allUrls);
243
assert.strictEqual(uniqueUrls.size, 6, 'should not have duplicate URLs');
244
245
// Check query parameter for any tool call that has it
246
toolCalls.forEach((toolCall, index) => {
247
const input = toolCall.input as IFetchWebPageToolParams;
248
assert.ok(input.query, `tool call ${index + 1}: query should be defined if provided`);
249
});
250
}
251
}
252
}));
253
});
254
255