@askqa/mcp 1.2.8 → 1.2.9
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 +26 -5
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -291,9 +291,10 @@ server.registerTool(
|
|
|
291
291
|
secrets: z.record(z.string()).optional().describe("Optional key-value secrets (e.g. { email: '...', password: '...' } or { api_key: '...' }). Encrypted at rest, never returned in API responses."),
|
|
292
292
|
headers: z.record(z.string()).optional().describe("Optional HTTP headers injected into requests to the target domain (e.g. { 'X-Test-Secret': 'abc' })"),
|
|
293
293
|
enable_test_mode: z.boolean().optional().describe("Send X-AskQA-Secret header to the target site, enabling test mode on sites that support it (default: true)"),
|
|
294
|
+
test_timeout: z.coerce.number().optional().describe("Maximum seconds the scheduler is allowed to run this test (default: 120, max: 300). Set this when the test has many page navigations and validate_test needed a timeout > 120 to pass."),
|
|
294
295
|
},
|
|
295
296
|
},
|
|
296
|
-
async ({ name, url, template_id, params, code, secrets, headers, enable_test_mode }) => {
|
|
297
|
+
async ({ name, url, template_id, params, code, secrets, headers, enable_test_mode, test_timeout }) => {
|
|
297
298
|
try {
|
|
298
299
|
const body = { name, url };
|
|
299
300
|
if (template_id) body.template_id = template_id;
|
|
@@ -302,6 +303,7 @@ server.registerTool(
|
|
|
302
303
|
if (secrets) body.secrets = secrets;
|
|
303
304
|
if (headers) body.headers = headers;
|
|
304
305
|
if (enable_test_mode !== undefined) body.enable_test_mode = enable_test_mode;
|
|
306
|
+
if (test_timeout !== undefined) body.test_timeout = test_timeout;
|
|
305
307
|
const test = await apiPost("/api/tests/create", body);
|
|
306
308
|
return { content: [{ type: "text", text: JSON.stringify(test, null, 2) }] };
|
|
307
309
|
} catch (err) {
|
|
@@ -375,18 +377,35 @@ server.registerTool(
|
|
|
375
377
|
server.registerTool(
|
|
376
378
|
"validate_test",
|
|
377
379
|
{
|
|
378
|
-
description:
|
|
380
|
+
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.
|
|
381
|
+
|
|
382
|
+
NAVIGATION RULE: test code must only call page.goto() once, to the site root/homepage (the url parameter). All further navigation must be through real user interactions — menu hovers, link clicks, form submissions. Never use page.goto() to jump directly to a sub-page, product URL, or collection path.
|
|
383
|
+
|
|
384
|
+
ONCE ALL STEPS PASS — before calling create_test, review the code against these quality checks and fix any violations:
|
|
385
|
+
|
|
386
|
+
1. NO waitForLoadState after clicks on SSR sites (Shopify, Next.js, most e-commerce): these pages render HTML server-side, so elements are in the initial response. Waiting for domcontentloaded after a click adds seconds of dead time. Instead: let the next step's element waitFor() serve as the nav signal, OR use page.waitForURL(/pattern/) when you need to confirm the URL changed before running JS (e.g. page.evaluate).
|
|
387
|
+
|
|
388
|
+
2. NO waitForTimeout — never use page.waitForTimeout(ms). Always wait for a specific condition: element visibility, URL change, or network idle.
|
|
389
|
+
|
|
390
|
+
3. NO JS clicks — never use element.evaluate(el => el.click()). Use locator.click() which simulates real mouse events. For elements covered by sticky headers or overlays, use click({ force: true }) instead of JS clicks.
|
|
391
|
+
|
|
392
|
+
4. DELAY-TRIGGERED POPUPS need dual guards — scroll-triggered or time-delayed popups (e.g. Klaviyo email capture) often fire after the dedicated dismiss step has already moved on. Add a second guard at the start of the next interaction step: check isVisible() and dismiss if present.
|
|
393
|
+
|
|
394
|
+
5. POPUP DISMISS TIMEOUTS — keep popup waitFor timeouts short (4–6 s). A popup that doesn't appear within 6 s is unlikely to appear before the next interaction anyway. Don't set long timeouts just to be safe — it burns time on every run.
|
|
395
|
+
|
|
396
|
+
6. COLLECTION CARD CLICKS — on collection/category pages, product image areas are often covered by a full-card anchor overlay (e.g. a.media_link on Shopify). If normal click fails with a coverage error, use click({ force: true }). Never use page.goto() to skip to the product URL.`,
|
|
379
397
|
readOnlyHint: true,
|
|
380
398
|
inputSchema: {
|
|
381
399
|
code: z.string().describe("Custom Playwright test code. Must define an async function test({ page, step, log })."),
|
|
382
400
|
url: z.string().describe("The target URL to test against (e.g. 'https://example.com')"),
|
|
383
401
|
timeout: z.coerce.number().optional().describe("Maximum seconds the test is allowed to run (default: 120, max: 300). Increase for tests with many page navigations."),
|
|
402
|
+
capture_trace: z.boolean().optional().describe("Enable Playwright trace recording (default: false). Enable when step output and screenshots are not enough to diagnose a failure — the trace includes full DOM snapshots, network timeline, and every action. Adds memory overhead; avoid on tests with many page navigations unless needed."),
|
|
384
403
|
},
|
|
385
404
|
},
|
|
386
|
-
async ({ code, url, timeout }) => {
|
|
405
|
+
async ({ code, url, timeout, capture_trace }) => {
|
|
387
406
|
try {
|
|
388
407
|
// POST returns immediately with test_run_id; poll until done (same as run_test)
|
|
389
|
-
const { test_run_id } = await apiPost("/api/tests/validate", { code, url, timeout });
|
|
408
|
+
const { test_run_id } = await apiPost("/api/tests/validate", { code, url, timeout, capture_trace });
|
|
390
409
|
const testRun = await pollTestRun(test_run_id);
|
|
391
410
|
const runResult = testRun.result || {};
|
|
392
411
|
const content = [];
|
|
@@ -497,11 +516,12 @@ server.registerTool(
|
|
|
497
516
|
secrets: z.record(z.string()).nullable().optional().describe("Updated secrets (pass null to clear). Encrypted at rest, never returned in API responses — must be provided again when updating a test that uses secrets."),
|
|
498
517
|
headers: z.record(z.string()).nullable().optional().describe("Updated HTTP headers (pass null to clear)"),
|
|
499
518
|
enable_test_mode: z.boolean().optional().describe("Send X-AskQA-Secret header to the target site, enabling test mode on sites that support it (default: true)"),
|
|
519
|
+
test_timeout: z.coerce.number().nullable().optional().describe("Maximum seconds the scheduler is allowed to run this test (default: 120, max: 300). Set when validate_test needed a timeout > 120 to pass. Pass null to reset to default."),
|
|
500
520
|
healing_disabled: z.boolean().optional().describe("Set true to stop AskQA from suggesting fixes for this test (e.g. a failing test that's acceptable as-is, or one you'd rather fix yourself). Auto-clears once the test passes again. Set false to re-enable."),
|
|
501
521
|
healing_note: z.string().nullable().optional().describe("Optional note explaining why healing was disabled (pass null to clear)."),
|
|
502
522
|
},
|
|
503
523
|
},
|
|
504
|
-
async ({ test_id, name, url, code, template_id, params, secrets, headers, enable_test_mode, healing_disabled, healing_note }) => {
|
|
524
|
+
async ({ test_id, name, url, code, template_id, params, secrets, headers, enable_test_mode, test_timeout, healing_disabled, healing_note }) => {
|
|
505
525
|
try {
|
|
506
526
|
const body = {};
|
|
507
527
|
if (name !== undefined) body.name = name;
|
|
@@ -512,6 +532,7 @@ server.registerTool(
|
|
|
512
532
|
if (secrets !== undefined) body.secrets = secrets;
|
|
513
533
|
if (headers !== undefined) body.headers = headers;
|
|
514
534
|
if (enable_test_mode !== undefined) body.enable_test_mode = enable_test_mode;
|
|
535
|
+
if (test_timeout !== undefined) body.test_timeout = test_timeout;
|
|
515
536
|
if (healing_disabled !== undefined) body.healing_disabled = healing_disabled;
|
|
516
537
|
if (healing_note !== undefined) body.healing_note = healing_note;
|
|
517
538
|
const test = await apiPatch(`/api/tests/${test_id}`, body);
|