@democratize-quality/mcp-server 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +658 -12
- package/README.md +11 -6
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +225 -0
- package/dist/server.js.map +1 -0
- package/package.json +24 -25
- package/browserControl.js +0 -113
- package/cli.js +0 -228
- package/mcpServer.js +0 -335
- package/run-server.js +0 -140
- package/src/chatmodes//360/237/214/220 api-generator.chatmode.md" +0 -409
- package/src/chatmodes//360/237/214/220 api-healer.chatmode.md" +0 -494
- package/src/chatmodes//360/237/214/220 api-planner.chatmode.md" +0 -954
- package/src/config/environments/api-only.js +0 -53
- package/src/config/environments/development.js +0 -54
- package/src/config/environments/production.js +0 -69
- package/src/config/index.js +0 -341
- package/src/config/server.js +0 -41
- package/src/config/tools/api.js +0 -67
- package/src/config/tools/browser.js +0 -90
- package/src/config/tools/default.js +0 -32
- package/src/docs/Agent_README.md +0 -310
- package/src/docs/QUICK_REFERENCE.md +0 -111
- package/src/services/browserService.js +0 -325
- package/src/skills/api-planning/SKILL.md +0 -224
- package/src/skills/test-execution/SKILL.md +0 -728
- package/src/skills/test-generation/SKILL.md +0 -309
- package/src/skills/test-healing/SKILL.md +0 -405
- package/src/tools/api/api-generator.js +0 -1865
- package/src/tools/api/api-healer.js +0 -617
- package/src/tools/api/api-planner.js +0 -2598
- package/src/tools/api/api-project-setup.js +0 -313
- package/src/tools/api/api-request.js +0 -641
- package/src/tools/api/api-session-report.js +0 -1278
- package/src/tools/api/api-session-status.js +0 -395
- package/src/tools/api/prompts/README.md +0 -293
- package/src/tools/api/prompts/generation-prompts.js +0 -703
- package/src/tools/api/prompts/healing-prompts.js +0 -195
- package/src/tools/api/prompts/index.js +0 -25
- package/src/tools/api/prompts/orchestrator.js +0 -334
- package/src/tools/api/prompts/validation-rules.js +0 -339
- package/src/tools/base/ToolBase.js +0 -230
- package/src/tools/base/ToolRegistry.js +0 -269
- package/src/tools/browser/advanced/browser-console.js +0 -384
- package/src/tools/browser/advanced/browser-dialog.js +0 -319
- package/src/tools/browser/advanced/browser-evaluate.js +0 -337
- package/src/tools/browser/advanced/browser-file.js +0 -480
- package/src/tools/browser/advanced/browser-keyboard.js +0 -343
- package/src/tools/browser/advanced/browser-mouse.js +0 -332
- package/src/tools/browser/advanced/browser-network.js +0 -421
- package/src/tools/browser/advanced/browser-pdf.js +0 -407
- package/src/tools/browser/advanced/browser-tabs.js +0 -497
- package/src/tools/browser/advanced/browser-wait.js +0 -378
- package/src/tools/browser/click.js +0 -168
- package/src/tools/browser/close.js +0 -60
- package/src/tools/browser/dom.js +0 -70
- package/src/tools/browser/launch.js +0 -67
- package/src/tools/browser/navigate.js +0 -270
- package/src/tools/browser/screenshot.js +0 -351
- package/src/tools/browser/type.js +0 -174
- package/src/tools/index.js +0 -95
- package/src/utils/agentInstaller.js +0 -418
- package/src/utils/browserHelpers.js +0 -83
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
const ToolBase = require('../base/ToolBase');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { spawn } = require('child_process');
|
|
5
|
-
|
|
6
|
-
let z;
|
|
7
|
-
try {
|
|
8
|
-
const zod = require('zod');
|
|
9
|
-
z = zod.z || zod.default?.z || zod;
|
|
10
|
-
if (!z || typeof z.object !== 'function') {
|
|
11
|
-
throw new Error('Zod not properly loaded');
|
|
12
|
-
}
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error('Failed to load Zod:', error.message);
|
|
15
|
-
// Fallback: create a simple validation function
|
|
16
|
-
z = {
|
|
17
|
-
object: (schema) => ({ parse: (data) => data }),
|
|
18
|
-
string: () => ({ optional: () => ({}) }),
|
|
19
|
-
enum: () => ({ optional: () => ({}) }),
|
|
20
|
-
record: () => ({ optional: () => ({}) }),
|
|
21
|
-
any: () => ({ optional: () => ({}) }),
|
|
22
|
-
number: () => ({ optional: () => ({}) }),
|
|
23
|
-
array: () => ({ optional: () => ({}) }),
|
|
24
|
-
boolean: () => ({ optional: () => ({}) })
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Input schema for the API healer tool
|
|
29
|
-
const apiHealerInputSchema = z.object({
|
|
30
|
-
testPath: z.string().optional(),
|
|
31
|
-
testFiles: z.array(z.string()).optional(),
|
|
32
|
-
testType: z.enum(['jest', 'playwright', 'postman', 'auto']).optional(),
|
|
33
|
-
sessionId: z.string().optional(),
|
|
34
|
-
maxHealingAttempts: z.number().optional(),
|
|
35
|
-
autoFix: z.boolean().optional(),
|
|
36
|
-
backupOriginal: z.boolean().optional(),
|
|
37
|
-
analysisOnly: z.boolean().optional(),
|
|
38
|
-
healingStrategies: z.array(z.enum(['schema-update', 'endpoint-fix', 'auth-repair', 'data-correction', 'assertion-update'])).optional()
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* API Healer Tool - Debug and fix failing API tests
|
|
43
|
-
*/
|
|
44
|
-
class ApiHealerTool extends ToolBase {
|
|
45
|
-
static definition = {
|
|
46
|
-
name: "api_healer",
|
|
47
|
-
description: "Debug and automatically fix failing API tests by analyzing errors, updating assertions, fixing endpoints, and correcting test data.",
|
|
48
|
-
input_schema: {
|
|
49
|
-
type: "object",
|
|
50
|
-
properties: {
|
|
51
|
-
testPath: {
|
|
52
|
-
type: "string",
|
|
53
|
-
description: "Path to a specific test file to heal"
|
|
54
|
-
},
|
|
55
|
-
testFiles: {
|
|
56
|
-
type: "array",
|
|
57
|
-
items: { type: "string" },
|
|
58
|
-
description: "Array of test file paths to heal (alternative to testPath)"
|
|
59
|
-
},
|
|
60
|
-
testType: {
|
|
61
|
-
type: "string",
|
|
62
|
-
enum: ["jest", "playwright", "postman", "auto"],
|
|
63
|
-
description: "Type of tests to heal - auto-detects if not specified"
|
|
64
|
-
},
|
|
65
|
-
sessionId: {
|
|
66
|
-
type: "string",
|
|
67
|
-
description: "Session ID for tracking healing process"
|
|
68
|
-
},
|
|
69
|
-
maxHealingAttempts: {
|
|
70
|
-
type: "number",
|
|
71
|
-
description: "Maximum number of healing attempts per test (default: 3)"
|
|
72
|
-
},
|
|
73
|
-
autoFix: {
|
|
74
|
-
type: "boolean",
|
|
75
|
-
description: "Automatically apply fixes without confirmation (default: true)"
|
|
76
|
-
},
|
|
77
|
-
backupOriginal: {
|
|
78
|
-
type: "boolean",
|
|
79
|
-
description: "Create backup of original test files (default: true)"
|
|
80
|
-
},
|
|
81
|
-
analysisOnly: {
|
|
82
|
-
type: "boolean",
|
|
83
|
-
description: "Only analyze failures without applying fixes (default: false)"
|
|
84
|
-
},
|
|
85
|
-
healingStrategies: {
|
|
86
|
-
type: "array",
|
|
87
|
-
items: {
|
|
88
|
-
type: "string",
|
|
89
|
-
enum: ["schema-update", "endpoint-fix", "auth-repair", "data-correction", "assertion-update"]
|
|
90
|
-
},
|
|
91
|
-
description: "Specific healing strategies to apply (default: all strategies)"
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
oneOf: [
|
|
95
|
-
{ required: ["testPath"] },
|
|
96
|
-
{ required: ["testFiles"] }
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
constructor() {
|
|
102
|
-
super();
|
|
103
|
-
this.apiRequestTool = null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async execute(parameters) {
|
|
107
|
-
try {
|
|
108
|
-
const params = apiHealerInputSchema.parse(parameters);
|
|
109
|
-
|
|
110
|
-
// Set defaults
|
|
111
|
-
const options = {
|
|
112
|
-
testType: params.testType || 'auto',
|
|
113
|
-
sessionId: params.sessionId || `api-heal-${Date.now()}`,
|
|
114
|
-
maxHealingAttempts: params.maxHealingAttempts || 3,
|
|
115
|
-
autoFix: params.autoFix !== false,
|
|
116
|
-
backupOriginal: params.backupOriginal !== false,
|
|
117
|
-
analysisOnly: params.analysisOnly || false,
|
|
118
|
-
healingStrategies: params.healingStrategies || ['schema-update', 'endpoint-fix', 'auth-repair', 'data-correction', 'assertion-update']
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Get test files to heal
|
|
122
|
-
let testFiles = [];
|
|
123
|
-
if (params.testPath) {
|
|
124
|
-
testFiles = [params.testPath];
|
|
125
|
-
} else if (params.testFiles) {
|
|
126
|
-
testFiles = params.testFiles;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Validate test files exist
|
|
130
|
-
for (const testFile of testFiles) {
|
|
131
|
-
if (!fs.existsSync(testFile)) {
|
|
132
|
-
throw new Error(`Test file not found: ${testFile}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Auto-detect test type if needed
|
|
137
|
-
if (options.testType === 'auto') {
|
|
138
|
-
options.testType = this._detectTestType(testFiles[0]);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const healingResults = {
|
|
142
|
-
sessionId: options.sessionId,
|
|
143
|
-
totalFiles: testFiles.length,
|
|
144
|
-
processedFiles: 0,
|
|
145
|
-
fixedFiles: 0,
|
|
146
|
-
failedFiles: 0,
|
|
147
|
-
results: []
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Process each test file
|
|
151
|
-
for (const testFile of testFiles) {
|
|
152
|
-
console.log(`[API Healer] Processing: ${testFile}`);
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const fileResult = await this._healTestFile(testFile, options);
|
|
156
|
-
healingResults.results.push(fileResult);
|
|
157
|
-
healingResults.processedFiles++;
|
|
158
|
-
|
|
159
|
-
if (fileResult.success) {
|
|
160
|
-
healingResults.fixedFiles++;
|
|
161
|
-
} else {
|
|
162
|
-
healingResults.failedFiles++;
|
|
163
|
-
}
|
|
164
|
-
} catch (error) {
|
|
165
|
-
healingResults.results.push({
|
|
166
|
-
file: testFile,
|
|
167
|
-
success: false,
|
|
168
|
-
error: error.message,
|
|
169
|
-
healingAttempts: 0,
|
|
170
|
-
appliedFixes: []
|
|
171
|
-
});
|
|
172
|
-
healingResults.failedFiles++;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
success: true,
|
|
178
|
-
message: `API healing completed for ${healingResults.processedFiles} files`,
|
|
179
|
-
...healingResults
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
} catch (error) {
|
|
183
|
-
return {
|
|
184
|
-
success: false,
|
|
185
|
-
error: error.message,
|
|
186
|
-
details: error.stack
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
_detectTestType(testFile) {
|
|
192
|
-
const content = fs.readFileSync(testFile, 'utf8');
|
|
193
|
-
|
|
194
|
-
if (content.includes('@playwright/test') || content.includes('test.describe')) {
|
|
195
|
-
return 'playwright';
|
|
196
|
-
} else if (content.includes('describe(') && content.includes('jest')) {
|
|
197
|
-
return 'jest';
|
|
198
|
-
} else if (testFile.endsWith('.postman_collection.json')) {
|
|
199
|
-
return 'postman';
|
|
200
|
-
} else if (content.includes('describe(') || content.includes('test(')) {
|
|
201
|
-
return 'jest'; // Default to Jest for generic test files
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
throw new Error(`Unable to detect test type for file: ${testFile}`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async _healTestFile(testFile, options) {
|
|
208
|
-
const result = {
|
|
209
|
-
file: testFile,
|
|
210
|
-
success: false,
|
|
211
|
-
healingAttempts: 0,
|
|
212
|
-
appliedFixes: [],
|
|
213
|
-
errors: [],
|
|
214
|
-
finalStatus: 'failed'
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Create backup if requested
|
|
218
|
-
if (options.backupOriginal) {
|
|
219
|
-
const backupPath = `${testFile}.backup.${Date.now()}`;
|
|
220
|
-
fs.copyFileSync(testFile, backupPath);
|
|
221
|
-
result.backupPath = backupPath;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Initial test run to identify failures
|
|
225
|
-
let testErrors = await this._runTests(testFile, options.testType);
|
|
226
|
-
result.errors = testErrors;
|
|
227
|
-
|
|
228
|
-
if (testErrors.length === 0) {
|
|
229
|
-
result.success = true;
|
|
230
|
-
result.finalStatus = 'passing';
|
|
231
|
-
return result;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (options.analysisOnly) {
|
|
235
|
-
result.analysis = await this._analyzeErrors(testErrors, testFile);
|
|
236
|
-
return result;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Healing loop
|
|
240
|
-
for (let attempt = 1; attempt <= options.maxHealingAttempts && testErrors.length > 0; attempt++) {
|
|
241
|
-
result.healingAttempts = attempt;
|
|
242
|
-
console.log(`[API Healer] Healing attempt ${attempt}/${options.maxHealingAttempts} for ${testFile}`);
|
|
243
|
-
|
|
244
|
-
const fixes = await this._generateFixes(testErrors, testFile, options);
|
|
245
|
-
|
|
246
|
-
if (fixes.length === 0) {
|
|
247
|
-
console.log('[API Healer] No fixes available for current errors');
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Apply fixes
|
|
252
|
-
for (const fix of fixes) {
|
|
253
|
-
try {
|
|
254
|
-
await this._applyFix(fix, testFile);
|
|
255
|
-
result.appliedFixes.push(fix);
|
|
256
|
-
console.log(`[API Healer] Applied fix: ${fix.type} - ${fix.description}`);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
console.log(`[API Healer] Failed to apply fix: ${fix.type} - ${error.message}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Re-run tests to check if issues are resolved
|
|
263
|
-
testErrors = await this._runTests(testFile, options.testType);
|
|
264
|
-
|
|
265
|
-
if (testErrors.length === 0) {
|
|
266
|
-
result.success = true;
|
|
267
|
-
result.finalStatus = 'healed';
|
|
268
|
-
break;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (testErrors.length > 0) {
|
|
273
|
-
result.finalStatus = 'partially-healed';
|
|
274
|
-
result.remainingErrors = testErrors;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async _runTests(testFile, testType) {
|
|
281
|
-
return new Promise((resolve) => {
|
|
282
|
-
let command, args;
|
|
283
|
-
|
|
284
|
-
switch (testType) {
|
|
285
|
-
case 'jest':
|
|
286
|
-
command = 'npx';
|
|
287
|
-
args = ['jest', testFile, '--verbose', '--no-coverage'];
|
|
288
|
-
break;
|
|
289
|
-
case 'playwright':
|
|
290
|
-
command = 'npx';
|
|
291
|
-
args = ['playwright', 'test', testFile];
|
|
292
|
-
break;
|
|
293
|
-
default:
|
|
294
|
-
resolve([{ type: 'unknown', message: `Unsupported test type: ${testType}` }]);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const process = spawn(command, args, { cwd: path.dirname(testFile) });
|
|
299
|
-
let output = '';
|
|
300
|
-
let errorOutput = '';
|
|
301
|
-
|
|
302
|
-
process.stdout.on('data', (data) => {
|
|
303
|
-
output += data.toString();
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
process.stderr.on('data', (data) => {
|
|
307
|
-
errorOutput += data.toString();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
process.on('close', (code) => {
|
|
311
|
-
const errors = this._parseTestErrors(output + errorOutput, testType);
|
|
312
|
-
resolve(errors);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
process.on('error', (error) => {
|
|
316
|
-
resolve([{ type: 'execution', message: `Failed to run tests: ${error.message}` }]);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
_parseTestErrors(output, testType) {
|
|
322
|
-
const errors = [];
|
|
323
|
-
|
|
324
|
-
switch (testType) {
|
|
325
|
-
case 'jest':
|
|
326
|
-
errors.push(...this._parseJestErrors(output));
|
|
327
|
-
break;
|
|
328
|
-
case 'playwright':
|
|
329
|
-
errors.push(...this._parsePlaywrightErrors(output));
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return errors;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
_parseJestErrors(output) {
|
|
337
|
-
const errors = [];
|
|
338
|
-
const lines = output.split('\n');
|
|
339
|
-
|
|
340
|
-
for (let i = 0; i < lines.length; i++) {
|
|
341
|
-
const line = lines[i];
|
|
342
|
-
|
|
343
|
-
// HTTP errors
|
|
344
|
-
if (line.includes('Request failed with status code')) {
|
|
345
|
-
const statusMatch = line.match(/status code (\d+)/);
|
|
346
|
-
if (statusMatch) {
|
|
347
|
-
errors.push({
|
|
348
|
-
type: 'http-error',
|
|
349
|
-
statusCode: parseInt(statusMatch[1]),
|
|
350
|
-
message: line.trim(),
|
|
351
|
-
line: i + 1
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Assertion errors
|
|
357
|
-
else if (line.includes('expect(') && line.includes('toBe')) {
|
|
358
|
-
errors.push({
|
|
359
|
-
type: 'assertion-error',
|
|
360
|
-
message: line.trim(),
|
|
361
|
-
line: i + 1
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Network errors
|
|
366
|
-
else if (line.includes('ECONNREFUSED') || line.includes('ENOTFOUND')) {
|
|
367
|
-
errors.push({
|
|
368
|
-
type: 'network-error',
|
|
369
|
-
message: line.trim(),
|
|
370
|
-
line: i + 1
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Authentication errors
|
|
375
|
-
else if (line.includes('401') || line.includes('403') || line.includes('Unauthorized')) {
|
|
376
|
-
errors.push({
|
|
377
|
-
type: 'auth-error',
|
|
378
|
-
message: line.trim(),
|
|
379
|
-
line: i + 1
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Timeout errors
|
|
384
|
-
else if (line.includes('timeout') || line.includes('ETIMEDOUT')) {
|
|
385
|
-
errors.push({
|
|
386
|
-
type: 'timeout-error',
|
|
387
|
-
message: line.trim(),
|
|
388
|
-
line: i + 1
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return errors;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
_parsePlaywrightErrors(output) {
|
|
397
|
-
const errors = [];
|
|
398
|
-
const lines = output.split('\n');
|
|
399
|
-
|
|
400
|
-
for (let i = 0; i < lines.length; i++) {
|
|
401
|
-
const line = lines[i];
|
|
402
|
-
|
|
403
|
-
// API response errors
|
|
404
|
-
if (line.includes('apiRequestContext.get') || line.includes('apiRequestContext.post')) {
|
|
405
|
-
if (lines[i + 1] && lines[i + 1].includes('Error:')) {
|
|
406
|
-
errors.push({
|
|
407
|
-
type: 'api-error',
|
|
408
|
-
message: lines[i + 1].trim(),
|
|
409
|
-
line: i + 1
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Assertion errors
|
|
415
|
-
else if (line.includes('expect(') && line.includes('toBe')) {
|
|
416
|
-
errors.push({
|
|
417
|
-
type: 'assertion-error',
|
|
418
|
-
message: line.trim(),
|
|
419
|
-
line: i + 1
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return errors;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async _analyzeErrors(errors, testFile) {
|
|
428
|
-
const analysis = {
|
|
429
|
-
totalErrors: errors.length,
|
|
430
|
-
errorTypes: {},
|
|
431
|
-
recommendations: [],
|
|
432
|
-
healable: true
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// Categorize errors
|
|
436
|
-
errors.forEach(error => {
|
|
437
|
-
if (!analysis.errorTypes[error.type]) {
|
|
438
|
-
analysis.errorTypes[error.type] = 0;
|
|
439
|
-
}
|
|
440
|
-
analysis.errorTypes[error.type]++;
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Generate recommendations
|
|
444
|
-
Object.keys(analysis.errorTypes).forEach(errorType => {
|
|
445
|
-
switch (errorType) {
|
|
446
|
-
case 'http-error':
|
|
447
|
-
analysis.recommendations.push('Check API endpoint URLs and HTTP methods');
|
|
448
|
-
break;
|
|
449
|
-
case 'assertion-error':
|
|
450
|
-
analysis.recommendations.push('Update test assertions to match current API responses');
|
|
451
|
-
break;
|
|
452
|
-
case 'auth-error':
|
|
453
|
-
analysis.recommendations.push('Update authentication tokens or credentials');
|
|
454
|
-
break;
|
|
455
|
-
case 'network-error':
|
|
456
|
-
analysis.recommendations.push('Verify API server is running and accessible');
|
|
457
|
-
analysis.healable = false;
|
|
458
|
-
break;
|
|
459
|
-
case 'timeout-error':
|
|
460
|
-
analysis.recommendations.push('Increase timeout values or check API performance');
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
return analysis;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
async _generateFixes(errors, testFile, options) {
|
|
469
|
-
const fixes = [];
|
|
470
|
-
const testContent = fs.readFileSync(testFile, 'utf8');
|
|
471
|
-
|
|
472
|
-
for (const error of errors) {
|
|
473
|
-
switch (error.type) {
|
|
474
|
-
case 'http-error':
|
|
475
|
-
if (error.statusCode === 404) {
|
|
476
|
-
fixes.push(await this._generateEndpointFix(error, testContent));
|
|
477
|
-
} else if (error.statusCode === 401 || error.statusCode === 403) {
|
|
478
|
-
fixes.push(await this._generateAuthFix(error, testContent));
|
|
479
|
-
} else if (error.statusCode >= 400 && error.statusCode < 500) {
|
|
480
|
-
fixes.push(await this._generateDataFix(error, testContent));
|
|
481
|
-
}
|
|
482
|
-
break;
|
|
483
|
-
|
|
484
|
-
case 'assertion-error':
|
|
485
|
-
fixes.push(await this._generateAssertionFix(error, testContent));
|
|
486
|
-
break;
|
|
487
|
-
|
|
488
|
-
case 'auth-error':
|
|
489
|
-
fixes.push(await this._generateAuthFix(error, testContent));
|
|
490
|
-
break;
|
|
491
|
-
|
|
492
|
-
case 'timeout-error':
|
|
493
|
-
fixes.push(this._generateTimeoutFix(error, testContent));
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return fixes.filter(fix => fix !== null);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async _generateEndpointFix(error, testContent) {
|
|
502
|
-
// Try to identify the failing endpoint and suggest alternatives
|
|
503
|
-
const urlMatches = testContent.match(/['"`]([^'"`]*\/[^'"`]*)['"`]/g);
|
|
504
|
-
|
|
505
|
-
if (urlMatches && urlMatches.length > 0) {
|
|
506
|
-
// For now, return a placeholder fix
|
|
507
|
-
return {
|
|
508
|
-
type: 'endpoint-fix',
|
|
509
|
-
description: 'Update endpoint URL to match current API',
|
|
510
|
-
pattern: urlMatches[0],
|
|
511
|
-
replacement: urlMatches[0], // Would implement actual endpoint discovery
|
|
512
|
-
confidence: 0.5
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
async _generateAuthFix(error, testContent) {
|
|
520
|
-
return {
|
|
521
|
-
type: 'auth-repair',
|
|
522
|
-
description: 'Update authentication method or refresh tokens',
|
|
523
|
-
pattern: /Authorization['":\s]+['"`]([^'"`]*)['"`]/,
|
|
524
|
-
replacement: 'Authorization: "Bearer {{updated_token}}"',
|
|
525
|
-
confidence: 0.8
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
async _generateDataFix(error, testContent) {
|
|
530
|
-
return {
|
|
531
|
-
type: 'data-correction',
|
|
532
|
-
description: 'Update request payload to match API schema',
|
|
533
|
-
pattern: /data:\s*{[^}]*}/,
|
|
534
|
-
replacement: 'data: { /* Updated payload */ }',
|
|
535
|
-
confidence: 0.6
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async _generateAssertionFix(error, testContent) {
|
|
540
|
-
// Extract expected vs actual values from error message
|
|
541
|
-
const expectMatch = error.message.match(/expect\(([^)]+)\)\.toBe\(([^)]+)\)/);
|
|
542
|
-
|
|
543
|
-
if (expectMatch) {
|
|
544
|
-
return {
|
|
545
|
-
type: 'assertion-update',
|
|
546
|
-
description: 'Update assertion to match actual API response',
|
|
547
|
-
pattern: expectMatch[0],
|
|
548
|
-
replacement: `expect(${expectMatch[1]}).toBe(/* actual value */)`,
|
|
549
|
-
confidence: 0.9
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
_generateTimeoutFix(error, testContent) {
|
|
557
|
-
return {
|
|
558
|
-
type: 'timeout-fix',
|
|
559
|
-
description: 'Increase timeout values',
|
|
560
|
-
pattern: /timeout:\s*\d+/,
|
|
561
|
-
replacement: 'timeout: 30000',
|
|
562
|
-
confidence: 0.7
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
async _applyFix(fix, testFile) {
|
|
567
|
-
if (!fix || !fix.pattern || !fix.replacement) {
|
|
568
|
-
throw new Error('Invalid fix object');
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
let content = fs.readFileSync(testFile, 'utf8');
|
|
572
|
-
|
|
573
|
-
if (typeof fix.pattern === 'string') {
|
|
574
|
-
content = content.replace(fix.pattern, fix.replacement);
|
|
575
|
-
} else if (fix.pattern instanceof RegExp) {
|
|
576
|
-
content = content.replace(fix.pattern, fix.replacement);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
fs.writeFileSync(testFile, content);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Initialize API request tool for live API testing
|
|
583
|
-
async _initializeApiTool() {
|
|
584
|
-
if (!this.apiRequestTool) {
|
|
585
|
-
const ApiRequestTool = require('./api-request');
|
|
586
|
-
this.apiRequestTool = new ApiRequestTool();
|
|
587
|
-
}
|
|
588
|
-
return this.apiRequestTool;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Test API endpoint to verify if it exists and returns expected data
|
|
592
|
-
async _testApiEndpoint(method, url, headers = {}, data = null) {
|
|
593
|
-
try {
|
|
594
|
-
const apiTool = await this._initializeApiTool();
|
|
595
|
-
const result = await apiTool.execute({
|
|
596
|
-
method: method,
|
|
597
|
-
url: url,
|
|
598
|
-
headers: headers,
|
|
599
|
-
data: data
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
return {
|
|
603
|
-
success: result.success,
|
|
604
|
-
statusCode: result.statusCode,
|
|
605
|
-
data: result.data,
|
|
606
|
-
headers: result.headers
|
|
607
|
-
};
|
|
608
|
-
} catch (error) {
|
|
609
|
-
return {
|
|
610
|
-
success: false,
|
|
611
|
-
error: error.message
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
module.exports = ApiHealerTool;
|