@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,325 +0,0 @@
|
|
|
1
|
-
const CDP = require('chrome-remote-interface');
|
|
2
|
-
//const launchChrome = require('chrome-launcher').launch;
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { findNodeBySelector, getElementClickCoordinates } = require('../utils/browserHelpers'); // Import helpers
|
|
6
|
-
const config = require('../config');
|
|
7
|
-
|
|
8
|
-
// A private in-memory store for our browser instances within the service
|
|
9
|
-
// Each key will be a unique browserId, value will be { chromeInstance, cdpClient, userDataDir }
|
|
10
|
-
const activeBrowsers = {};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Ensures a user data directory exists.
|
|
14
|
-
* @param {string} dirPath - The absolute path to the user data directory.
|
|
15
|
-
*/
|
|
16
|
-
function ensureUserDataDir(dirPath) {
|
|
17
|
-
if (!fs.existsSync(dirPath)) {
|
|
18
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
19
|
-
console.log(`[BrowserService] Created user data directory: ${dirPath}`);
|
|
20
|
-
} else {
|
|
21
|
-
console.log(`[BrowserService] Using existing user data directory: ${dirPath}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Retrieves a browser instance by ID.
|
|
27
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
28
|
-
* @returns {object|null} - The browser instance object or null if not found.
|
|
29
|
-
*/
|
|
30
|
-
function getBrowserInstance(browserId) {
|
|
31
|
-
return activeBrowsers[browserId];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Launches a new Chrome instance.
|
|
36
|
-
* @param {boolean} headless - Whether to run Chrome in headless mode.
|
|
37
|
-
* @param {number} port - The port for remote debugging.
|
|
38
|
-
* @param {string|null} userDataDir - Path to the user data directory for persistent profiles.
|
|
39
|
-
* @returns {Promise<object>} - Object containing browserId, port, and resolvedUserDataDir.
|
|
40
|
-
*/
|
|
41
|
-
async function launchBrowser(headless, port, userDataDir) {
|
|
42
|
-
let chrome;
|
|
43
|
-
let client;
|
|
44
|
-
const { launch: launchChrome } = await import('chrome-launcher');
|
|
45
|
-
let resolvedUserDataDir = null;
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
if (userDataDir) {
|
|
49
|
-
resolvedUserDataDir = path.resolve(process.cwd(), userDataDir);
|
|
50
|
-
ensureUserDataDir(resolvedUserDataDir);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log(`[BrowserService] Launching Chrome (headless: ${headless}, userDataDir: ${resolvedUserDataDir || 'temporary'})...`);
|
|
54
|
-
|
|
55
|
-
const launchOptions = {
|
|
56
|
-
port: port,
|
|
57
|
-
userDataDir: resolvedUserDataDir, // Set userDataDir if provided
|
|
58
|
-
chromeFlags: [
|
|
59
|
-
headless ? '--headless=new' : '',
|
|
60
|
-
'--disable-gpu',
|
|
61
|
-
'--disable-setuid-sandbox',
|
|
62
|
-
'--no-sandbox'
|
|
63
|
-
].filter(Boolean)
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
chrome = await launchChrome(launchOptions);
|
|
67
|
-
|
|
68
|
-
// Generate browserId: profile-name if userDataDir is used, otherwise a unique timestamped ID
|
|
69
|
-
const browserId = userDataDir ? `profile-${path.basename(resolvedUserDataDir)}` : `browser-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
70
|
-
|
|
71
|
-
if (activeBrowsers[browserId]) {
|
|
72
|
-
console.warn(`[BrowserService] Warning: Browser ID '${browserId}' already exists. Overwriting.`);
|
|
73
|
-
// In a real scenario, you might want more sophisticated handling here,
|
|
74
|
-
// e.g., error if ID exists, or try to attach to existing.
|
|
75
|
-
// For now, we're assuming a new launch means a fresh start or overwrite.
|
|
76
|
-
try { // Attempt to clean up old instance if it exists
|
|
77
|
-
if (activeBrowsers[browserId].cdpClient) activeBrowsers[browserId].cdpClient.close();
|
|
78
|
-
if (activeBrowsers[browserId].chromeInstance) await activeBrowsers[browserId].chromeInstance.kill();
|
|
79
|
-
} catch (cleanupErr) {
|
|
80
|
-
console.error(`[BrowserService] Error cleaning up old instance for ${browserId}:`, cleanupErr.message);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
activeBrowsers[browserId] = { chromeInstance: chrome, cdpClient: null, userDataDir: resolvedUserDataDir };
|
|
85
|
-
console.log(`[BrowserService] Chrome launched on port ${chrome.port} with ID: ${browserId}`);
|
|
86
|
-
|
|
87
|
-
client = await CDP({ port: chrome.port });
|
|
88
|
-
activeBrowsers[browserId].cdpClient = client;
|
|
89
|
-
|
|
90
|
-
const { Page, Runtime, DOM, Network, Security, Input } = client; // Enable Input domain here
|
|
91
|
-
await Page.enable();
|
|
92
|
-
await Runtime.enable();
|
|
93
|
-
await DOM.enable();
|
|
94
|
-
await Network.enable();
|
|
95
|
-
await Security.enable();
|
|
96
|
-
//await Input.enable(); // Enable Input domain
|
|
97
|
-
|
|
98
|
-
console.log(`[BrowserService] CDP client connected and domains enabled for ${browserId}.`);
|
|
99
|
-
|
|
100
|
-
return { browserId, port: chrome.port, userDataDir: resolvedUserDataDir };
|
|
101
|
-
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.error(`[BrowserService] Error launching browser:`, error);
|
|
104
|
-
if (chrome && !client) { // If chrome launched but CDP connection failed
|
|
105
|
-
try {
|
|
106
|
-
await chrome.kill();
|
|
107
|
-
console.log(`[BrowserService] Partially launched Chrome instance killed due to error.`);
|
|
108
|
-
} catch (killError) {
|
|
109
|
-
console.error(`[BrowserService] Error killing partially launched Chrome:`, killError);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
throw error; // Re-throw to be caught by the route handler
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Navigates a specific browser instance to a URL.
|
|
118
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
119
|
-
* @param {string} url - The URL to navigate to.
|
|
120
|
-
* @returns {Promise<void>}
|
|
121
|
-
*/
|
|
122
|
-
async function navigateBrowser(browserId, url) {
|
|
123
|
-
const instance = getBrowserInstance(browserId);
|
|
124
|
-
if (!instance) throw new Error(`Browser instance with ID '${browserId}' not found.`);
|
|
125
|
-
|
|
126
|
-
const { cdpClient } = instance;
|
|
127
|
-
console.log(`[BrowserService] Browser ${browserId} navigating to: ${url}`);
|
|
128
|
-
await cdpClient.Page.navigate({ url: url });
|
|
129
|
-
await cdpClient.Page.loadEventFired(); // Wait for page to load
|
|
130
|
-
console.log(`[BrowserService] Browser ${browserId} successfully navigated to ${url}.`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Takes a screenshot of a specific browser page and can save it to disk.
|
|
135
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
136
|
-
* @param {string} [fileName='screenshot.png'] - Optional: The name of the file to save the screenshot as.
|
|
137
|
-
* @param {boolean} [saveToDisk=true] - Optional: Whether to save the screenshot to disk.
|
|
138
|
-
* @returns {Promise<string>} - Base64 encoded screenshot data.
|
|
139
|
-
*/
|
|
140
|
-
async function takeScreenshot(browserId, fileName = 'screenshot.png', saveToDisk = true) { // Added fileName and saveToDisk params
|
|
141
|
-
const instance = getBrowserInstance(browserId);
|
|
142
|
-
if (!instance) throw new Error(`Browser instance with ID '${browserId}' not found.`);
|
|
143
|
-
|
|
144
|
-
const { cdpClient } = instance;
|
|
145
|
-
console.log(`[BrowserService] Taking screenshot for browser ${browserId}...`);
|
|
146
|
-
const screenshot = await cdpClient.Page.captureScreenshot({ format: 'png', quality: 80 });
|
|
147
|
-
|
|
148
|
-
if (saveToDisk) {
|
|
149
|
-
const screenshotBuffer = Buffer.from(screenshot.data, 'base64');
|
|
150
|
-
// Ensure the output directory exists
|
|
151
|
-
if (!fs.existsSync(config.OUTPUT_DIR)) {
|
|
152
|
-
fs.mkdirSync(config.OUTPUT_DIR, { recursive: true });
|
|
153
|
-
}
|
|
154
|
-
const filePath = path.join(config.OUTPUT_DIR, fileName);
|
|
155
|
-
fs.writeFileSync(filePath, screenshotBuffer);
|
|
156
|
-
console.log(`[BrowserService] Screenshot saved to ${filePath}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.log(`[BrowserService] Screenshot captured for browser ${browserId}.`);
|
|
160
|
-
return screenshot.data; // Always return base64 data to the caller (e.g., AI agent)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Gets the current DOM content of a specific browser page.
|
|
165
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
166
|
-
* @returns {Promise<string>} - The outer HTML of the document.
|
|
167
|
-
*/
|
|
168
|
-
async function getDomContent(browserId) {
|
|
169
|
-
const instance = getBrowserInstance(browserId);
|
|
170
|
-
if (!instance) throw new Error(`Browser instance with ID '${browserId}' not found.`);
|
|
171
|
-
|
|
172
|
-
const { cdpClient } = instance;
|
|
173
|
-
console.log(`[BrowserService] Getting DOM for browser ${browserId}...`);
|
|
174
|
-
const documentNode = await cdpClient.DOM.getDocument({ depth: -1 });
|
|
175
|
-
const outerHTML = await cdpClient.DOM.getOuterHTML({ nodeId: documentNode.root.nodeId });
|
|
176
|
-
console.log(`[BrowserService] DOM content retrieved for browser ${browserId}.`);
|
|
177
|
-
return outerHTML.outerHTML;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Clicks an element identified by a locator.
|
|
182
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
183
|
-
* @param {object} locator - { type: 'css'|'xpath', value: 'selector' }
|
|
184
|
-
* @returns {Promise<object>} - Coordinates of the click.
|
|
185
|
-
*/
|
|
186
|
-
async function clickElement(browserId, locator) {
|
|
187
|
-
const instance = getBrowserInstance(browserId);
|
|
188
|
-
if (!instance) throw new Error(`Browser instance with ID '${browserId}' not found.`);
|
|
189
|
-
|
|
190
|
-
const { cdpClient } = instance;
|
|
191
|
-
const { Input } = cdpClient;
|
|
192
|
-
|
|
193
|
-
console.log(`[BrowserService] Browser ${browserId}: Attempting to click element with locator:`, locator);
|
|
194
|
-
|
|
195
|
-
const nodeId = await findNodeBySelector(cdpClient, locator.type, locator.value);
|
|
196
|
-
if (!nodeId) {
|
|
197
|
-
throw new Error(`Element not found for locator: ${JSON.stringify(locator)}`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const coords = await getElementClickCoordinates(cdpClient, nodeId);
|
|
201
|
-
if (!coords) {
|
|
202
|
-
throw new Error(`Could not determine click coordinates for element: ${JSON.stringify(locator)}`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
await Input.dispatchMouseEvent({
|
|
206
|
-
type: 'mousePressed',
|
|
207
|
-
button: 'left',
|
|
208
|
-
x: coords.x,
|
|
209
|
-
y: coords.y,
|
|
210
|
-
clickCount: 1
|
|
211
|
-
});
|
|
212
|
-
await Input.dispatchMouseEvent({
|
|
213
|
-
type: 'mouseReleased',
|
|
214
|
-
button: 'left',
|
|
215
|
-
x: coords.x,
|
|
216
|
-
y: coords.y,
|
|
217
|
-
clickCount: 1
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
console.log(`[BrowserService] Browser ${browserId}: Clicked element at x: ${coords.x}, y: ${coords.y}`);
|
|
221
|
-
return coords;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Types text into an element identified by a locator.
|
|
226
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
227
|
-
* @param {object} locator - { type: 'css'|'xpath', value: 'selector' }
|
|
228
|
-
* @param {string} text - The text to type.
|
|
229
|
-
* @returns {Promise<void>}
|
|
230
|
-
*/
|
|
231
|
-
async function typeIntoElement(browserId, locator, text) {
|
|
232
|
-
const instance = getBrowserInstance(browserId);
|
|
233
|
-
if (!instance) throw new Error(`Browser instance with ID '${browserId}' not found.`);
|
|
234
|
-
|
|
235
|
-
const { cdpClient } = instance;
|
|
236
|
-
const { DOM, Input } = cdpClient;
|
|
237
|
-
|
|
238
|
-
console.log(`[BrowserService] Browser ${browserId}: Attempting to type "${text}" into element with locator:`, locator);
|
|
239
|
-
|
|
240
|
-
const nodeId = await findNodeBySelector(cdpClient, locator.type, locator.value);
|
|
241
|
-
if (!nodeId) {
|
|
242
|
-
throw new Error(`Element not found for locator: ${JSON.stringify(locator)}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
await DOM.focus({ nodeId: nodeId });
|
|
246
|
-
await new Promise(resolve => setTimeout(resolve, 50)); // Small delay for focus
|
|
247
|
-
|
|
248
|
-
// Clear existing text: Cmd/Ctrl+A then Backspace
|
|
249
|
-
await Input.dispatchKeyEvent({ type: 'keyDown', text: 'a', modifiers: (process.platform === 'darwin' ? 4 : 2) }); // 4 for Meta (Cmd), 2 for Control
|
|
250
|
-
await Input.dispatchKeyEvent({ type: 'keyUp', text: 'a', modifiers: (process.platform === 'darwin' ? 4 : 2) });
|
|
251
|
-
await Input.dispatchKeyEvent({ type: 'keyDown', key: 'Backspace' });
|
|
252
|
-
await Input.dispatchKeyEvent({ type: 'keyUp', key: 'Backspace' });
|
|
253
|
-
await new Promise(resolve => setTimeout(resolve, 50)); // Small delay for clear
|
|
254
|
-
|
|
255
|
-
for (const char of text) {
|
|
256
|
-
await Input.dispatchKeyEvent({ type: 'keyDown', text: char, key: char });
|
|
257
|
-
await Input.dispatchKeyEvent({ type: 'keyUp', text: char, key: char });
|
|
258
|
-
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay for realism
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
console.log(`[BrowserService] Browser ${browserId}: Typed "${text}" into element.`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Closes a specific browser instance.
|
|
266
|
-
* @param {string} browserId - The ID of the browser instance.
|
|
267
|
-
* @returns {Promise<void>}
|
|
268
|
-
*/
|
|
269
|
-
async function closeBrowser(browserId) {
|
|
270
|
-
const instance = getBrowserInstance(browserId);
|
|
271
|
-
if (!instance) throw new Error(`Browser instance with ID '${browserId}' not found.`);
|
|
272
|
-
|
|
273
|
-
const { chromeInstance, cdpClient, userDataDir } = instance;
|
|
274
|
-
|
|
275
|
-
console.log(`[BrowserService] Closing browser ${browserId} (profile: ${userDataDir || 'temporary'})...`);
|
|
276
|
-
if (cdpClient) {
|
|
277
|
-
try {
|
|
278
|
-
cdpClient.close();
|
|
279
|
-
console.log(`[BrowserService] CDP client disconnected for ${browserId}.`);
|
|
280
|
-
} catch (err) {
|
|
281
|
-
console.warn(`[BrowserService] Error during CDP client close for ${browserId}:`, err.message);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if (chromeInstance) {
|
|
285
|
-
try {
|
|
286
|
-
await chromeInstance.kill();
|
|
287
|
-
console.log(`[BrowserService] Chrome instance ${browserId} killed.`);
|
|
288
|
-
} catch (err) {
|
|
289
|
-
console.warn(`[BrowserService] Error during Chrome instance kill for ${browserId}:`, err.message);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
delete activeBrowsers[browserId]; // Remove from our store
|
|
293
|
-
console.log(`[BrowserService] Browser ${browserId} removed from active list.`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Shuts down all active browser instances. Used for graceful server shutdown.
|
|
298
|
-
* @returns {Promise<void>}
|
|
299
|
-
*/
|
|
300
|
-
async function shutdownAllBrowsers() {
|
|
301
|
-
const browserIds = Object.keys(activeBrowsers);
|
|
302
|
-
if (browserIds.length === 0) {
|
|
303
|
-
console.log('[BrowserService] No active browsers to shut down.');
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
console.log(`[BrowserService] Shutting down ${browserIds.length} active browser(s)...`);
|
|
307
|
-
await Promise.all(browserIds.map(id => closeBrowser(id).catch(err => {
|
|
308
|
-
console.error(`[BrowserService] Failed to gracefully close browser ${id}:`, err.message);
|
|
309
|
-
// Continue with other shutdowns even if one fails
|
|
310
|
-
})));
|
|
311
|
-
console.log('[BrowserService] All active browsers shut down.');
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
module.exports = {
|
|
316
|
-
launchBrowser,
|
|
317
|
-
navigateBrowser,
|
|
318
|
-
getBrowserInstance,
|
|
319
|
-
takeScreenshot,
|
|
320
|
-
getDomContent,
|
|
321
|
-
clickElement,
|
|
322
|
-
typeIntoElement,
|
|
323
|
-
closeBrowser,
|
|
324
|
-
shutdownAllBrowsers // Export for server.js to use
|
|
325
|
-
};
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: api-planning
|
|
3
|
-
description: Analyze API schemas (OpenAPI, Swagger, GraphQL) and create comprehensive test plans with realistic sample data and optional endpoint validation. Use when user mentions API testing, test plans, test coverage, API documentation, schema analysis, REST APIs, GraphQL APIs, test scenarios, or needs to plan API test strategy.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# API Test Planning Skill
|
|
7
|
-
|
|
8
|
-
Use this skill to analyze API schemas and generate comprehensive test plans with realistic, context-aware sample data.
|
|
9
|
-
|
|
10
|
-
## When to Use This Skill
|
|
11
|
-
|
|
12
|
-
- User provides an API schema URL or file path
|
|
13
|
-
- User mentions creating test plans for APIs
|
|
14
|
-
- User needs to analyze API documentation
|
|
15
|
-
- User wants to validate API endpoints
|
|
16
|
-
- User requests test coverage analysis for REST or GraphQL APIs
|
|
17
|
-
|
|
18
|
-
## Core Workflow
|
|
19
|
-
|
|
20
|
-
### Step 1: Generate Test Plan with Realistic Samples
|
|
21
|
-
|
|
22
|
-
When user provides a schema URL or file path, use the `api_planner` tool from the democratize-quality MCP server:
|
|
23
|
-
|
|
24
|
-
```javascript
|
|
25
|
-
await tools.api_planner({
|
|
26
|
-
schemaUrl: "https://api.example.com/swagger.json",
|
|
27
|
-
// OR schemaPath: "./schema.graphql" for local files
|
|
28
|
-
apiBaseUrl: "https://api.example.com",
|
|
29
|
-
includeAuth: true,
|
|
30
|
-
includeSecurity: true,
|
|
31
|
-
includeErrorHandling: true,
|
|
32
|
-
outputPath: "./api-test-plan.md",
|
|
33
|
-
testCategories: ["functional", "security", "performance", "integration", "edge-cases"],
|
|
34
|
-
validateEndpoints: false // Set to true for live validation
|
|
35
|
-
})
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Important Workflow Rules:**
|
|
39
|
-
1. Call `api_planner` tool ONCE to generate the test plan
|
|
40
|
-
2. Review the results and explain what was generated to the user
|
|
41
|
-
3. Answer questions based on the generated output
|
|
42
|
-
4. Only call api_planner again if user explicitly requests a new/different test plan
|
|
43
|
-
|
|
44
|
-
### Step 2: Optional Endpoint Validation
|
|
45
|
-
|
|
46
|
-
When API is accessible, enable validation to verify schemas match reality:
|
|
47
|
-
|
|
48
|
-
```javascript
|
|
49
|
-
await tools.api_planner({
|
|
50
|
-
schemaUrl: "https://api.example.com/swagger.json",
|
|
51
|
-
validateEndpoints: true,
|
|
52
|
-
validationSampleSize: 3, // Default: 3, use -1 for all endpoints
|
|
53
|
-
validationTimeout: 5000 // 5 seconds per request
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Validation Features:**
|
|
58
|
-
- Real API testing with actual responses
|
|
59
|
-
- Response time metrics
|
|
60
|
-
- Success/failure indicators (✅/❌)
|
|
61
|
-
- Validation summary with success rate
|
|
62
|
-
- Graceful error handling
|
|
63
|
-
|
|
64
|
-
## What You Get
|
|
65
|
-
|
|
66
|
-
### Without Validation (Fast):
|
|
67
|
-
- Test plan with realistic sample data
|
|
68
|
-
- Context-aware field values (names, emails, dates)
|
|
69
|
-
- Ready-to-use test data
|
|
70
|
-
|
|
71
|
-
### With Validation (Comprehensive):
|
|
72
|
-
- Test plan with realistic samples
|
|
73
|
-
- Validation summary (success rate, statistics)
|
|
74
|
-
- Per-endpoint validation results
|
|
75
|
-
- Actual API responses captured
|
|
76
|
-
- Response time metrics for each endpoint
|
|
77
|
-
|
|
78
|
-
## Working with Schema Files
|
|
79
|
-
|
|
80
|
-
### GraphQL SDL Files (.graphql, .gql)
|
|
81
|
-
**ALWAYS use `schemaPath` parameter for SDL files:**
|
|
82
|
-
|
|
83
|
-
```javascript
|
|
84
|
-
await tools.api_planner({
|
|
85
|
-
schemaPath: "./schema.graphql", // Tool reads full file, converts SDL to introspection
|
|
86
|
-
outputPath: "./test-plan.md"
|
|
87
|
-
})
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
**What happens:**
|
|
91
|
-
- Tool reads full file (no truncation)
|
|
92
|
-
- Automatically converts SDL → Introspection JSON
|
|
93
|
-
- Saves `schema.json` alongside `schema.graphql`
|
|
94
|
-
- Generates test plan from introspection
|
|
95
|
-
|
|
96
|
-
### OpenAPI/Swagger Files
|
|
97
|
-
- Use `schemaPath` for local files (`.json`, `.yaml`, `.yml`)
|
|
98
|
-
- Use `schemaUrl` for remote URLs
|
|
99
|
-
|
|
100
|
-
## Realistic Sample Data Generation
|
|
101
|
-
|
|
102
|
-
The tool generates context-aware sample data automatically:
|
|
103
|
-
|
|
104
|
-
**50+ Field Patterns:**
|
|
105
|
-
- Personal info: `firstName` → "John", `email` → "john.doe@example.com"
|
|
106
|
-
- Contact: `phoneNumber` → "+1-555-0123", `city` → "New York"
|
|
107
|
-
- Business: `company` → "Acme Corp", `jobTitle` → "Engineer"
|
|
108
|
-
- Identifiers: `uuid` → valid UUID, `token` → realistic token
|
|
109
|
-
- Content: `title` → "Report Title", `description` → meaningful text
|
|
110
|
-
- Numeric: `price` → 19.99, `age` → 25, `rating` → 4.5
|
|
111
|
-
- Dates: `createdAt` → "2026-02-15T10:30:00Z"
|
|
112
|
-
|
|
113
|
-
## Advanced Scenarios
|
|
114
|
-
|
|
115
|
-
### Scenario 1: Local Schema File
|
|
116
|
-
```javascript
|
|
117
|
-
await tools.api_planner({
|
|
118
|
-
schemaPath: "./openapi.json",
|
|
119
|
-
apiBaseUrl: "https://staging-api.example.com",
|
|
120
|
-
validateEndpoints: true
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Scenario 2: GraphQL API
|
|
125
|
-
```javascript
|
|
126
|
-
await tools.api_planner({
|
|
127
|
-
schemaUrl: "https://api.example.com/graphql",
|
|
128
|
-
schemaType: "graphql",
|
|
129
|
-
testCategories: ["functional", "edge-cases"]
|
|
130
|
-
})
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Scenario 3: Multiple Related Services
|
|
134
|
-
```javascript
|
|
135
|
-
const services = [
|
|
136
|
-
{ url: "https://api1.example.com/swagger.json", name: "auth-service" },
|
|
137
|
-
{ url: "https://api2.example.com/swagger.json", name: "user-service" }
|
|
138
|
-
]
|
|
139
|
-
|
|
140
|
-
for (const service of services) {
|
|
141
|
-
await tools.api_planner({
|
|
142
|
-
schemaUrl: service.url,
|
|
143
|
-
outputPath: `./${service.name}-test-plan.md`
|
|
144
|
-
})
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Best Practices
|
|
149
|
-
|
|
150
|
-
### DO:
|
|
151
|
-
- Use api_planner once per schema
|
|
152
|
-
- Enable validation when API is accessible
|
|
153
|
-
- Use schemaPath for local files (especially GraphQL SDL)
|
|
154
|
-
- Review validation results for API issues
|
|
155
|
-
- Save plans to files using outputPath parameter
|
|
156
|
-
- Include security and edge case testing
|
|
157
|
-
|
|
158
|
-
### DON'T:
|
|
159
|
-
- Don't call api_planner multiple times without user request
|
|
160
|
-
- Don't regenerate plans just to answer questions
|
|
161
|
-
- Don't skip validation if API is accessible
|
|
162
|
-
- Don't ignore failed validations
|
|
163
|
-
- Don't validate all endpoints for large APIs (use validationSampleSize)
|
|
164
|
-
|
|
165
|
-
## Parameter Reference
|
|
166
|
-
|
|
167
|
-
**Required (one of):**
|
|
168
|
-
- `schemaUrl` - URL to fetch schema
|
|
169
|
-
- `schemaPath` - Local file path
|
|
170
|
-
|
|
171
|
-
**Common Optional:**
|
|
172
|
-
- `apiBaseUrl` - Override base URL from schema
|
|
173
|
-
- `outputPath` - Save test plan to file
|
|
174
|
-
- `includeAuth` - Include auth scenarios (default: false)
|
|
175
|
-
- `includeSecurity` - Include security scenarios (default: false)
|
|
176
|
-
- `includeErrorHandling` - Include error scenarios (default: false)
|
|
177
|
-
- `testCategories` - Array of test types
|
|
178
|
-
|
|
179
|
-
**Validation Optional:**
|
|
180
|
-
- `validateEndpoints` - Enable actual API testing (default: false)
|
|
181
|
-
- `validationSampleSize` - Number to validate (default: 3, -1 = all)
|
|
182
|
-
- `validationTimeout` - Timeout in ms (default: 5000)
|
|
183
|
-
|
|
184
|
-
## Troubleshooting
|
|
185
|
-
|
|
186
|
-
### Issue: Validation Not Running
|
|
187
|
-
- Ensure `validateEndpoints: true` is set explicitly
|
|
188
|
-
|
|
189
|
-
### Issue: All Validations Fail with 401/403
|
|
190
|
-
- API requires authentication
|
|
191
|
-
- Use api_request tool to test with proper auth headers
|
|
192
|
-
- Document auth requirements in manual review
|
|
193
|
-
|
|
194
|
-
### Issue: Validation Timeout
|
|
195
|
-
- Increase `validationTimeout` parameter (e.g., 10000 for 10s)
|
|
196
|
-
|
|
197
|
-
### Issue: Schema Parse Errors
|
|
198
|
-
- Verify schema URL is accessible
|
|
199
|
-
- Check schema format compatibility
|
|
200
|
-
- Try using `schemaPath` for local files
|
|
201
|
-
|
|
202
|
-
## Additional Validation Tools
|
|
203
|
-
|
|
204
|
-
If you need to validate specific endpoints manually:
|
|
205
|
-
|
|
206
|
-
```javascript
|
|
207
|
-
await tools.api_request({
|
|
208
|
-
sessionId: "validation-session",
|
|
209
|
-
method: "GET",
|
|
210
|
-
url: "https://api.example.com/endpoint",
|
|
211
|
-
expect: { status: 200 }
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
// Check session status
|
|
215
|
-
await tools.api_session_status({
|
|
216
|
-
sessionId: "validation-session"
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
// Generate report
|
|
220
|
-
await tools.api_session_report({
|
|
221
|
-
sessionId: "validation-session",
|
|
222
|
-
outputPath: "./validation-report.html"
|
|
223
|
-
})
|
|
224
|
-
```
|