@askqa/mcp 1.2.5 → 1.2.6
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/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/server.js +55 -64
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -84,6 +84,17 @@ async function pollTestRun(testRunId, maxWaitMs = 300000) {
|
|
|
84
84
|
throw new Error(`Test run ${testRunId} did not finish within ${maxWaitMs / 1000}s`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
async function pollJob(jobId, maxWaitMs = 300000) {
|
|
88
|
+
const start = Date.now();
|
|
89
|
+
while (Date.now() - start < maxWaitMs) {
|
|
90
|
+
const job = await apiGet(`/api/jobs/${jobId}`);
|
|
91
|
+
if (job.status === "done") return job.result;
|
|
92
|
+
if (job.status === "failed") throw new Error(job.error || "Job failed");
|
|
93
|
+
await sleep(2000);
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`Job ${jobId} did not finish within ${maxWaitMs / 1000}s`);
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
async function fetchScreenshot(url) {
|
|
88
99
|
try {
|
|
89
100
|
const res = await fetch(url, {
|
|
@@ -176,16 +187,13 @@ const server = new McpServer(
|
|
|
176
187
|
"If the latest run passed, confirm it's working. If it failed, report what failed.",
|
|
177
188
|
"Only call run_test if the user explicitly asks to run a new test — checking status should use existing results.",
|
|
178
189
|
"",
|
|
179
|
-
"When the user asks to monitor a site
|
|
180
|
-
"
|
|
181
|
-
"",
|
|
182
|
-
"
|
|
183
|
-
" Just call create_test with template_id and you're done.",
|
|
190
|
+
"When the user asks to monitor a site or set up tests, use detect_tests first.",
|
|
191
|
+
"detect_tests screenshots the site and asks AI to suggest 2-3 meaningful e2e tests.",
|
|
192
|
+
"The user then picks which tests to add. You write the code, validate with validate_test,",
|
|
193
|
+
"iterate until all steps pass, then save with create_test.",
|
|
184
194
|
"",
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
" then call create_test with that generated code (not the template_id).",
|
|
188
|
-
" Templates that support detection have supportsDetection: true in list_templates output.",
|
|
195
|
+
"For universal templates (e.g. 'quick-checks') that work on any site with no config,",
|
|
196
|
+
"you can call create_test with template_id directly without detect_tests.",
|
|
189
197
|
].join("\n"),
|
|
190
198
|
}
|
|
191
199
|
);
|
|
@@ -193,7 +201,7 @@ const server = new McpServer(
|
|
|
193
201
|
server.registerTool(
|
|
194
202
|
"list_templates",
|
|
195
203
|
{
|
|
196
|
-
description: "List available test templates
|
|
204
|
+
description: "List available universal test templates (e.g. 'quick-checks') that work on any site with no configuration. For custom site-specific tests, use detect_tests instead.",
|
|
197
205
|
readOnlyHint: true,
|
|
198
206
|
},
|
|
199
207
|
async () => {
|
|
@@ -205,12 +213,7 @@ server.registerTool(
|
|
|
205
213
|
lines.push(` Name: ${t.name}`);
|
|
206
214
|
lines.push(` Description: ${t.description}`);
|
|
207
215
|
lines.push(` Steps: ${t.steps.join(", ")}`);
|
|
208
|
-
|
|
209
|
-
lines.push(` supportsDetection: true`);
|
|
210
|
-
lines.push(` Usage: call detect_template → get generated code → create_test with code`);
|
|
211
|
-
} else {
|
|
212
|
-
lines.push(` Usage: call create_test with template_id="${t.id}" directly`);
|
|
213
|
-
}
|
|
216
|
+
lines.push(` Usage: call create_test with template_id="${t.id}" directly`);
|
|
214
217
|
lines.push("");
|
|
215
218
|
}
|
|
216
219
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
@@ -221,66 +224,53 @@ server.registerTool(
|
|
|
221
224
|
);
|
|
222
225
|
|
|
223
226
|
server.registerTool(
|
|
224
|
-
"
|
|
227
|
+
"detect_tests",
|
|
225
228
|
{
|
|
226
|
-
description: "
|
|
229
|
+
description: "Start here when a user wants to monitor a new site. Screenshots the URL and uses AI to suggest 2-3 meaningful e2e tests tailored to that specific site (e.g. checkout flow, login, add to cart). Returns a page_summary and a list of suggestions with names, descriptions, and step sketches. After calling this: present the suggestions to the user, let them pick which ones to add, then YOU write the Playwright test code for each chosen test, validate with validate_test, iterate until it passes, then save with create_test.",
|
|
227
230
|
readOnlyHint: true,
|
|
228
231
|
inputSchema: {
|
|
229
|
-
|
|
230
|
-
url: z.string().describe("The target URL to detect against (e.g. 'https://my-store.myshopify.com')"),
|
|
232
|
+
url: z.string().describe("The website URL to analyze (e.g. 'https://my-store.com')"),
|
|
231
233
|
},
|
|
232
234
|
},
|
|
233
|
-
async ({
|
|
235
|
+
async ({ url }) => {
|
|
234
236
|
try {
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
+
const { job_id } = await apiPost("/api/tests/suggest", { url });
|
|
238
|
+
const result = await pollJob(job_id);
|
|
237
239
|
const lines = [];
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
lines.push("");
|
|
242
|
-
|
|
243
|
-
if (result.steps) {
|
|
244
|
-
lines.push("Steps:");
|
|
245
|
-
for (const step of result.steps) {
|
|
246
|
-
const icon = step.status === "passed" ? "+" : "x";
|
|
247
|
-
lines.push(` ${icon} ${step.name} — ${step.status}`);
|
|
248
|
-
if (step.error) lines.push(` Error: ${step.error}`);
|
|
249
|
-
}
|
|
241
|
+
if (result.page_summary) {
|
|
242
|
+
lines.push(`Site: ${result.page_summary}`);
|
|
250
243
|
lines.push("");
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
246
|
+
const suggestions = result.suggestions || [];
|
|
247
|
+
if (suggestions.length === 0) {
|
|
248
|
+
lines.push("No suggestions available. You can still write a custom test using screenshot_url to inspect the page.");
|
|
249
|
+
} else {
|
|
250
|
+
lines.push(`Suggested tests (${suggestions.length}):`);
|
|
258
251
|
lines.push("");
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// Fetch screenshots for each step
|
|
271
|
-
if (result.steps) {
|
|
272
|
-
for (const step of result.steps) {
|
|
273
|
-
if (!step.screenshot) continue;
|
|
274
|
-
const screenshotUrl = `${API_URL}/api/screenshots/${result.executionId}/${step.screenshot}`;
|
|
275
|
-
const base64 = await fetchScreenshot(screenshotUrl);
|
|
276
|
-
if (base64) {
|
|
277
|
-
content.push({ type: "text", text: `Screenshot: ${step.name}` });
|
|
278
|
-
content.push({ type: "image", data: base64, mimeType: "image/png" });
|
|
252
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
253
|
+
const s = suggestions[i];
|
|
254
|
+
lines.push(`${i + 1}. ${s.name}`);
|
|
255
|
+
lines.push(` What it tests: ${s.description}`);
|
|
256
|
+
lines.push(` Why it matters: ${s.why}`);
|
|
257
|
+
if (s.steps && s.steps.length > 0) {
|
|
258
|
+
lines.push(` Steps:`);
|
|
259
|
+
for (const step of s.steps) {
|
|
260
|
+
lines.push(` - ${step}`);
|
|
261
|
+
}
|
|
279
262
|
}
|
|
263
|
+
lines.push("");
|
|
280
264
|
}
|
|
265
|
+
lines.push("Next steps:");
|
|
266
|
+
lines.push("1. Ask the user which test(s) they want to add");
|
|
267
|
+
lines.push("2. Use screenshot_url to inspect the page and discover selectors");
|
|
268
|
+
lines.push("3. Write the Playwright test code");
|
|
269
|
+
lines.push("4. Validate with validate_test — iterate until ALL steps pass");
|
|
270
|
+
lines.push("5. Save with create_test");
|
|
281
271
|
}
|
|
282
272
|
|
|
283
|
-
return { content };
|
|
273
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
284
274
|
} catch (err) {
|
|
285
275
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
286
276
|
}
|
|
@@ -290,7 +280,7 @@ server.registerTool(
|
|
|
290
280
|
server.registerTool(
|
|
291
281
|
"create_test",
|
|
292
282
|
{
|
|
293
|
-
description: "
|
|
283
|
+
description: "Save a validated test. IMPORTANT: For code-based tests, only call this AFTER validate_test confirms the test passes — never save untested code. Use template_id for universal templates (e.g. 'quick-checks') that work on any site without validation. Use code for custom tests after running detect_tests → write code → validate_test. Provide template_id or code, not both.",
|
|
294
284
|
destructiveHint: true,
|
|
295
285
|
inputSchema: {
|
|
296
286
|
name: z.string().describe("A name for this test (e.g. 'Homepage health check')"),
|
|
@@ -331,7 +321,8 @@ server.registerTool(
|
|
|
331
321
|
},
|
|
332
322
|
async ({ url }) => {
|
|
333
323
|
try {
|
|
334
|
-
const
|
|
324
|
+
const { job_id } = await apiPost("/api/tests/screenshot", { url });
|
|
325
|
+
const result = await pollJob(job_id);
|
|
335
326
|
const content = [];
|
|
336
327
|
|
|
337
328
|
// Page info as structured text
|
|
@@ -384,7 +375,7 @@ server.registerTool(
|
|
|
384
375
|
server.registerTool(
|
|
385
376
|
"validate_test",
|
|
386
377
|
{
|
|
387
|
-
description: "
|
|
378
|
+
description: "REQUIRED before create_test for any code-based test. Dry-runs Playwright code against a URL without saving it — returns step results, screenshots, and page structure. Steps continue even on failure for maximum debug signal. Iterate here until ALL steps pass, then call create_test to save it.",
|
|
388
379
|
readOnlyHint: true,
|
|
389
380
|
inputSchema: {
|
|
390
381
|
code: z.string().describe("Custom Playwright test code. Must define an async function test({ page, step, log })."),
|
|
@@ -1120,7 +1111,7 @@ server.registerTool(
|
|
|
1120
1111
|
}
|
|
1121
1112
|
|
|
1122
1113
|
if (run.test_code) {
|
|
1123
|
-
lines.push("Test code (
|
|
1114
|
+
lines.push("Test code (validate with validate_test first, then create_test to save):");
|
|
1124
1115
|
lines.push("```javascript");
|
|
1125
1116
|
lines.push(run.test_code);
|
|
1126
1117
|
lines.push("```");
|