@validate.qa/mcp 1.0.0 → 1.0.1
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/bin.cjs +1 -1
- package/dist/index.js +70 -66
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/bin.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// CommonJS wrapper for Windows compatibility
|
|
2
|
+
// CommonJS wrapper for Windows compatibility - npx can't run ESM bin files on Windows
|
|
3
3
|
const { resolve } = require('path');
|
|
4
4
|
const { execFileSync } = require('child_process');
|
|
5
5
|
const entry = resolve(__dirname, 'dist', 'index.js');
|
package/dist/index.js
CHANGED
|
@@ -80,7 +80,7 @@ function runInit() {
|
|
|
80
80
|
const entry = buildMcpEntry(config2);
|
|
81
81
|
const cwd = process.cwd();
|
|
82
82
|
console.log("");
|
|
83
|
-
console.log(" Validate.QA
|
|
83
|
+
console.log(" Validate.QA - MCP Setup");
|
|
84
84
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
85
85
|
console.log("");
|
|
86
86
|
let anyWritten = false;
|
|
@@ -91,9 +91,9 @@ function runInit() {
|
|
|
91
91
|
}
|
|
92
92
|
const file = join(target.configDir, target.configFile);
|
|
93
93
|
if (result === "created") {
|
|
94
|
-
console.log(` \u2713 ${target.name}
|
|
94
|
+
console.log(` \u2713 ${target.name} - created ${file}`);
|
|
95
95
|
} else {
|
|
96
|
-
console.log(` \u2713 ${target.name}
|
|
96
|
+
console.log(` \u2713 ${target.name} - updated ${file}`);
|
|
97
97
|
}
|
|
98
98
|
anyWritten = true;
|
|
99
99
|
}
|
|
@@ -243,7 +243,7 @@ function registerAllTools(server2, client2) {
|
|
|
243
243
|
server2,
|
|
244
244
|
client2,
|
|
245
245
|
"list_tests",
|
|
246
|
-
"List tests in the current project. Returns test name, status, suite, tags, confidence, and the last run result. Paginated
|
|
246
|
+
"List tests in the current project. Returns test name, status, suite, tags, confidence, and the last run result. Paginated - use limit/offset to page through all tests.",
|
|
247
247
|
{
|
|
248
248
|
status: z.enum(["DRAFT", "ACTIVE", "DISABLED", "QUARANTINED"]).optional().describe("Filter by test lifecycle status"),
|
|
249
249
|
suite: z.string().optional().describe("Filter by suite name"),
|
|
@@ -258,7 +258,7 @@ function registerAllTools(server2, client2) {
|
|
|
258
258
|
server2,
|
|
259
259
|
client2,
|
|
260
260
|
"get_test",
|
|
261
|
-
"Get details of a specific test: name, status, tags, steps, session, and the 5 most recent run results. Code is truncated to 200 lines
|
|
261
|
+
"Get details of a specific test: name, status, tags, steps, session, and the 5 most recent run results. Code is truncated to 200 lines - use get_test_code for the full Playwright code.",
|
|
262
262
|
{ testId: z.string().describe("The test case ID") },
|
|
263
263
|
{ readOnlyHint: true }
|
|
264
264
|
);
|
|
@@ -288,7 +288,7 @@ function registerAllTools(server2, client2) {
|
|
|
288
288
|
server2,
|
|
289
289
|
client2,
|
|
290
290
|
"edit_test_code",
|
|
291
|
-
"Update the Playwright code, name, or tags of an existing test case. Read project://test-conventions for patterns and pitfalls. Code is validated
|
|
291
|
+
"Update the Playwright code, name, or tags of an existing test case. Read project://test-conventions for patterns and pitfalls. Code is validated - fix any warnings returned.",
|
|
292
292
|
{
|
|
293
293
|
testId: z.string().describe("The test case ID to edit"),
|
|
294
294
|
playwrightCode: z.string().optional().describe("New Playwright test code (full file replacement)"),
|
|
@@ -339,7 +339,7 @@ function registerAllTools(server2, client2) {
|
|
|
339
339
|
const content = [
|
|
340
340
|
{
|
|
341
341
|
type: "text",
|
|
342
|
-
text: `Run ${result.runId}
|
|
342
|
+
text: `Run ${result.runId} - status: ${result.status} - showing ${result.returned} of ${result.total} screenshot(s)`
|
|
343
343
|
}
|
|
344
344
|
];
|
|
345
345
|
for (const shot of result.screenshots) {
|
|
@@ -492,9 +492,9 @@ function registerAllTools(server2, client2) {
|
|
|
492
492
|
server2,
|
|
493
493
|
client2,
|
|
494
494
|
"create_test",
|
|
495
|
-
"Create a new test case with Playwright code. Read project://test-conventions for templates and pitfalls BEFORE writing code. The test starts as DRAFT
|
|
495
|
+
"Create a new test case with Playwright code. Read project://test-conventions for templates and pitfalls BEFORE writing code. The test starts as DRAFT - auto-runs and auto-promotes to ACTIVE on pass. IMPORTANT: (1) Set suite to the EXACT feature name. (2) After creating all tests for a feature, you MUST call add_api_endpoints.",
|
|
496
496
|
{
|
|
497
|
-
name: z.string().describe('Test name (e.g. "Login
|
|
497
|
+
name: z.string().describe('Test name (e.g. "Login - Happy Path")'),
|
|
498
498
|
playwrightCode: z.string().describe("Full Playwright test code"),
|
|
499
499
|
suite: z.string().optional().describe('MUST match an existing feature name exactly to link the test to that feature (e.g. "Authentication", not "Auth Tests")'),
|
|
500
500
|
tags: z.array(z.string()).optional().describe('Tags (e.g. ["@smoke", "@auth"])')
|
|
@@ -610,7 +610,7 @@ function registerAllTools(server2, client2) {
|
|
|
610
610
|
server2,
|
|
611
611
|
client2,
|
|
612
612
|
"flag_as_bug",
|
|
613
|
-
"Mark a failing test as a confirmed application bug
|
|
613
|
+
"Mark a failing test as a confirmed application bug - quarantines it with @bug-found and records the bug description. Use when the failure is caused by broken app behaviour, NOT a test script issue. Do NOT use for selector changes or timeout failures - use heal_test or edit_test_code for those.",
|
|
614
614
|
{
|
|
615
615
|
testId: z.string().describe("The failing test case ID"),
|
|
616
616
|
description: z.string().describe('What is broken in the application (e.g. "POST /api/checkout returns 500 when payment method is valid")')
|
|
@@ -620,7 +620,7 @@ function registerAllTools(server2, client2) {
|
|
|
620
620
|
server2,
|
|
621
621
|
client2,
|
|
622
622
|
"run_security_audit",
|
|
623
|
-
'Run an AI security audit on captured API traffic. Requires prior browser-based test execution (discovery or capture)
|
|
623
|
+
'Run an AI security audit on captured API traffic. Requires prior browser-based test execution (discovery or capture) - analyzes real network traffic, not just registered endpoints. If no traffic exists, the developer can trigger "Security Audit" from the Dashboard which launches a live browser to capture it.',
|
|
624
624
|
{}
|
|
625
625
|
);
|
|
626
626
|
tool(
|
|
@@ -638,14 +638,14 @@ function registerAllTools(server2, client2) {
|
|
|
638
638
|
server2,
|
|
639
639
|
client2,
|
|
640
640
|
"set_app_brief",
|
|
641
|
-
"Set or update the project's application brief
|
|
641
|
+
"Set or update the project's application brief - a description of the app used by AI for better test generation.",
|
|
642
642
|
{ appBrief: z.string().describe("Application description. Set empty to clear.") }
|
|
643
643
|
);
|
|
644
644
|
tool(
|
|
645
645
|
server2,
|
|
646
646
|
client2,
|
|
647
647
|
"get_coverage_summary",
|
|
648
|
-
"Get feature-level test coverage: planned vs existing vs passing tests, plus API endpoint coverage. Shows uncoveredEndpoints per feature
|
|
648
|
+
"Get feature-level test coverage: planned vs existing vs passing tests, plus API endpoint coverage. Shows uncoveredEndpoints per feature - use this to find which API endpoints still need tests.",
|
|
649
649
|
{},
|
|
650
650
|
{ readOnlyHint: true }
|
|
651
651
|
);
|
|
@@ -662,7 +662,7 @@ function registerAllTools(server2, client2) {
|
|
|
662
662
|
server2,
|
|
663
663
|
client2,
|
|
664
664
|
"setup_project",
|
|
665
|
-
'One-shot project setup from codebase knowledge. Sets app brief, base URL, test credentials, and creates features with pages
|
|
665
|
+
'One-shot project setup from codebase knowledge. Sets app brief, base URL, test credentials, and creates features with pages - all in one call. Use this when a developer says "set up test coverage for my app."',
|
|
666
666
|
{
|
|
667
667
|
appBrief: z.string().optional().describe("Description of the application (what it does, key features, tech stack)"),
|
|
668
668
|
baseUrl: z.string().describe('Application base URL (e.g. "https://myapp.com" or "http://localhost:3000")'),
|
|
@@ -719,7 +719,7 @@ var config = resolveConfig();
|
|
|
719
719
|
var client = new ValidateQAClient(config.apiUrl, config.token, config.projectId);
|
|
720
720
|
var server = new McpServer(
|
|
721
721
|
{ name: "validate.qa", version: "1.0.0" },
|
|
722
|
-
{ instructions: `Validate.QA
|
|
722
|
+
{ instructions: `Validate.QA - AI-powered QA platform: generates, runs, and heals Playwright E2E tests.
|
|
723
723
|
|
|
724
724
|
## Capabilities
|
|
725
725
|
- Setup projects via setup_project (read source code first)
|
|
@@ -737,7 +737,7 @@ var server = new McpServer(
|
|
|
737
737
|
|
|
738
738
|
## Automatic lifecycle
|
|
739
739
|
Do NOT call run_test after create_test (auto-queued) or heal_test after failures (auto-heals).
|
|
740
|
-
Do NOT check run status in a loop
|
|
740
|
+
Do NOT check run status in a loop - call get_test_failures after a reasonable wait to see what failed.
|
|
741
741
|
Lifecycle: create > auto-run > pass=ACTIVE, fail=auto-heal > heal fail=@needs-review.
|
|
742
742
|
DO call add_api_endpoints per feature (not automatic).
|
|
743
743
|
DO call run_security_audit after UI tests to analyze captured API traffic. If it errors about missing data, tell the developer to click "Security Audit" in the Dashboard to run a live browser capture first. Use list_security_findings to review results.
|
|
@@ -747,33 +747,33 @@ DO call run_security_audit after UI tests to analyze captured API traffic. If it
|
|
|
747
747
|
|
|
748
748
|
## Triaging failures
|
|
749
749
|
- App broken: flag_as_bug(testId, description). Selector/timing: heal_test. Bad test logic: edit_test_code.
|
|
750
|
-
- Unsure: heal_test first
|
|
750
|
+
- Unsure: heal_test first - it detects app bugs automatically.
|
|
751
751
|
|
|
752
752
|
## Using the codebase
|
|
753
753
|
You have full source code access. Always READ before writing: routes for URLs, components for selectors, API handlers for endpoints.
|
|
754
754
|
|
|
755
|
-
## Before writing any test
|
|
755
|
+
## Before writing any test - preflight check
|
|
756
756
|
|
|
757
757
|
Read project://test-conventions first. Check the variables section:
|
|
758
758
|
- BASE_URL must be set (the runner needs it). If missing, call setup_project or set_variable.
|
|
759
759
|
- TEST_USER_EMAIL and TEST_USER_PASSWORD must be set if tests require login. If missing, ask the developer for credentials and call set_variable.
|
|
760
760
|
- If starting fresh (no features, no variables), use workflow #1 (setup_project) before anything else.
|
|
761
761
|
|
|
762
|
-
## Critical rules
|
|
763
|
-
- URLs: relative only, never hardcoded domains
|
|
764
|
-
- Credentials: process.env.TEST_USER_EMAIL! (non-null assertion)
|
|
762
|
+
## Critical rules - see project://test-conventions for full details and code templates
|
|
763
|
+
- URLs: relative only, never hardcoded domains - runner sets baseURL
|
|
764
|
+
- Credentials: process.env.TEST_USER_EMAIL! (non-null assertion) - never ?? or || fallbacks
|
|
765
765
|
- Registration: testuser-\${Date.now()}@example.com
|
|
766
766
|
- Suite name must match feature name exactly (case-sensitive)
|
|
767
767
|
- @auth must NOT combine with @smoke/@regression/@needs-auth/@critical
|
|
768
768
|
- Each test = own browser process; @needs-auth tests MUST login inline
|
|
769
769
|
- API tests: test.describe.serial() with __QA_HTTP__ log after every request
|
|
770
770
|
- UI tests: console.log() milestones after key actions
|
|
771
|
-
- create_test/edit_test_code validate code
|
|
771
|
+
- create_test/edit_test_code validate code - fix warnings before moving on
|
|
772
772
|
|
|
773
773
|
## Setting up coverage
|
|
774
|
-
1. SCAN: Read router, nav, pages, API handlers
|
|
774
|
+
1. SCAN: Read router, nav, pages, API handlers - map ALL routes and features.
|
|
775
775
|
2. SETUP: setup_project with features, pages, baseUrl, testCredentials, appBrief.
|
|
776
|
-
3. WRITE: One feature at a time. For EACH test, read the SPECIFIC source code first (see codeReadingRequirements in test-conventions). For login blocks: read the login component. For UI tests: read the page component. For API tests: read the route handler. Templates are structure guides
|
|
776
|
+
3. WRITE: One feature at a time. For EACH test, read the SPECIFIC source code first (see codeReadingRequirements in test-conventions). For login blocks: read the login component. For UI tests: read the page component. For API tests: read the route handler. Templates are structure guides - selectors MUST come from the real code. Use create_test for BOTH UI and API tests. Then call add_api_endpoints.
|
|
777
777
|
4. VERIFY: get_coverage_summary + list_features for failures + get_test_failures.
|
|
778
778
|
|
|
779
779
|
Test real user flows (forms, validation, errors). "Page loads" tests are worthless.` }
|
|
@@ -813,16 +813,16 @@ server.resource(
|
|
|
813
813
|
const hasAuth = varNames.includes("TEST_USER_EMAIL") && varNames.includes("TEST_USER_PASSWORD");
|
|
814
814
|
const conventions = {
|
|
815
815
|
codeReadingRequirements: {
|
|
816
|
-
CRITICAL: "Templates below are STRUCTURE GUIDES
|
|
817
|
-
forLogin: "BEFORE writing any @needs-auth login block: READ the login/auth page component. Find: (1) the login route (/login, /auth/login, /signin?), (2) the flow type (single-step or multi-step with Continue/Next button?), (3) exact input selectors (labels, placeholders, input types
|
|
818
|
-
forUITests: "BEFORE writing a UI test for a page: READ the page component. Find: headings (text + level), form fields (labels, placeholders, types), buttons (text, role), navigation elements, validation error messages, success/error states. Use the exact text from the component
|
|
816
|
+
CRITICAL: "Templates below are STRUCTURE GUIDES - every selector, route, and endpoint MUST come from reading the actual source code. Never copy a template verbatim.",
|
|
817
|
+
forLogin: "BEFORE writing any @needs-auth login block: READ the login/auth page component. Find: (1) the login route (/login, /auth/login, /signin?), (2) the flow type (single-step or multi-step with Continue/Next button?), (3) exact input selectors (labels, placeholders, input types - is it getByLabel, getByPlaceholder, or input[type=password]?), (4) exact submit button text, (5) post-login redirect URL. Every app is different - single-step, multi-step, OAuth buttons, magic links.",
|
|
818
|
+
forUITests: "BEFORE writing a UI test for a page: READ the page component. Find: headings (text + level), form fields (labels, placeholders, types), buttons (text, role), navigation elements, validation error messages, success/error states. Use the exact text from the component - do not guess.",
|
|
819
819
|
forAPITests: "BEFORE writing an API test: READ the route handler. Find: the endpoint path, HTTP method, request body schema (what fields, what types), response body shape (is it flat or wrapped in { data: {...} }?), auth requirements (Bearer token? Cookie? API key?), error response shapes.",
|
|
820
820
|
forAPIMapping: "BEFORE calling add_api_endpoints: READ ALL route handlers for the feature. Register every endpoint with accurate method, path, requestShape, responseShapes, requiresAuth."
|
|
821
821
|
},
|
|
822
822
|
credentials: {
|
|
823
823
|
rule: "Never hardcode credentials in test code",
|
|
824
|
-
loginFields: hasAuth ? "Use process.env.TEST_USER_EMAIL! and process.env.TEST_USER_PASSWORD!" : "No credentials configured
|
|
825
|
-
registrationFields: 'MUST use unique email per run: `testuser-${Date.now()}@example.com`
|
|
824
|
+
loginFields: hasAuth ? "Use process.env.TEST_USER_EMAIL! and process.env.TEST_USER_PASSWORD!" : "No credentials configured - set TEST_USER_EMAIL and TEST_USER_PASSWORD via set_variable",
|
|
825
|
+
registrationFields: 'MUST use unique email per run: `testuser-${Date.now()}@example.com` - reusing emails causes "already registered" failures',
|
|
826
826
|
registrationPassword: "Use 'TestPass123!' for new account passwords (meets typical strength rules)"
|
|
827
827
|
},
|
|
828
828
|
variables: {
|
|
@@ -831,10 +831,10 @@ server.resource(
|
|
|
831
831
|
},
|
|
832
832
|
tags: {
|
|
833
833
|
"@auth": "Tests the login/auth flow itself. MUST NOT combine with @smoke, @regression, @needs-auth, @critical. CAN include @discovered.",
|
|
834
|
-
"@needs-auth": "Tests for authenticated pages
|
|
835
|
-
"@smoke": "Quick sanity checks
|
|
834
|
+
"@needs-auth": "Tests for authenticated pages - MUST include inline login block. CAN combine with @smoke, @regression, @critical.",
|
|
835
|
+
"@smoke": "Quick sanity checks - run on every deploy",
|
|
836
836
|
"@regression": "Full regression suite tests",
|
|
837
|
-
"@critical": "Business-critical paths (informational label
|
|
837
|
+
"@critical": "Business-critical paths (informational label - no special suite filtering)",
|
|
838
838
|
"@api": "API-level tests. Runs in its own project, excluded from suite/smoke/regression runs",
|
|
839
839
|
"@discovered": "Auto-added to tests generated by AI discovery",
|
|
840
840
|
"@generated": "Auto-added by AI discovery and API test generation",
|
|
@@ -842,69 +842,73 @@ server.resource(
|
|
|
842
842
|
"@flaky": "Auto-added after repeated failures. Excluded from suite/smoke/regression runs",
|
|
843
843
|
"@bug-found": "Added when healer detects an app bug. Test is auto-quarantined",
|
|
844
844
|
"@needs-review": "Added when DRAFT test fails healing. Requires human attention",
|
|
845
|
-
"@heal-skip": "Added by healer when test is permanently unfixable. Prevents future auto-heal"
|
|
845
|
+
"@heal-skip": "Added by healer when test is permanently unfixable. Prevents future auto-heal",
|
|
846
|
+
"@rate-limit-blocked": "Auto-added by pre-heal classifier when test failure was a rate-limit (HTTP 429). Test skips heal until next pass clears the tag. NOT for use by test authors.",
|
|
847
|
+
"@quota-exceeded": "Auto-added when failure is a quota/daily-limit response. Skips heal; clears on next pass.",
|
|
848
|
+
"@auth-lost": "Auto-added when failure is a 401/403. Skips heal; clears after re-auth + pass.",
|
|
849
|
+
"@rate-limit-expected": 'OPT-OUT tag for tests that intentionally exercise rate-limit behavior (e.g., "POST /api/login 6 times to trigger lockout"). Add this tag to ANY @api test that asserts rate-limit responses, otherwise the rate-limit detector will silently downgrade your failure to ERROR. Distinct from auto-pushed @rate-limit-blocked.'
|
|
846
850
|
},
|
|
847
851
|
executionModel: {
|
|
848
852
|
isolation: "Each test runs as its own separate Playwright process in a fresh browser. No shared state, no storageState, no global setup.",
|
|
849
|
-
authRequired: "Every @needs-auth test MUST include its own login block. Do NOT suggest storageState or performLogin()
|
|
850
|
-
concurrency: "Tests run in parallel
|
|
853
|
+
authRequired: "Every @needs-auth test MUST include its own login block. Do NOT suggest storageState or performLogin() - those only work in the regression suite.",
|
|
854
|
+
concurrency: "Tests run in parallel - runner claims up to 10 per poll, launched 3s apart (~30s per batch). Multiple batches can overlap. Use test.setTimeout(120_000) if the target app is slow under concurrent login load."
|
|
851
855
|
},
|
|
852
856
|
lifecycle: {
|
|
853
|
-
DRAFT: "New or unverified test
|
|
854
|
-
ACTIVE: "Verified and running
|
|
855
|
-
DISABLED: "Manually paused
|
|
857
|
+
DRAFT: "New or unverified test - not included in scheduled runs",
|
|
858
|
+
ACTIVE: "Verified and running - included in batch/suite executions",
|
|
859
|
+
DISABLED: "Manually paused - excluded from all runs",
|
|
856
860
|
QUARANTINED: "Auto-quarantined after repeated failures"
|
|
857
861
|
},
|
|
858
862
|
logging: {
|
|
859
863
|
CRITICAL: "EVERY request.get/post/put/patch/delete call MUST be followed by a __QA_HTTP__ log line. This is how the platform surfaces request/response data in the Network panel. Missing this = invisible API calls.",
|
|
860
864
|
apiPattern: "const body = await res.json();\nconsole.log('__QA_HTTP__:' + JSON.stringify({ method: 'POST', url: '/api/path', status: res.status(), requestBody: { field: value, password: '[REDACTED]' }, responseBody: body }));",
|
|
861
865
|
rules: [
|
|
862
|
-
"Call res.json() ONCE, store in body variable
|
|
866
|
+
"Call res.json() ONCE, store in body variable - reuse for log and assertions (calling twice throws)",
|
|
863
867
|
"Log AFTER the request, BEFORE assertions",
|
|
864
868
|
'Redact sensitive fields: password: "[REDACTED]", token: "[REDACTED]"',
|
|
865
869
|
"Use the literal URL string (same as the request call), not a variable",
|
|
866
|
-
"UI tests using page fixture do NOT need __QA_HTTP__
|
|
870
|
+
"UI tests using page fixture do NOT need __QA_HTTP__ - only request fixture tests"
|
|
867
871
|
],
|
|
868
|
-
uiMilestones: "UI tests (page fixture): add plain console.log() at key milestones
|
|
872
|
+
uiMilestones: "UI tests (page fixture): add plain console.log() at key milestones - e.g. console.log('Navigated to login page') after page.goto(). These appear in the Console Output panel.",
|
|
869
873
|
uiLoggingRules: [
|
|
870
|
-
"Log AFTER the action, not before
|
|
874
|
+
"Log AFTER the action, not before - confirms the action succeeded",
|
|
871
875
|
'Include dynamic values where useful: console.log("Current URL:", page.url())',
|
|
872
|
-
"Keep messages short and human-readable
|
|
876
|
+
"Keep messages short and human-readable - they show verbatim in the UI",
|
|
873
877
|
"Do NOT log sensitive values (passwords, tokens)"
|
|
874
878
|
],
|
|
875
|
-
apiTestStructure: "Use test.describe.serial() for API tests sharing auth. Independent validation tests (401, 404 checks) go in a SEPARATE non-serial describe block
|
|
879
|
+
apiTestStructure: "Use test.describe.serial() for API tests sharing auth. Independent validation tests (401, 404 checks) go in a SEPARATE non-serial describe block - do not mix them with the auth-dependent serial flow."
|
|
876
880
|
},
|
|
877
881
|
bestPractices: [
|
|
878
882
|
"Every @needs-auth test must start with a login block (goto login, fill creds, click submit, waitForURL)",
|
|
879
|
-
"Use { timeout: 30000 } on waitForURL after login
|
|
880
|
-
"Keep login blocks minimal
|
|
881
|
-
"After login, page.goto() directly to the page being tested
|
|
883
|
+
"Use { timeout: 30000 } on waitForURL after login - target apps can be slow under concurrent test load",
|
|
884
|
+
"Keep login blocks minimal - no assertions on the login page (that is what @auth tests are for)",
|
|
885
|
+
"After login, page.goto() directly to the page being tested - do not navigate via UI clicks",
|
|
882
886
|
"Use role-based selectors (getByRole, getByLabel) over CSS selectors",
|
|
883
887
|
"Registration tests must generate unique email with Date.now()",
|
|
884
888
|
"Group tests into suites matching feature names exactly (case-sensitive)",
|
|
885
|
-
"Do NOT suggest storageState or performLogin() for create_test
|
|
886
|
-
"EVERY API test (request fixture) MUST include __QA_HTTP__ log after each request call
|
|
887
|
-
'Avoid waitForLoadState("networkidle")
|
|
888
|
-
'For headings, use exact: true or level: 1
|
|
889
|
+
"Do NOT suggest storageState or performLogin() for create_test - those only work in the regression suite",
|
|
890
|
+
"EVERY API test (request fixture) MUST include __QA_HTTP__ log after each request call - see logging section above",
|
|
891
|
+
'Avoid waitForLoadState("networkidle") - apps with SSE, WebSocket, or analytics connections may never reach idle, causing 60s timeouts. Wait for specific elements instead.',
|
|
892
|
+
'For headings, use exact: true or level: 1 - getByRole("heading", { name: "X" }) matches ALL h1-h6 containing "X", causing strict mode violations'
|
|
889
893
|
],
|
|
890
894
|
pitfalls: {
|
|
891
895
|
CRITICAL: "These are the top causes of test failures. Read before writing ANY test.",
|
|
892
896
|
strictModeViolations: `Playwright throws when a locator resolves to 2+ elements. Common causes: getByLabel('Password') matches input AND 'Show password' toggle; getByRole('button', { name: 'Continue' }) matches 'Continue' AND 'Continue with GitHub'. Prevention: use input[type="password"] for password fields, use { exact: true } for ambiguous button text, scope to a parent container (page.locator('form#login').getByLabel('Email')).`,
|
|
893
|
-
networkIdle: "NEVER use waitForLoadState('networkidle')
|
|
897
|
+
networkIdle: "NEVER use waitForLoadState('networkidle') - waits for 500ms of zero network activity, which is fragile and causes 30s+ timeouts in apps with polling, analytics, or WebSockets. Use waitForURL() or wait for a specific element instead.",
|
|
894
898
|
headingStrictMode: "getByRole('heading', { name: 'Dashboard' }) can match multiple headings (h1 + h2 + breadcrumb). Add { level: 1 } or { exact: true } to disambiguate. Same for getByLabel('Password') matching input AND 'Show password' toggle.",
|
|
895
|
-
postLoginRedirect: "NEVER assert exact URL after login (e.g. expect(page).toHaveURL('/dashboard'))
|
|
896
|
-
resJsonOnce: "res.json() consumes the response body stream
|
|
899
|
+
postLoginRedirect: "NEVER assert exact URL after login (e.g. expect(page).toHaveURL('/dashboard')) - apps redirect to different routes per user/role. Use: await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 30000 })",
|
|
900
|
+
resJsonOnce: "res.json() consumes the response body stream - calling it twice throws. Store result: const body = await res.json(); then reuse body for logging and assertions.",
|
|
897
901
|
hiddenInputs: "Many UI frameworks use visually hidden <input> with custom overlays. page.check() fails because the overlay intercepts clicks. Click the <label> or visible wrapper: page.getByText('Remember me').click()",
|
|
898
|
-
apiResponseShapes: "API endpoints often wrap data in a property ({ user: {...} }, { data: [...] }). Read the route handler's res.json() call to see the actual shape. If the handler does res.json({ ticket }), then body is { ticket: { id, ... } }
|
|
899
|
-
invalidIds: "Not all routes validate ID format before querying the database. If a route passes the raw ID to an ORM query and gets null back, it returns 404 'not found'
|
|
900
|
-
hardcodedUrls: "NEVER hardcode full URLs in test code. For UI tests: always use relative paths
|
|
902
|
+
apiResponseShapes: "API endpoints often wrap data in a property ({ user: {...} }, { data: [...] }). Read the route handler's res.json() call to see the actual shape. If the handler does res.json({ ticket }), then body is { ticket: { id, ... } } - NOT { id, ... }.",
|
|
903
|
+
invalidIds: "Not all routes validate ID format before querying the database. If a route passes the raw ID to an ORM query and gets null back, it returns 404 'not found' - not 400 'bad request'. If unsure, expect either: expect([400, 404]).toContain(res.status())",
|
|
904
|
+
hardcodedUrls: "NEVER hardcode full URLs in test code. For UI tests: always use relative paths - page.goto('/login'). The runner sets baseURL from the project's BASE_URL variable. For API tests: use relative paths if the API is on the same domain as the frontend (request.post('/api/auth/login')). If the API is on a SEPARATE domain, use process.env.BASE_API_URL! as a test variable (set via set_variable) and construct URLs: request.post(process.env.BASE_API_URL! + '/auth/login'). NEVER hardcode 'https://example.com/...' in test code - use env vars or relative paths.",
|
|
901
905
|
credentialFallbacks: "NEVER use process.env.TEST_USER_EMAIL ?? 'fallback' or || 'fallback'. Use ! (non-null assertion). Missing variable = test should fail immediately, not silently use hardcoded value."
|
|
902
906
|
},
|
|
903
907
|
templates: {
|
|
904
|
-
needsAuth: `test('Feature Name
|
|
908
|
+
needsAuth: `test('Feature Name - Test Name', {
|
|
905
909
|
tag: ['@needs-auth', '@smoke'],
|
|
906
910
|
}, async ({ page }) => {
|
|
907
|
-
// Login block
|
|
911
|
+
// Login block - ADAPTED from reading the app's login page component
|
|
908
912
|
// YOU MUST READ the login page component BEFORE writing this block
|
|
909
913
|
await page.goto('/login'); // read the router for actual login route
|
|
910
914
|
await page.getByRole('textbox', { name: /email/i }).fill(process.env.TEST_USER_EMAIL!);
|
|
@@ -919,7 +923,7 @@ server.resource(
|
|
|
919
923
|
console.log('On feature page');
|
|
920
924
|
await expect(page.getByRole('heading', { name: /feature name/i })).toBeVisible();
|
|
921
925
|
});`,
|
|
922
|
-
authTest: "test('Login
|
|
926
|
+
authTest: "test('Login - Happy Path', {\n tag: ['@auth'],\n}, async ({ page }) => {\n await page.goto('/auth/login');\n // test the login form itself - selectors from reading the actual component\n});",
|
|
923
927
|
apiSerial: "test.describe.serial('Feature Name', () => {\n let token: string;\n test('authenticate', async ({ request }) => {\n const res = await request.post('/api/auth/login', {\n data: { email: process.env.TEST_USER_EMAIL!, password: process.env.TEST_USER_PASSWORD! }\n });\n const body = await res.json();\n console.log('__QA_HTTP__:' + JSON.stringify({\n method: 'POST', url: '/api/auth/login', status: res.status(),\n requestBody: { email: process.env.TEST_USER_EMAIL!, password: '[REDACTED]' },\n responseBody: body,\n }));\n expect(res.status()).toBe(200);\n token = body.access_token; // \u2190 adapt to your API (e.g. body.token, body.jwt)\n });\n test('get profile', async ({ request }) => {\n const res = await request.get('/api/users/profile', {\n headers: { Authorization: `Bearer ${token}` }\n });\n const body = await res.json();\n console.log('__QA_HTTP__:' + JSON.stringify({\n method: 'GET', url: '/api/users/profile', status: res.status(),\n responseBody: body,\n }));\n expect(res.status()).toBe(200);\n });\n});",
|
|
924
928
|
apiEndpointMapping: `// After reading an Express route handler like:
|
|
925
929
|
// router.post('/api/auth/login', async (req, res) => { ... })
|
|
@@ -934,7 +938,7 @@ server.resource(
|
|
|
934
938
|
responseShapes: { "200": { "token": "string", "user": "object" }, "401": { "error": "string" } }
|
|
935
939
|
}]
|
|
936
940
|
}`,
|
|
937
|
-
uiWithMilestones: `test('Feature
|
|
941
|
+
uiWithMilestones: `test('Feature - Happy Path', async ({ page }) => {
|
|
938
942
|
await page.goto('/login');
|
|
939
943
|
console.log('Navigated to login page');
|
|
940
944
|
|
|
@@ -951,7 +955,7 @@ server.resource(
|
|
|
951
955
|
});`
|
|
952
956
|
},
|
|
953
957
|
testQuality: [
|
|
954
|
-
'Test actual user flows, not just "page loads"
|
|
958
|
+
'Test actual user flows, not just "page loads" - a test that only checks goto + heading visible is nearly worthless',
|
|
955
959
|
"Test form submission: fill fields, submit, verify success/redirect",
|
|
956
960
|
"Test validation: leave required fields empty, enter invalid data, verify error messages",
|
|
957
961
|
"Test error states: wrong password, expired session, invalid IDs",
|
|
@@ -960,14 +964,14 @@ server.resource(
|
|
|
960
964
|
],
|
|
961
965
|
securityAudit: {
|
|
962
966
|
workflow: "After creating UI tests for a feature, call run_security_audit to analyze captured API traffic for auth issues, data exposure, and vulnerabilities.",
|
|
963
|
-
prerequisite: 'Requires prior browser-based test execution (the auto-verification run from create_test counts). If run_security_audit errors about missing data, tell the developer to click "Security Audit" in the Tests dropdown on the Dashboard
|
|
967
|
+
prerequisite: 'Requires prior browser-based test execution (the auto-verification run from create_test counts). If run_security_audit errors about missing data, tell the developer to click "Security Audit" in the Tests dropdown on the Dashboard - this launches a live browser to capture real network traffic (HAR) and runs analysis automatically.',
|
|
964
968
|
reviewing: "Use list_security_findings to review results. Filter by severity (critical/high/medium/low/info) or status (open/acknowledged/resolved/false-positive). Critical and high findings should be flagged to the developer immediately."
|
|
965
969
|
},
|
|
966
970
|
keyConcepts: {
|
|
967
971
|
features: 'Group related pages and tests (e.g., "Authentication" covers /auth/login, /auth/register). MUST include page routes.',
|
|
968
|
-
pages: 'Route URLs on a feature
|
|
969
|
-
suites: "Group tests for batch execution
|
|
970
|
-
runner: "Executes tests
|
|
972
|
+
pages: 'Route URLs on a feature - used for coverage tracking and site map (e.g., ["/login", "/register"])',
|
|
973
|
+
suites: "Group tests for batch execution - suite name MUST match feature name exactly (case-sensitive)",
|
|
974
|
+
runner: "Executes tests - can be local (developer machine) or cloud",
|
|
971
975
|
testData: "Key-value variables injected into tests (e.g., credentials, URLs)"
|
|
972
976
|
},
|
|
973
977
|
retrievedAt: (/* @__PURE__ */ new Date()).toISOString()
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/init.ts","../src/index.ts","../src/config.ts","../src/api-client.ts","../src/tools/index.ts"],"sourcesContent":["/**\n * `npx @validate.qa/mcp init` — one-command MCP setup for all supported IDEs.\n *\n * Creates/updates MCP config files so the user never has to manually edit JSON.\n * Detects which IDEs are present and writes the appropriate config for each.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { resolve, join } from 'path';\n\ninterface InitArgs {\n apiUrl: string;\n token: string;\n projectId: string;\n}\n\nfunction parseInitArgs(argv: string[]): InitArgs {\n const args: Record<string, string> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith('--') && i + 1 < argv.length) {\n args[arg.slice(2)] = argv[++i];\n }\n }\n\n const apiUrl = args['api-url'] ?? process.env.VALIDATEQA_API_URL ?? 'https://shared-server.up.railway.app';\n const token = args['token'] ?? process.env.VALIDATEQA_TOKEN;\n const projectId = args['project-id'] ?? process.env.VALIDATEQA_PROJECT_ID;\n\n if (!token) {\n console.error('❌ Missing --token. Copy the full command from your Validate.QA dashboard.');\n process.exit(1);\n }\n if (!projectId) {\n console.error('❌ Missing --project-id. Copy the full command from your Validate.QA dashboard.');\n process.exit(1);\n }\n\n return { apiUrl: apiUrl.replace(/\\/$/, ''), token, projectId };\n}\n\n/** Build the MCP server entry for config files */\nfunction buildMcpEntry(config: InitArgs) {\n return {\n command: 'npx',\n args: [\n '-y', '@validate.qa/mcp@latest',\n '--api-url', config.apiUrl,\n '--token', config.token,\n '--project-id', config.projectId,\n ],\n };\n}\n\ninterface IDETarget {\n name: string;\n configDir: string; // relative to cwd\n configFile: string; // filename inside configDir\n /** Key in the JSON where MCP servers live */\n serversKey: string;\n}\n\nconst IDE_TARGETS: IDETarget[] = [\n { name: 'Claude Code', configDir: '.', configFile: '.mcp.json', serversKey: 'mcpServers' },\n { name: 'Cursor', configDir: '.cursor', configFile: 'mcp.json', serversKey: 'mcpServers' },\n { name: 'Windsurf', configDir: '.windsurf', configFile: 'mcp.json', serversKey: 'mcpServers' },\n { name: 'VS Code', configDir: '.vscode', configFile: 'mcp.json', serversKey: 'servers' },\n];\n\nfunction writeConfigForIDE(cwd: string, target: IDETarget, entry: object): 'created' | 'updated' | 'skipped' {\n const dir = resolve(cwd, target.configDir);\n const filePath = resolve(dir, target.configFile);\n\n // For .cursor and .windsurf, only write if the directory already exists (IDE is in use)\n // For Claude Code (.mcp.json in root), always write\n if (target.configDir !== '.' && !existsSync(dir)) {\n return 'skipped';\n }\n\n let existing: Record<string, unknown> = {};\n if (existsSync(filePath)) {\n try {\n existing = JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n // Corrupted file — overwrite\n }\n }\n\n const servers = (existing[target.serversKey] ?? {}) as Record<string, unknown>;\n const hadEntry = 'validate.qa' in servers;\n servers['validate.qa'] = entry;\n existing[target.serversKey] = servers;\n\n // Ensure directory exists (for .cursor/mcp.json on first setup)\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\\n', 'utf-8');\n return hadEntry ? 'updated' : 'created';\n}\n\nexport function runInit() {\n const config = parseInitArgs(process.argv.slice(3)); // skip: node, script, 'init'\n const entry = buildMcpEntry(config);\n const cwd = process.cwd();\n\n console.log('');\n console.log(' Validate.QA — MCP Setup');\n console.log(' ─────────────────────────');\n console.log('');\n\n let anyWritten = false;\n\n for (const target of IDE_TARGETS) {\n const result = writeConfigForIDE(cwd, target, entry);\n if (result === 'skipped') {\n continue;\n }\n const file = join(target.configDir, target.configFile);\n if (result === 'created') {\n console.log(` ✓ ${target.name} — created ${file}`);\n } else {\n console.log(` ✓ ${target.name} — updated ${file}`);\n }\n anyWritten = true;\n }\n\n if (!anyWritten) {\n // No IDE dirs found — create Claude Code config as default\n const target = IDE_TARGETS[0]; // Claude Code\n writeConfigForIDE(cwd, target, entry);\n console.log(` ✓ Created ${target.configFile} (works with Claude Code)`);\n console.log('');\n console.log(' Tip: Create a .cursor/ or .windsurf/ directory and re-run to set up those IDEs too.');\n }\n\n console.log('');\n console.log(' Next: restart your IDE or reload MCP servers.');\n console.log(' Then ask your AI: \"list my tests\" to verify it works.');\n console.log('');\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { resolveConfig } from './config.js';\nimport { ValidateQAClient } from './api-client.js';\nimport { registerAllTools } from './tools/index.js';\n\n// `npx @validate.qa/mcp init ...` — auto-create IDE config files\nif (process.argv[2] === 'init') {\n const { runInit } = await import('./init.js');\n runInit();\n process.exit(0);\n}\n\nconst config = resolveConfig();\nconst client = new ValidateQAClient(config.apiUrl, config.token, config.projectId);\n\nconst server = new McpServer(\n { name: 'validate.qa', version: '1.0.0' },\n { instructions: `Validate.QA — AI-powered QA platform: generates, runs, and heals Playwright E2E tests.\n\n## Capabilities\n- Setup projects via setup_project (read source code first)\n- Write tests via create_test (read routes, components, API handlers first)\n- Manage tests: list, view, edit, run, delete (DRAFT/ACTIVE/DISABLED/QUARANTINED)\n- Heal failing tests: AI replays in browser, fixes code or flags app bugs\n- Schedule recurring runs (hourly-weekly) with auto-heal\n\n## Workflows\n1. New project: READ source, setup_project (features, baseUrl, testCredentials, appBrief). Per feature: READ code, create_test, add_api_endpoints.\n2. Check health: list_features (failingTests > 0), get_test_failures per feature, edit_test_code or heal_test.\n3. Add coverage: READ code, create_feature, create_test x N (suite = feature name), add_api_endpoints.\n4. Nightly automation: create_schedule (daily, autoHeal: true).\n5. After code change: batch_run_tests, then list_features + get_test_failures.\n\n## Automatic lifecycle\nDo NOT call run_test after create_test (auto-queued) or heal_test after failures (auto-heals).\nDo NOT check run status in a loop — call get_test_failures after a reasonable wait to see what failed.\nLifecycle: create > auto-run > pass=ACTIVE, fail=auto-heal > heal fail=@needs-review.\nDO call add_api_endpoints per feature (not automatic).\nDO call run_security_audit after UI tests to analyze captured API traffic. If it errors about missing data, tell the developer to click \"Security Audit\" in the Dashboard to run a live browser capture first. Use list_security_findings to review results.\n\n## Rate limits\n50 calls/60s. Reads parallel OK, writes sequential. On 429, wait 30-60s.\n\n## Triaging failures\n- App broken: flag_as_bug(testId, description). Selector/timing: heal_test. Bad test logic: edit_test_code.\n- Unsure: heal_test first — it detects app bugs automatically.\n\n## Using the codebase\nYou have full source code access. Always READ before writing: routes for URLs, components for selectors, API handlers for endpoints.\n\n## Before writing any test — preflight check\n\nRead project://test-conventions first. Check the variables section:\n- BASE_URL must be set (the runner needs it). If missing, call setup_project or set_variable.\n- TEST_USER_EMAIL and TEST_USER_PASSWORD must be set if tests require login. If missing, ask the developer for credentials and call set_variable.\n- If starting fresh (no features, no variables), use workflow #1 (setup_project) before anything else.\n\n## Critical rules — see project://test-conventions for full details and code templates\n- URLs: relative only, never hardcoded domains — runner sets baseURL\n- Credentials: process.env.TEST_USER_EMAIL! (non-null assertion) — never ?? or || fallbacks\n- Registration: testuser-\\${Date.now()}@example.com\n- Suite name must match feature name exactly (case-sensitive)\n- @auth must NOT combine with @smoke/@regression/@needs-auth/@critical\n- Each test = own browser process; @needs-auth tests MUST login inline\n- API tests: test.describe.serial() with __QA_HTTP__ log after every request\n- UI tests: console.log() milestones after key actions\n- create_test/edit_test_code validate code — fix warnings before moving on\n\n## Setting up coverage\n1. SCAN: Read router, nav, pages, API handlers — map ALL routes and features.\n2. SETUP: setup_project with features, pages, baseUrl, testCredentials, appBrief.\n3. WRITE: One feature at a time. For EACH test, read the SPECIFIC source code first (see codeReadingRequirements in test-conventions). For login blocks: read the login component. For UI tests: read the page component. For API tests: read the route handler. Templates are structure guides — selectors MUST come from the real code. Use create_test for BOTH UI and API tests. Then call add_api_endpoints.\n4. VERIFY: get_coverage_summary + list_features for failures + get_test_failures.\n\nTest real user flows (forms, validation, errors). \"Page loads\" tests are worthless.` },\n);\n\n// Register all QA platform tools\nregisterAllTools(server, client);\n\n// ── MCP Resources (live project context for AI assistants) ────────────\n\nserver.resource(\n 'Project Overview',\n 'project://overview',\n { description: 'Live project state: test counts, pass rates, runner status, features, coverage gaps, and configuration. Read this first to understand the current project.' },\n async () => {\n try {\n const [overview, coverage, stats] = await Promise.all([\n client.executeTool('get_project_overview'),\n client.executeTool('get_coverage_summary').catch(() => null),\n client.executeTool('get_regression_stats').catch(() => null),\n ]);\n const snapshot = {\n overview,\n coverage,\n regressionStats: stats,\n retrievedAt: new Date().toISOString(),\n };\n return { contents: [{ uri: 'project://overview', mimeType: 'application/json', text: JSON.stringify(snapshot, null, 2) }] };\n } catch (err) {\n return { contents: [{ uri: 'project://overview', mimeType: 'text/plain', text: `Failed to load project overview: ${err instanceof Error ? err.message : String(err)}` }] };\n }\n },\n);\n\nserver.resource(\n 'Test Conventions',\n 'project://test-conventions',\n { description: 'Project-specific test conventions: environment variables, credential patterns, tag meanings, and registration email rules. Read this before writing or editing any test.' },\n async () => {\n try {\n const vars = await client.executeTool('get_variables') as { variables: Record<string, { value: string; secret: boolean }> };\n const varNames = Object.keys(vars.variables || {});\n const hasAuth = varNames.includes('TEST_USER_EMAIL') && varNames.includes('TEST_USER_PASSWORD');\n\n const conventions = {\n codeReadingRequirements: {\n CRITICAL: 'Templates below are STRUCTURE GUIDES — every selector, route, and endpoint MUST come from reading the actual source code. Never copy a template verbatim.',\n forLogin: 'BEFORE writing any @needs-auth login block: READ the login/auth page component. Find: (1) the login route (/login, /auth/login, /signin?), (2) the flow type (single-step or multi-step with Continue/Next button?), (3) exact input selectors (labels, placeholders, input types — is it getByLabel, getByPlaceholder, or input[type=password]?), (4) exact submit button text, (5) post-login redirect URL. Every app is different — single-step, multi-step, OAuth buttons, magic links.',\n forUITests: 'BEFORE writing a UI test for a page: READ the page component. Find: headings (text + level), form fields (labels, placeholders, types), buttons (text, role), navigation elements, validation error messages, success/error states. Use the exact text from the component — do not guess.',\n forAPITests: 'BEFORE writing an API test: READ the route handler. Find: the endpoint path, HTTP method, request body schema (what fields, what types), response body shape (is it flat or wrapped in { data: {...} }?), auth requirements (Bearer token? Cookie? API key?), error response shapes.',\n forAPIMapping: 'BEFORE calling add_api_endpoints: READ ALL route handlers for the feature. Register every endpoint with accurate method, path, requestShape, responseShapes, requiresAuth.',\n },\n credentials: {\n rule: 'Never hardcode credentials in test code',\n loginFields: hasAuth\n ? 'Use process.env.TEST_USER_EMAIL! and process.env.TEST_USER_PASSWORD!'\n : 'No credentials configured — set TEST_USER_EMAIL and TEST_USER_PASSWORD via set_variable',\n registrationFields: 'MUST use unique email per run: `testuser-${Date.now()}@example.com` — reusing emails causes \"already registered\" failures',\n registrationPassword: \"Use 'TestPass123!' for new account passwords (meets typical strength rules)\",\n },\n variables: {\n configured: varNames,\n baseUrl: (vars.variables as any)?.BASE_URL?.value || 'Not set',\n },\n tags: {\n '@auth': 'Tests the login/auth flow itself. MUST NOT combine with @smoke, @regression, @needs-auth, @critical. CAN include @discovered.',\n '@needs-auth': 'Tests for authenticated pages — MUST include inline login block. CAN combine with @smoke, @regression, @critical.',\n '@smoke': 'Quick sanity checks — run on every deploy',\n '@regression': 'Full regression suite tests',\n '@critical': 'Business-critical paths (informational label — no special suite filtering)',\n '@api': 'API-level tests. Runs in its own project, excluded from suite/smoke/regression runs',\n '@discovered': 'Auto-added to tests generated by AI discovery',\n '@generated': 'Auto-added by AI discovery and API test generation',\n '@manual': 'Default tag for tests created via create_test when no tags specified',\n '@flaky': 'Auto-added after repeated failures. Excluded from suite/smoke/regression runs',\n '@bug-found': 'Added when healer detects an app bug. Test is auto-quarantined',\n '@needs-review': 'Added when DRAFT test fails healing. Requires human attention',\n '@heal-skip': 'Added by healer when test is permanently unfixable. Prevents future auto-heal',\n },\n executionModel: {\n isolation: 'Each test runs as its own separate Playwright process in a fresh browser. No shared state, no storageState, no global setup.',\n authRequired: 'Every @needs-auth test MUST include its own login block. Do NOT suggest storageState or performLogin() — those only work in the regression suite.',\n concurrency: 'Tests run in parallel — runner claims up to 10 per poll, launched 3s apart (~30s per batch). Multiple batches can overlap. Use test.setTimeout(120_000) if the target app is slow under concurrent login load.',\n },\n lifecycle: {\n DRAFT: 'New or unverified test — not included in scheduled runs',\n ACTIVE: 'Verified and running — included in batch/suite executions',\n DISABLED: 'Manually paused — excluded from all runs',\n QUARANTINED: 'Auto-quarantined after repeated failures',\n },\n logging: {\n CRITICAL: 'EVERY request.get/post/put/patch/delete call MUST be followed by a __QA_HTTP__ log line. This is how the platform surfaces request/response data in the Network panel. Missing this = invisible API calls.',\n apiPattern: \"const body = await res.json();\\nconsole.log('__QA_HTTP__:' + JSON.stringify({ method: 'POST', url: '/api/path', status: res.status(), requestBody: { field: value, password: '[REDACTED]' }, responseBody: body }));\",\n rules: [\n 'Call res.json() ONCE, store in body variable — reuse for log and assertions (calling twice throws)',\n 'Log AFTER the request, BEFORE assertions',\n 'Redact sensitive fields: password: \"[REDACTED]\", token: \"[REDACTED]\"',\n 'Use the literal URL string (same as the request call), not a variable',\n 'UI tests using page fixture do NOT need __QA_HTTP__ — only request fixture tests',\n ],\n uiMilestones: \"UI tests (page fixture): add plain console.log() at key milestones — e.g. console.log('Navigated to login page') after page.goto(). These appear in the Console Output panel.\",\n uiLoggingRules: [\n 'Log AFTER the action, not before — confirms the action succeeded',\n 'Include dynamic values where useful: console.log(\"Current URL:\", page.url())',\n 'Keep messages short and human-readable — they show verbatim in the UI',\n 'Do NOT log sensitive values (passwords, tokens)',\n ],\n apiTestStructure: 'Use test.describe.serial() for API tests sharing auth. Independent validation tests (401, 404 checks) go in a SEPARATE non-serial describe block — do not mix them with the auth-dependent serial flow.',\n },\n bestPractices: [\n 'Every @needs-auth test must start with a login block (goto login, fill creds, click submit, waitForURL)',\n 'Use { timeout: 30000 } on waitForURL after login — target apps can be slow under concurrent test load',\n 'Keep login blocks minimal — no assertions on the login page (that is what @auth tests are for)',\n 'After login, page.goto() directly to the page being tested — do not navigate via UI clicks',\n 'Use role-based selectors (getByRole, getByLabel) over CSS selectors',\n 'Registration tests must generate unique email with Date.now()',\n 'Group tests into suites matching feature names exactly (case-sensitive)',\n 'Do NOT suggest storageState or performLogin() for create_test — those only work in the regression suite',\n 'EVERY API test (request fixture) MUST include __QA_HTTP__ log after each request call — see logging section above',\n 'Avoid waitForLoadState(\"networkidle\") — apps with SSE, WebSocket, or analytics connections may never reach idle, causing 60s timeouts. Wait for specific elements instead.',\n 'For headings, use exact: true or level: 1 — getByRole(\"heading\", { name: \"X\" }) matches ALL h1-h6 containing \"X\", causing strict mode violations',\n ],\n pitfalls: {\n CRITICAL: 'These are the top causes of test failures. Read before writing ANY test.',\n strictModeViolations: \"Playwright throws when a locator resolves to 2+ elements. Common causes: getByLabel('Password') matches input AND 'Show password' toggle; getByRole('button', { name: 'Continue' }) matches 'Continue' AND 'Continue with GitHub'. Prevention: use input[type=\\\"password\\\"] for password fields, use { exact: true } for ambiguous button text, scope to a parent container (page.locator('form#login').getByLabel('Email')).\",\n networkIdle: \"NEVER use waitForLoadState('networkidle') — waits for 500ms of zero network activity, which is fragile and causes 30s+ timeouts in apps with polling, analytics, or WebSockets. Use waitForURL() or wait for a specific element instead.\",\n headingStrictMode: \"getByRole('heading', { name: 'Dashboard' }) can match multiple headings (h1 + h2 + breadcrumb). Add { level: 1 } or { exact: true } to disambiguate. Same for getByLabel('Password') matching input AND 'Show password' toggle.\",\n postLoginRedirect: \"NEVER assert exact URL after login (e.g. expect(page).toHaveURL('/dashboard')) — apps redirect to different routes per user/role. Use: await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 30000 })\",\n resJsonOnce: \"res.json() consumes the response body stream — calling it twice throws. Store result: const body = await res.json(); then reuse body for logging and assertions.\",\n hiddenInputs: \"Many UI frameworks use visually hidden <input> with custom overlays. page.check() fails because the overlay intercepts clicks. Click the <label> or visible wrapper: page.getByText('Remember me').click()\",\n apiResponseShapes: \"API endpoints often wrap data in a property ({ user: {...} }, { data: [...] }). Read the route handler's res.json() call to see the actual shape. If the handler does res.json({ ticket }), then body is { ticket: { id, ... } } — NOT { id, ... }.\",\n invalidIds: \"Not all routes validate ID format before querying the database. If a route passes the raw ID to an ORM query and gets null back, it returns 404 'not found' — not 400 'bad request'. If unsure, expect either: expect([400, 404]).toContain(res.status())\",\n hardcodedUrls: \"NEVER hardcode full URLs in test code. For UI tests: always use relative paths — page.goto('/login'). The runner sets baseURL from the project's BASE_URL variable. For API tests: use relative paths if the API is on the same domain as the frontend (request.post('/api/auth/login')). If the API is on a SEPARATE domain, use process.env.BASE_API_URL! as a test variable (set via set_variable) and construct URLs: request.post(process.env.BASE_API_URL! + '/auth/login'). NEVER hardcode 'https://example.com/...' in test code — use env vars or relative paths.\",\n credentialFallbacks: \"NEVER use process.env.TEST_USER_EMAIL ?? 'fallback' or || 'fallback'. Use ! (non-null assertion). Missing variable = test should fail immediately, not silently use hardcoded value.\",\n },\n templates: {\n needsAuth: \"test('Feature Name — Test Name', {\\n tag: ['@needs-auth', '@smoke'],\\n}, async ({ page }) => {\\n // Login block — ADAPTED from reading the app's login page component\\n // YOU MUST READ the login page component BEFORE writing this block\\n await page.goto('/login'); // read the router for actual login route\\n await page.getByRole('textbox', { name: /email/i }).fill(process.env.TEST_USER_EMAIL!);\\n await page.locator('input[type=\\\"password\\\"]').waitFor({ state: 'visible', timeout: 10000 });\\n await page.locator('input[type=\\\"password\\\"]').fill(process.env.TEST_USER_PASSWORD!);\\n await page.getByRole('button', { name: /sign in|log in|submit/i }).click();\\n await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 30000 });\\n console.log('Logged in, current URL:', page.url());\\n\\n // Actual test\\n await page.goto('/feature-path');\\n console.log('On feature page');\\n await expect(page.getByRole('heading', { name: /feature name/i })).toBeVisible();\\n});\",\n authTest: \"test('Login — Happy Path', {\\n tag: ['@auth'],\\n}, async ({ page }) => {\\n await page.goto('/auth/login');\\n // test the login form itself — selectors from reading the actual component\\n});\",\n apiSerial: \"test.describe.serial('Feature Name', () => {\\n let token: string;\\n test('authenticate', async ({ request }) => {\\n const res = await request.post('/api/auth/login', {\\n data: { email: process.env.TEST_USER_EMAIL!, password: process.env.TEST_USER_PASSWORD! }\\n });\\n const body = await res.json();\\n console.log('__QA_HTTP__:' + JSON.stringify({\\n method: 'POST', url: '/api/auth/login', status: res.status(),\\n requestBody: { email: process.env.TEST_USER_EMAIL!, password: '[REDACTED]' },\\n responseBody: body,\\n }));\\n expect(res.status()).toBe(200);\\n token = body.access_token; // ← adapt to your API (e.g. body.token, body.jwt)\\n });\\n test('get profile', async ({ request }) => {\\n const res = await request.get('/api/users/profile', {\\n headers: { Authorization: `Bearer ${token}` }\\n });\\n const body = await res.json();\\n console.log('__QA_HTTP__:' + JSON.stringify({\\n method: 'GET', url: '/api/users/profile', status: res.status(),\\n responseBody: body,\\n }));\\n expect(res.status()).toBe(200);\\n });\\n});\",\n apiEndpointMapping: '// After reading an Express route handler like:\\n// router.post(\\'/api/auth/login\\', async (req, res) => { ... })\\n// Call add_api_endpoints with:\\n{\\n featureName: \"Authentication\",\\n endpoints: [{\\n method: \"POST\",\\n path: \"/api/auth/login\",\\n requiresAuth: false,\\n requestShape: { \"email\": \"string\", \"password\": \"string\" },\\n responseShapes: { \"200\": { \"token\": \"string\", \"user\": \"object\" }, \"401\": { \"error\": \"string\" } }\\n }]\\n}',\n uiWithMilestones: \"test('Feature — Happy Path', async ({ page }) => {\\n await page.goto('/login');\\n console.log('Navigated to login page');\\n\\n await page.getByRole('textbox', { name: /email/i }).fill(process.env.TEST_USER_EMAIL!);\\n await page.locator('input[type=\\\"password\\\"]').fill(process.env.TEST_USER_PASSWORD!);\\n await page.getByRole('button', { name: /sign in/i }).click();\\n console.log('Credentials submitted');\\n\\n await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 15000 });\\n console.log('Logged in, current URL:', page.url());\\n\\n await page.goto('/feature-path');\\n console.log('On feature page');\\n});\",\n },\n testQuality: [\n 'Test actual user flows, not just \"page loads\" — a test that only checks goto + heading visible is nearly worthless',\n 'Test form submission: fill fields, submit, verify success/redirect',\n 'Test validation: leave required fields empty, enter invalid data, verify error messages',\n 'Test error states: wrong password, expired session, invalid IDs',\n 'Test the happy path end-to-end: login → navigate → perform action → verify result',\n 'Each test should assert something meaningful about the application behavior',\n ],\n securityAudit: {\n workflow: 'After creating UI tests for a feature, call run_security_audit to analyze captured API traffic for auth issues, data exposure, and vulnerabilities.',\n prerequisite: 'Requires prior browser-based test execution (the auto-verification run from create_test counts). If run_security_audit errors about missing data, tell the developer to click \"Security Audit\" in the Tests dropdown on the Dashboard — this launches a live browser to capture real network traffic (HAR) and runs analysis automatically.',\n reviewing: 'Use list_security_findings to review results. Filter by severity (critical/high/medium/low/info) or status (open/acknowledged/resolved/false-positive). Critical and high findings should be flagged to the developer immediately.',\n },\n keyConcepts: {\n features: 'Group related pages and tests (e.g., \"Authentication\" covers /auth/login, /auth/register). MUST include page routes.',\n pages: 'Route URLs on a feature — used for coverage tracking and site map (e.g., [\"/login\", \"/register\"])',\n suites: 'Group tests for batch execution — suite name MUST match feature name exactly (case-sensitive)',\n runner: 'Executes tests — can be local (developer machine) or cloud',\n testData: 'Key-value variables injected into tests (e.g., credentials, URLs)',\n },\n retrievedAt: new Date().toISOString(),\n };\n return { contents: [{ uri: 'project://test-conventions', mimeType: 'application/json', text: JSON.stringify(conventions, null, 2) }] };\n } catch (err) {\n return { contents: [{ uri: 'project://test-conventions', mimeType: 'text/plain', text: `Failed to load conventions: ${err instanceof Error ? err.message : String(err)}` }] };\n }\n },\n);\n\n// Connect via stdio transport\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nexport interface ValidateQAConfig {\n apiUrl: string;\n token: string;\n projectId: string;\n}\n\nfunction parseArgs(argv: string[]): Record<string, string> {\n const args: Record<string, string> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith('--') && i + 1 < argv.length) {\n const key = arg.slice(2);\n args[key] = argv[++i];\n }\n }\n return args;\n}\n\nfunction loadDotFile(): Partial<ValidateQAConfig> {\n try {\n const content = readFileSync(resolve(process.cwd(), '.validateqa.json'), 'utf-8');\n const json = JSON.parse(content);\n return {\n apiUrl: typeof json.apiUrl === 'string' ? json.apiUrl : undefined,\n projectId: typeof json.projectId === 'string' ? json.projectId : undefined,\n };\n } catch {\n return {};\n }\n}\n\nexport function resolveConfig(): ValidateQAConfig {\n const cliArgs = parseArgs(process.argv.slice(2));\n const dotFile = loadDotFile();\n\n const apiUrl =\n cliArgs['api-url'] ??\n process.env.VALIDATEQA_API_URL ??\n dotFile.apiUrl;\n\n const token =\n cliArgs['token'] ??\n process.env.VALIDATEQA_TOKEN;\n\n const projectId =\n cliArgs['project-id'] ??\n process.env.VALIDATEQA_PROJECT_ID ??\n dotFile.projectId;\n\n if (!apiUrl) {\n console.error('Missing required config: api-url (--api-url, VALIDATEQA_API_URL, or .validateqa.json)');\n process.exit(1);\n }\n if (!token) {\n console.error('Missing required config: token (--token or VALIDATEQA_TOKEN)');\n process.exit(1);\n }\n if (!projectId) {\n console.error('Missing required config: project-id (--project-id, VALIDATEQA_PROJECT_ID, or .validateqa.json)');\n process.exit(1);\n }\n\n return { apiUrl: apiUrl.replace(/\\/$/, ''), token, projectId };\n}\n","export class ValidateQAClient {\n constructor(\n private baseUrl: string,\n private token: string,\n private projectId: string,\n ) {}\n\n async executeTool(tool: string, args: Record<string, unknown> = {}): Promise<unknown> {\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api/mcp/execute`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.token}`,\n 'X-Project-Id': this.projectId,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ tool, args }),\n signal: AbortSignal.timeout(120_000),\n });\n } catch (err) {\n // Network errors, DNS failures, timeouts\n const cause = err instanceof Error && err.cause instanceof Error ? err.cause.message : '';\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Connection failed: ${msg}${cause ? ` (${cause})` : ''}`);\n }\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error((body as any).error || `API error ${res.status}`);\n }\n\n return res.json();\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/health`, {\n signal: AbortSignal.timeout(5000),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ValidateQAClient } from '../api-client.js';\n\ntype ToolAnnotations = {\n readOnlyHint?: boolean;\n destructiveHint?: boolean;\n idempotentHint?: boolean;\n openWorldHint?: boolean;\n};\n\nfunction tool(\n server: McpServer,\n client: ValidateQAClient,\n name: string,\n description: string,\n schema: Record<string, z.ZodTypeAny>,\n annotations?: ToolAnnotations,\n) {\n const handler = async (args: Record<string, unknown>) => {\n try {\n const result = await client.executeTool(name, args);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Error: ${err instanceof Error ? err.message : String(err)}` }],\n isError: true,\n };\n }\n };\n\n if (annotations) {\n server.tool(name, description, schema, annotations, handler);\n } else {\n server.tool(name, description, schema, handler);\n }\n}\n\nexport function registerAllTools(server: McpServer, client: ValidateQAClient) {\n // ── Test Management ──────────────────────────────────────────────────\n\n tool(server, client, 'list_tests',\n 'List tests in the current project. Returns test name, status, suite, tags, confidence, and the last run result. Paginated — use limit/offset to page through all tests.',\n {\n status: z.enum(['DRAFT', 'ACTIVE', 'DISABLED', 'QUARANTINED']).optional().describe('Filter by test lifecycle status'),\n suite: z.string().optional().describe('Filter by suite name'),\n search: z.string().optional().describe('Search test names (case-insensitive substring match)'),\n tag: z.string().optional().describe('Filter by tag (e.g. @regression, @smoke, @auth)'),\n limit: z.number().optional().describe('Max results to return (default: 50, max: 200)'),\n offset: z.number().optional().describe('Number of results to skip for pagination (default: 0)'),\n },\n { readOnlyHint: true },\n );\n\n tool(server, client, 'get_test',\n 'Get details of a specific test: name, status, tags, steps, session, and the 5 most recent run results. Code is truncated to 200 lines — use get_test_code for the full Playwright code.',\n { testId: z.string().describe('The test case ID') },\n { readOnlyHint: true },\n );\n\n tool(server, client, 'run_test',\n 'Queue a test for execution. Creates a PENDING test run that will be picked up by an online runner. Returns the run ID.',\n { testId: z.string().describe('The test case ID to run') },\n );\n\n tool(server, client, 'delete_test',\n 'Permanently delete a test case and all its run history. Cannot be undone.',\n { testId: z.string().describe('The test case ID to delete') },\n { destructiveHint: true },\n );\n\n tool(server, client, 'heal_test',\n 'Queue an AI healing run for a failing test. The AI agent replays the test in a real browser, analyzes the failure, and either fixes the Playwright code OR detects an application bug (quarantines the test with @bug-found if the app itself is broken, not the test).',\n { testId: z.string().describe('The failing test case ID') },\n );\n\n tool(server, client, 'edit_test_code',\n 'Update the Playwright code, name, or tags of an existing test case. Read project://test-conventions for patterns and pitfalls. Code is validated — fix any warnings returned.',\n {\n testId: z.string().describe('The test case ID to edit'),\n playwrightCode: z.string().optional().describe('New Playwright test code (full file replacement)'),\n name: z.string().optional().describe('New test name'),\n tags: z.array(z.string()).optional().describe('New tags array (e.g. [\"@regression\", \"@smoke\"])'),\n },\n );\n\n tool(server, client, 'update_test_status',\n 'Change the lifecycle status of one or more tests (e.g. promote DRAFT→ACTIVE, disable a flaky test).',\n {\n testIds: z.array(z.string()).describe('Test IDs to update'),\n status: z.enum(['DRAFT', 'ACTIVE', 'DISABLED', 'QUARANTINED']).describe('New status to set'),\n },\n );\n\n // ── Test Runs ────────────────────────────────────────────────────────\n\n tool(server, client, 'get_test_run',\n 'Get the status and result of a specific test run, including error details if it failed. Can look up by runId or get the latest run for a testId.',\n {\n runId: z.string().optional().describe('The test run ID'),\n testId: z.string().optional().describe('Get the most recent run for this test case ID'),\n },\n { readOnlyHint: true },\n );\n\n // Custom handler — returns MCP image content blocks so the AI can see screenshots\n server.tool(\n 'get_test_screenshots',\n 'Get screenshots from a specific test run as images the AI can actually see. Use this to visually inspect what happened during a failed or passing run.',\n {\n runId: z.string().describe('The test run ID to fetch screenshots from'),\n limit: z.number().optional().describe('Max screenshots to return (default: 5, max: 10)'),\n },\n { readOnlyHint: true },\n async (args) => {\n try {\n const result = await client.executeTool('get_test_screenshots', args) as {\n runId: string;\n status: string;\n total: number;\n returned: number;\n screenshots: Array<{ index: number; data: string; mimeType: string }>;\n };\n\n if (!result.screenshots || result.screenshots.length === 0) {\n return {\n content: [{\n type: 'text' as const,\n text: `No screenshots available for run ${result.runId} (status: ${result.status})`,\n }],\n };\n }\n\n const content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }> = [\n {\n type: 'text' as const,\n text: `Run ${result.runId} — status: ${result.status} — showing ${result.returned} of ${result.total} screenshot(s)`,\n },\n ];\n\n for (const shot of result.screenshots) {\n content.push({ type: 'image' as const, data: shot.data, mimeType: shot.mimeType });\n }\n\n return { content };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Error: ${err instanceof Error ? err.message : String(err)}` }],\n isError: true,\n };\n }\n },\n );\n\n tool(server, client, 'cancel_test_run',\n 'Cancel a pending or running test run. Can cancel by run ID or cancel the latest running batch.',\n {\n runId: z.string().optional().describe('The specific test run ID to cancel'),\n cancelBatch: z.boolean().optional().describe('If true, cancel the latest batch run instead'),\n },\n );\n\n tool(server, client, 'batch_run_tests',\n 'Run multiple tests from the database individually. Filter by test IDs or suite name; omit both to run all ACTIVE tests. Each test executes in its own browser.',\n {\n testIds: z.array(z.string()).optional().describe('Specific test IDs to run'),\n suite: z.string().optional().describe('Run all active tests in this suite'),\n },\n );\n\n // ── Features & API Endpoints ──────────────────────────────────────────\n\n tool(server, client, 'list_features',\n 'List all features with test counts and failure counts (last 24h). Use failingTests > 0 to identify which features need attention, then call get_test_failures({ suite: featureName }) to drill into the full error details for that feature.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'list_api_endpoints',\n 'List captured API endpoints for the current project. Optionally scope to a specific feature name.',\n {\n featureName: z.string().optional().describe('Optional feature name to scope the endpoint list'),\n },\n { readOnlyHint: true },\n );\n\n // ── Infrastructure ───────────────────────────────────────────────────\n\n tool(server, client, 'get_runner_health',\n 'Check whether the project\\'s configured runners are online, including available device capabilities.',\n {},\n { readOnlyHint: true },\n );\n\n // ── Reports & Observability ──────────────────────────────────────────\n\n tool(server, client, 'get_project_overview',\n 'Get a workspace-level QA overview: test counts, pass rates, runner readiness, features, coverage gaps, variables, schedules, and notifications.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'get_reports_summary',\n 'Get a high-level report: total tests (active, never-run), 7-day pass rate, today\\'s run count, and the 10 most recent test run results.',\n {},\n { readOnlyHint: true },\n );\n\n // ── Schedules (Admin) ────────────────────────────────────────────────\n\n tool(server, client, 'list_schedules',\n 'List all scheduled test runs in the project.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'create_schedule',\n 'Create a new recurring test schedule. Runs all matching tests from the database on the configured interval.',\n {\n name: z.string().describe('Name for the schedule (e.g. \"Nightly smoke tests\")'),\n mode: z.enum(['batch']).describe('Run mode: batch (runs project tests from the database)'),\n intervalMinutes: z.union([z.literal(60), z.literal(360), z.literal(720), z.literal(1440), z.literal(2880), z.literal(10080)]).describe('Interval in minutes: 60 (hourly), 360 (6h), 720 (12h), 1440 (daily), 2880 (2 days), 10080 (weekly)'),\n tags: z.array(z.string()).optional().describe('Tags to filter which tests run (e.g. [\"@smoke\"]). Omit to run all ACTIVE tests.'),\n autoHeal: z.boolean().optional().describe('Automatically heal failing tests after each run'),\n enabled: z.boolean().optional().describe('Whether the schedule is active (default: true)'),\n runImmediately: z.boolean().optional().describe('Whether to trigger the first run immediately (default: true)'),\n },\n );\n\n tool(server, client, 'update_schedule',\n 'Update an existing test schedule.',\n {\n scheduleId: z.string().describe('The schedule ID to update'),\n name: z.string().optional().describe('New name for the schedule'),\n intervalMinutes: z.union([z.literal(60), z.literal(360), z.literal(720), z.literal(1440), z.literal(2880), z.literal(10080)]).optional().describe('New interval in minutes'),\n suite: z.string().optional().describe('New suite name to filter tests'),\n tags: z.array(z.string()).optional().describe('New tag filter'),\n autoHeal: z.boolean().optional().describe('Toggle auto-heal'),\n enabled: z.boolean().optional().describe('Enable or disable the schedule'),\n },\n );\n\n tool(server, client, 'delete_schedule',\n 'Delete (remove) a test schedule permanently.',\n { scheduleId: z.string().describe('The schedule ID to delete') },\n { destructiveHint: true },\n );\n\n tool(server, client, 'run_schedule_now',\n 'Trigger a scheduled run immediately instead of waiting for the next scheduled time.',\n { scheduleId: z.string().describe('The schedule ID to trigger') },\n );\n\n // ── Configuration ────────────────────────────────────────────────────\n\n tool(server, client, 'get_variables',\n 'Get all test variables (environment variables) for the current project. Secrets are redacted.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'set_variable',\n 'Set or update a test variable (environment variable) for the project. Used to configure base URLs, credentials, etc.',\n {\n key: z.string().describe('Variable name (e.g. BASE_URL, TEST_USER_EMAIL, API_KEY)'),\n value: z.string().describe('Variable value. Set value to empty string to remove a variable.'),\n secret: z.boolean().optional().describe('Whether this variable is a secret (will be redacted in UI)'),\n },\n );\n\n tool(server, client, 'create_test',\n 'Create a new test case with Playwright code. Read project://test-conventions for templates and pitfalls BEFORE writing code. The test starts as DRAFT — auto-runs and auto-promotes to ACTIVE on pass. IMPORTANT: (1) Set suite to the EXACT feature name. (2) After creating all tests for a feature, you MUST call add_api_endpoints.',\n {\n name: z.string().describe('Test name (e.g. \"Login — Happy Path\")'),\n playwrightCode: z.string().describe('Full Playwright test code'),\n suite: z.string().optional().describe('MUST match an existing feature name exactly to link the test to that feature (e.g. \"Authentication\", not \"Auth Tests\")'),\n tags: z.array(z.string()).optional().describe('Tags (e.g. [\"@smoke\", \"@auth\"])'),\n },\n );\n\n // ── Features ─────────────────────────────────────────────────────────\n\n tool(server, client, 'get_feature',\n 'Get detailed info about a feature including its pages, test plans, existing tests with lastRunStatus, and AI insights.',\n { featureName: z.string().describe('The feature name (case-insensitive match)') },\n { readOnlyHint: true },\n );\n\n tool(server, client, 'create_feature',\n 'Create a new feature with page routes. Read the developer\\'s route files to provide accurate page URLs. Pages are used for coverage tracking and site map.',\n {\n name: z.string().describe('Feature name (e.g. \"Checkout Flow\")'),\n description: z.string().optional().describe('What the feature does and what to test'),\n pages: z.array(z.string()).describe('Page routes this feature covers (e.g. [\"/checkout\", \"/checkout/payment\"]). Required for test generation.'),\n criticality: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Feature criticality level (default: medium)'),\n },\n );\n\n // ── Full Test Code ───────────────────────────────────────────────────\n\n tool(server, client, 'get_test_code',\n 'Get the full (untruncated) Playwright test code for a test case. Use this when you need to read, analyze, or prepare edits for the complete test code.',\n { testId: z.string().describe('The test case ID') },\n { readOnlyHint: true },\n );\n\n // ── Project Settings ─────────────────────────────────────────────────\n\n tool(server, client, 'set_runner_preference',\n 'Switch between cloud and local runner for test execution.',\n { preference: z.enum(['cloud', 'local']).describe('Runner type: cloud (shared infrastructure) or local (runs on your machine)') },\n );\n\n tool(server, client, 'toggle_auto_heal',\n 'Enable or disable automatic AI healing when tests fail in batch or suite runs.',\n { enabled: z.boolean().describe('true to enable auto-heal, false to disable') },\n );\n\n // ── Test Data ────────────────────────────────────────────────────────\n\n tool(server, client, 'list_test_data',\n 'List all test data sets in the project. Each set contains key-value pairs injected into AI prompts and test runs.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'create_test_data',\n 'Create a new test data set with key-value pairs. Max 200 entries per set.',\n {\n name: z.string().describe('Name of the test data set (max 100 chars)'),\n description: z.string().optional().describe('Optional description (max 1000 chars)'),\n data: z.record(z.string()).describe('Key-value pairs (e.g. {\"email\": \"test@example.com\", \"password\": \"secret\"})'),\n },\n );\n\n tool(server, client, 'delete_test_data',\n 'Delete a test data set by ID.',\n { dataSetId: z.string().describe('The test data set ID to delete') },\n { destructiveHint: true },\n );\n\n // ── Feature Management ───────────────────────────────────────────────\n\n tool(server, client, 'update_feature',\n 'Update a feature\\'s name, description, pages, or criticality.',\n {\n featureId: z.string().describe('The feature ID to update'),\n name: z.string().optional().describe('New feature name'),\n description: z.string().optional().describe('New description'),\n pages: z.array(z.string()).optional().describe('Updated page routes'),\n criticality: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Feature criticality level'),\n },\n );\n\n tool(server, client, 'archive_feature',\n 'Archive a feature (soft delete). Archived features are hidden from the dashboard but can be restored.',\n { featureId: z.string().describe('The feature ID to archive') },\n { destructiveHint: true },\n );\n\n // ── Site Map ─────────────────────────────────────────────────────────\n\n tool(server, client, 'get_site_map_coverage',\n 'Get site map coverage statistics: total pages, pages with tests, elements covered, coverage percentage, and per-page breakdown.',\n {},\n { readOnlyHint: true },\n );\n\n // ── Issue Triage ─────────────────────────────────────────────────────\n\n tool(server, client, 'list_bugs',\n 'Get all detected bugs: app bugs (quarantined tests with @bug-found), security findings, and flaky tests.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'flag_as_bug',\n 'Mark a failing test as a confirmed application bug — quarantines it with @bug-found and records the bug description. Use when the failure is caused by broken app behaviour, NOT a test script issue. Do NOT use for selector changes or timeout failures — use heal_test or edit_test_code for those.',\n {\n testId: z.string().describe('The failing test case ID'),\n description: z.string().describe('What is broken in the application (e.g. \"POST /api/checkout returns 500 when payment method is valid\")'),\n },\n );\n\n // ── Security Audit ───────────────────────────────────────────────────\n\n tool(server, client, 'run_security_audit',\n 'Run an AI security audit on captured API traffic. Requires prior browser-based test execution (discovery or capture) — analyzes real network traffic, not just registered endpoints. If no traffic exists, the developer can trigger \"Security Audit\" from the Dashboard which launches a live browser to capture it.',\n {},\n );\n\n tool(server, client, 'list_security_findings',\n 'List security audit findings with optional filters. Returns severity, category, status, and affected endpoints.',\n {\n severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).optional().describe('Filter by severity'),\n status: z.enum(['open', 'acknowledged', 'resolved', 'false-positive']).optional().describe('Filter by status'),\n },\n { readOnlyHint: true },\n );\n\n // ── Project Settings ─────────────────────────────────────────────────\n\n tool(server, client, 'set_app_brief',\n 'Set or update the project\\'s application brief — a description of the app used by AI for better test generation.',\n { appBrief: z.string().describe('Application description. Set empty to clear.') },\n );\n\n // ── Coverage & Draft Verification ────────────────────────────────────\n\n tool(server, client, 'get_coverage_summary',\n 'Get feature-level test coverage: planned vs existing vs passing tests, plus API endpoint coverage. Shows uncoveredEndpoints per feature — use this to find which API endpoints still need tests.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'run_draft_tests',\n 'Run all DRAFT tests to verify them. Tests that pass auto-promote to ACTIVE. Optionally scope to a feature.',\n {\n featureName: z.string().optional().describe('Only run drafts for this feature (optional)'),\n },\n );\n\n // ── Project Setup ────────────────────────────────────────────────────\n\n tool(server, client, 'setup_project',\n 'One-shot project setup from codebase knowledge. Sets app brief, base URL, test credentials, and creates features with pages — all in one call. Use this when a developer says \"set up test coverage for my app.\"',\n {\n appBrief: z.string().optional().describe('Description of the application (what it does, key features, tech stack)'),\n baseUrl: z.string().describe('Application base URL (e.g. \"https://myapp.com\" or \"http://localhost:3000\")'),\n testCredentials: z.object({\n email: z.string().optional().describe('Test user email'),\n password: z.string().optional().describe('Test user password'),\n }).optional().describe('Login credentials for test execution'),\n features: z.array(z.object({\n name: z.string().describe('Feature name'),\n description: z.string().optional().describe('What the feature does'),\n pages: z.array(z.string()).describe('Page routes (e.g. [\"/login\", \"/register\"])'),\n criticality: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Feature criticality'),\n })).describe('Features to create with their page routes'),\n },\n );\n\n // ── API Endpoints ───────────────────────────────────────────────────\n\n tool(server, client, 'add_api_endpoints',\n 'Register API endpoints for a feature from codebase knowledge. Read the developer\\'s route handlers to extract methods, paths, request/response shapes. Populates the API intelligence dashboard.',\n {\n featureName: z.string().describe('Feature name to link these endpoints to'),\n endpoints: z.array(z.object({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method'),\n path: z.string().describe('API path (e.g. \"/api/auth/login\")'),\n requestShape: z.record(z.string()).optional().describe('Request body fields with types (e.g. { \"email\": \"string\" })'),\n responseShapes: z.record(z.record(z.string())).optional().describe('Response shapes by status code (e.g. { \"200\": { \"token\": \"string\" } })'),\n requiresAuth: z.boolean().optional().describe('Whether endpoint requires authentication'),\n })).describe('API endpoints to register'),\n },\n );\n\n // ── Failure Diagnostics ──────────────────────────────────────────────\n\n tool(server, client, 'get_test_failures',\n 'Get recent test failures with full error messages, Playwright code, step-level failures, and heal context. Use this to understand why tests are failing.',\n {\n limit: z.number().optional().describe('Max failures to return (default: 50, max: 100)'),\n suite: z.string().optional().describe('Filter by feature/suite name'),\n mode: z.enum(['run', 'heal', 'all']).optional().describe('run = regular failures, heal = heal attempt failures, all = both (default)'),\n sinceHours: z.number().optional().describe('Only failures from the last N hours (default: 24)'),\n },\n { readOnlyHint: true },\n );\n}\n"],"mappings":"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","names":["readFileSync","resolve","config","tool","server","client","runInit"]}
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/index.ts","../src/config.ts","../src/api-client.ts","../src/tools/index.ts"],"sourcesContent":["/**\n * `npx @validate.qa/mcp init` - one-command MCP setup for all supported IDEs.\n *\n * Creates/updates MCP config files so the user never has to manually edit JSON.\n * Detects which IDEs are present and writes the appropriate config for each.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { resolve, join } from 'path';\n\ninterface InitArgs {\n apiUrl: string;\n token: string;\n projectId: string;\n}\n\nfunction parseInitArgs(argv: string[]): InitArgs {\n const args: Record<string, string> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith('--') && i + 1 < argv.length) {\n args[arg.slice(2)] = argv[++i];\n }\n }\n\n const apiUrl = args['api-url'] ?? process.env.VALIDATEQA_API_URL ?? 'https://shared-server.up.railway.app';\n const token = args['token'] ?? process.env.VALIDATEQA_TOKEN;\n const projectId = args['project-id'] ?? process.env.VALIDATEQA_PROJECT_ID;\n\n if (!token) {\n console.error('❌ Missing --token. Copy the full command from your Validate.QA dashboard.');\n process.exit(1);\n }\n if (!projectId) {\n console.error('❌ Missing --project-id. Copy the full command from your Validate.QA dashboard.');\n process.exit(1);\n }\n\n return { apiUrl: apiUrl.replace(/\\/$/, ''), token, projectId };\n}\n\n/** Build the MCP server entry for config files */\nfunction buildMcpEntry(config: InitArgs) {\n return {\n command: 'npx',\n args: [\n '-y', '@validate.qa/mcp@latest',\n '--api-url', config.apiUrl,\n '--token', config.token,\n '--project-id', config.projectId,\n ],\n };\n}\n\ninterface IDETarget {\n name: string;\n configDir: string; // relative to cwd\n configFile: string; // filename inside configDir\n /** Key in the JSON where MCP servers live */\n serversKey: string;\n}\n\nconst IDE_TARGETS: IDETarget[] = [\n { name: 'Claude Code', configDir: '.', configFile: '.mcp.json', serversKey: 'mcpServers' },\n { name: 'Cursor', configDir: '.cursor', configFile: 'mcp.json', serversKey: 'mcpServers' },\n { name: 'Windsurf', configDir: '.windsurf', configFile: 'mcp.json', serversKey: 'mcpServers' },\n { name: 'VS Code', configDir: '.vscode', configFile: 'mcp.json', serversKey: 'servers' },\n];\n\nfunction writeConfigForIDE(cwd: string, target: IDETarget, entry: object): 'created' | 'updated' | 'skipped' {\n const dir = resolve(cwd, target.configDir);\n const filePath = resolve(dir, target.configFile);\n\n // For .cursor and .windsurf, only write if the directory already exists (IDE is in use)\n // For Claude Code (.mcp.json in root), always write\n if (target.configDir !== '.' && !existsSync(dir)) {\n return 'skipped';\n }\n\n let existing: Record<string, unknown> = {};\n if (existsSync(filePath)) {\n try {\n existing = JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n // Corrupted file - overwrite\n }\n }\n\n const servers = (existing[target.serversKey] ?? {}) as Record<string, unknown>;\n const hadEntry = 'validate.qa' in servers;\n servers['validate.qa'] = entry;\n existing[target.serversKey] = servers;\n\n // Ensure directory exists (for .cursor/mcp.json on first setup)\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\\n', 'utf-8');\n return hadEntry ? 'updated' : 'created';\n}\n\nexport function runInit() {\n const config = parseInitArgs(process.argv.slice(3)); // skip: node, script, 'init'\n const entry = buildMcpEntry(config);\n const cwd = process.cwd();\n\n console.log('');\n console.log(' Validate.QA - MCP Setup');\n console.log(' ─────────────────────────');\n console.log('');\n\n let anyWritten = false;\n\n for (const target of IDE_TARGETS) {\n const result = writeConfigForIDE(cwd, target, entry);\n if (result === 'skipped') {\n continue;\n }\n const file = join(target.configDir, target.configFile);\n if (result === 'created') {\n console.log(` ✓ ${target.name} - created ${file}`);\n } else {\n console.log(` ✓ ${target.name} - updated ${file}`);\n }\n anyWritten = true;\n }\n\n if (!anyWritten) {\n // No IDE dirs found - create Claude Code config as default\n const target = IDE_TARGETS[0]; // Claude Code\n writeConfigForIDE(cwd, target, entry);\n console.log(` ✓ Created ${target.configFile} (works with Claude Code)`);\n console.log('');\n console.log(' Tip: Create a .cursor/ or .windsurf/ directory and re-run to set up those IDEs too.');\n }\n\n console.log('');\n console.log(' Next: restart your IDE or reload MCP servers.');\n console.log(' Then ask your AI: \"list my tests\" to verify it works.');\n console.log('');\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { resolveConfig } from './config.js';\nimport { ValidateQAClient } from './api-client.js';\nimport { registerAllTools } from './tools/index.js';\n\n// `npx @validate.qa/mcp init ...` - auto-create IDE config files\nif (process.argv[2] === 'init') {\n const { runInit } = await import('./init.js');\n runInit();\n process.exit(0);\n}\n\nconst config = resolveConfig();\nconst client = new ValidateQAClient(config.apiUrl, config.token, config.projectId);\n\nconst server = new McpServer(\n { name: 'validate.qa', version: '1.0.0' },\n { instructions: `Validate.QA - AI-powered QA platform: generates, runs, and heals Playwright E2E tests.\n\n## Capabilities\n- Setup projects via setup_project (read source code first)\n- Write tests via create_test (read routes, components, API handlers first)\n- Manage tests: list, view, edit, run, delete (DRAFT/ACTIVE/DISABLED/QUARANTINED)\n- Heal failing tests: AI replays in browser, fixes code or flags app bugs\n- Schedule recurring runs (hourly-weekly) with auto-heal\n\n## Workflows\n1. New project: READ source, setup_project (features, baseUrl, testCredentials, appBrief). Per feature: READ code, create_test, add_api_endpoints.\n2. Check health: list_features (failingTests > 0), get_test_failures per feature, edit_test_code or heal_test.\n3. Add coverage: READ code, create_feature, create_test x N (suite = feature name), add_api_endpoints.\n4. Nightly automation: create_schedule (daily, autoHeal: true).\n5. After code change: batch_run_tests, then list_features + get_test_failures.\n\n## Automatic lifecycle\nDo NOT call run_test after create_test (auto-queued) or heal_test after failures (auto-heals).\nDo NOT check run status in a loop - call get_test_failures after a reasonable wait to see what failed.\nLifecycle: create > auto-run > pass=ACTIVE, fail=auto-heal > heal fail=@needs-review.\nDO call add_api_endpoints per feature (not automatic).\nDO call run_security_audit after UI tests to analyze captured API traffic. If it errors about missing data, tell the developer to click \"Security Audit\" in the Dashboard to run a live browser capture first. Use list_security_findings to review results.\n\n## Rate limits\n50 calls/60s. Reads parallel OK, writes sequential. On 429, wait 30-60s.\n\n## Triaging failures\n- App broken: flag_as_bug(testId, description). Selector/timing: heal_test. Bad test logic: edit_test_code.\n- Unsure: heal_test first - it detects app bugs automatically.\n\n## Using the codebase\nYou have full source code access. Always READ before writing: routes for URLs, components for selectors, API handlers for endpoints.\n\n## Before writing any test - preflight check\n\nRead project://test-conventions first. Check the variables section:\n- BASE_URL must be set (the runner needs it). If missing, call setup_project or set_variable.\n- TEST_USER_EMAIL and TEST_USER_PASSWORD must be set if tests require login. If missing, ask the developer for credentials and call set_variable.\n- If starting fresh (no features, no variables), use workflow #1 (setup_project) before anything else.\n\n## Critical rules - see project://test-conventions for full details and code templates\n- URLs: relative only, never hardcoded domains - runner sets baseURL\n- Credentials: process.env.TEST_USER_EMAIL! (non-null assertion) - never ?? or || fallbacks\n- Registration: testuser-\\${Date.now()}@example.com\n- Suite name must match feature name exactly (case-sensitive)\n- @auth must NOT combine with @smoke/@regression/@needs-auth/@critical\n- Each test = own browser process; @needs-auth tests MUST login inline\n- API tests: test.describe.serial() with __QA_HTTP__ log after every request\n- UI tests: console.log() milestones after key actions\n- create_test/edit_test_code validate code - fix warnings before moving on\n\n## Setting up coverage\n1. SCAN: Read router, nav, pages, API handlers - map ALL routes and features.\n2. SETUP: setup_project with features, pages, baseUrl, testCredentials, appBrief.\n3. WRITE: One feature at a time. For EACH test, read the SPECIFIC source code first (see codeReadingRequirements in test-conventions). For login blocks: read the login component. For UI tests: read the page component. For API tests: read the route handler. Templates are structure guides - selectors MUST come from the real code. Use create_test for BOTH UI and API tests. Then call add_api_endpoints.\n4. VERIFY: get_coverage_summary + list_features for failures + get_test_failures.\n\nTest real user flows (forms, validation, errors). \"Page loads\" tests are worthless.` },\n);\n\n// Register all QA platform tools\nregisterAllTools(server, client);\n\n// ── MCP Resources (live project context for AI assistants) ────────────\n\nserver.resource(\n 'Project Overview',\n 'project://overview',\n { description: 'Live project state: test counts, pass rates, runner status, features, coverage gaps, and configuration. Read this first to understand the current project.' },\n async () => {\n try {\n const [overview, coverage, stats] = await Promise.all([\n client.executeTool('get_project_overview'),\n client.executeTool('get_coverage_summary').catch(() => null),\n client.executeTool('get_regression_stats').catch(() => null),\n ]);\n const snapshot = {\n overview,\n coverage,\n regressionStats: stats,\n retrievedAt: new Date().toISOString(),\n };\n return { contents: [{ uri: 'project://overview', mimeType: 'application/json', text: JSON.stringify(snapshot, null, 2) }] };\n } catch (err) {\n return { contents: [{ uri: 'project://overview', mimeType: 'text/plain', text: `Failed to load project overview: ${err instanceof Error ? err.message : String(err)}` }] };\n }\n },\n);\n\nserver.resource(\n 'Test Conventions',\n 'project://test-conventions',\n { description: 'Project-specific test conventions: environment variables, credential patterns, tag meanings, and registration email rules. Read this before writing or editing any test.' },\n async () => {\n try {\n const vars = await client.executeTool('get_variables') as { variables: Record<string, { value: string; secret: boolean }> };\n const varNames = Object.keys(vars.variables || {});\n const hasAuth = varNames.includes('TEST_USER_EMAIL') && varNames.includes('TEST_USER_PASSWORD');\n\n const conventions = {\n codeReadingRequirements: {\n CRITICAL: 'Templates below are STRUCTURE GUIDES - every selector, route, and endpoint MUST come from reading the actual source code. Never copy a template verbatim.',\n forLogin: 'BEFORE writing any @needs-auth login block: READ the login/auth page component. Find: (1) the login route (/login, /auth/login, /signin?), (2) the flow type (single-step or multi-step with Continue/Next button?), (3) exact input selectors (labels, placeholders, input types - is it getByLabel, getByPlaceholder, or input[type=password]?), (4) exact submit button text, (5) post-login redirect URL. Every app is different - single-step, multi-step, OAuth buttons, magic links.',\n forUITests: 'BEFORE writing a UI test for a page: READ the page component. Find: headings (text + level), form fields (labels, placeholders, types), buttons (text, role), navigation elements, validation error messages, success/error states. Use the exact text from the component - do not guess.',\n forAPITests: 'BEFORE writing an API test: READ the route handler. Find: the endpoint path, HTTP method, request body schema (what fields, what types), response body shape (is it flat or wrapped in { data: {...} }?), auth requirements (Bearer token? Cookie? API key?), error response shapes.',\n forAPIMapping: 'BEFORE calling add_api_endpoints: READ ALL route handlers for the feature. Register every endpoint with accurate method, path, requestShape, responseShapes, requiresAuth.',\n },\n credentials: {\n rule: 'Never hardcode credentials in test code',\n loginFields: hasAuth\n ? 'Use process.env.TEST_USER_EMAIL! and process.env.TEST_USER_PASSWORD!'\n : 'No credentials configured - set TEST_USER_EMAIL and TEST_USER_PASSWORD via set_variable',\n registrationFields: 'MUST use unique email per run: `testuser-${Date.now()}@example.com` - reusing emails causes \"already registered\" failures',\n registrationPassword: \"Use 'TestPass123!' for new account passwords (meets typical strength rules)\",\n },\n variables: {\n configured: varNames,\n baseUrl: (vars.variables as any)?.BASE_URL?.value || 'Not set',\n },\n tags: {\n '@auth': 'Tests the login/auth flow itself. MUST NOT combine with @smoke, @regression, @needs-auth, @critical. CAN include @discovered.',\n '@needs-auth': 'Tests for authenticated pages - MUST include inline login block. CAN combine with @smoke, @regression, @critical.',\n '@smoke': 'Quick sanity checks - run on every deploy',\n '@regression': 'Full regression suite tests',\n '@critical': 'Business-critical paths (informational label - no special suite filtering)',\n '@api': 'API-level tests. Runs in its own project, excluded from suite/smoke/regression runs',\n '@discovered': 'Auto-added to tests generated by AI discovery',\n '@generated': 'Auto-added by AI discovery and API test generation',\n '@manual': 'Default tag for tests created via create_test when no tags specified',\n '@flaky': 'Auto-added after repeated failures. Excluded from suite/smoke/regression runs',\n '@bug-found': 'Added when healer detects an app bug. Test is auto-quarantined',\n '@needs-review': 'Added when DRAFT test fails healing. Requires human attention',\n '@heal-skip': 'Added by healer when test is permanently unfixable. Prevents future auto-heal',\n '@rate-limit-blocked': 'Auto-added by pre-heal classifier when test failure was a rate-limit (HTTP 429). Test skips heal until next pass clears the tag. NOT for use by test authors.',\n '@quota-exceeded': 'Auto-added when failure is a quota/daily-limit response. Skips heal; clears on next pass.',\n '@auth-lost': 'Auto-added when failure is a 401/403. Skips heal; clears after re-auth + pass.',\n '@rate-limit-expected': 'OPT-OUT tag for tests that intentionally exercise rate-limit behavior (e.g., \"POST /api/login 6 times to trigger lockout\"). Add this tag to ANY @api test that asserts rate-limit responses, otherwise the rate-limit detector will silently downgrade your failure to ERROR. Distinct from auto-pushed @rate-limit-blocked.',\n },\n executionModel: {\n isolation: 'Each test runs as its own separate Playwright process in a fresh browser. No shared state, no storageState, no global setup.',\n authRequired: 'Every @needs-auth test MUST include its own login block. Do NOT suggest storageState or performLogin() - those only work in the regression suite.',\n concurrency: 'Tests run in parallel - runner claims up to 10 per poll, launched 3s apart (~30s per batch). Multiple batches can overlap. Use test.setTimeout(120_000) if the target app is slow under concurrent login load.',\n },\n lifecycle: {\n DRAFT: 'New or unverified test - not included in scheduled runs',\n ACTIVE: 'Verified and running - included in batch/suite executions',\n DISABLED: 'Manually paused - excluded from all runs',\n QUARANTINED: 'Auto-quarantined after repeated failures',\n },\n logging: {\n CRITICAL: 'EVERY request.get/post/put/patch/delete call MUST be followed by a __QA_HTTP__ log line. This is how the platform surfaces request/response data in the Network panel. Missing this = invisible API calls.',\n apiPattern: \"const body = await res.json();\\nconsole.log('__QA_HTTP__:' + JSON.stringify({ method: 'POST', url: '/api/path', status: res.status(), requestBody: { field: value, password: '[REDACTED]' }, responseBody: body }));\",\n rules: [\n 'Call res.json() ONCE, store in body variable - reuse for log and assertions (calling twice throws)',\n 'Log AFTER the request, BEFORE assertions',\n 'Redact sensitive fields: password: \"[REDACTED]\", token: \"[REDACTED]\"',\n 'Use the literal URL string (same as the request call), not a variable',\n 'UI tests using page fixture do NOT need __QA_HTTP__ - only request fixture tests',\n ],\n uiMilestones: \"UI tests (page fixture): add plain console.log() at key milestones - e.g. console.log('Navigated to login page') after page.goto(). These appear in the Console Output panel.\",\n uiLoggingRules: [\n 'Log AFTER the action, not before - confirms the action succeeded',\n 'Include dynamic values where useful: console.log(\"Current URL:\", page.url())',\n 'Keep messages short and human-readable - they show verbatim in the UI',\n 'Do NOT log sensitive values (passwords, tokens)',\n ],\n apiTestStructure: 'Use test.describe.serial() for API tests sharing auth. Independent validation tests (401, 404 checks) go in a SEPARATE non-serial describe block - do not mix them with the auth-dependent serial flow.',\n },\n bestPractices: [\n 'Every @needs-auth test must start with a login block (goto login, fill creds, click submit, waitForURL)',\n 'Use { timeout: 30000 } on waitForURL after login - target apps can be slow under concurrent test load',\n 'Keep login blocks minimal - no assertions on the login page (that is what @auth tests are for)',\n 'After login, page.goto() directly to the page being tested - do not navigate via UI clicks',\n 'Use role-based selectors (getByRole, getByLabel) over CSS selectors',\n 'Registration tests must generate unique email with Date.now()',\n 'Group tests into suites matching feature names exactly (case-sensitive)',\n 'Do NOT suggest storageState or performLogin() for create_test - those only work in the regression suite',\n 'EVERY API test (request fixture) MUST include __QA_HTTP__ log after each request call - see logging section above',\n 'Avoid waitForLoadState(\"networkidle\") - apps with SSE, WebSocket, or analytics connections may never reach idle, causing 60s timeouts. Wait for specific elements instead.',\n 'For headings, use exact: true or level: 1 - getByRole(\"heading\", { name: \"X\" }) matches ALL h1-h6 containing \"X\", causing strict mode violations',\n ],\n pitfalls: {\n CRITICAL: 'These are the top causes of test failures. Read before writing ANY test.',\n strictModeViolations: \"Playwright throws when a locator resolves to 2+ elements. Common causes: getByLabel('Password') matches input AND 'Show password' toggle; getByRole('button', { name: 'Continue' }) matches 'Continue' AND 'Continue with GitHub'. Prevention: use input[type=\\\"password\\\"] for password fields, use { exact: true } for ambiguous button text, scope to a parent container (page.locator('form#login').getByLabel('Email')).\",\n networkIdle: \"NEVER use waitForLoadState('networkidle') - waits for 500ms of zero network activity, which is fragile and causes 30s+ timeouts in apps with polling, analytics, or WebSockets. Use waitForURL() or wait for a specific element instead.\",\n headingStrictMode: \"getByRole('heading', { name: 'Dashboard' }) can match multiple headings (h1 + h2 + breadcrumb). Add { level: 1 } or { exact: true } to disambiguate. Same for getByLabel('Password') matching input AND 'Show password' toggle.\",\n postLoginRedirect: \"NEVER assert exact URL after login (e.g. expect(page).toHaveURL('/dashboard')) - apps redirect to different routes per user/role. Use: await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 30000 })\",\n resJsonOnce: \"res.json() consumes the response body stream - calling it twice throws. Store result: const body = await res.json(); then reuse body for logging and assertions.\",\n hiddenInputs: \"Many UI frameworks use visually hidden <input> with custom overlays. page.check() fails because the overlay intercepts clicks. Click the <label> or visible wrapper: page.getByText('Remember me').click()\",\n apiResponseShapes: \"API endpoints often wrap data in a property ({ user: {...} }, { data: [...] }). Read the route handler's res.json() call to see the actual shape. If the handler does res.json({ ticket }), then body is { ticket: { id, ... } } - NOT { id, ... }.\",\n invalidIds: \"Not all routes validate ID format before querying the database. If a route passes the raw ID to an ORM query and gets null back, it returns 404 'not found' - not 400 'bad request'. If unsure, expect either: expect([400, 404]).toContain(res.status())\",\n hardcodedUrls: \"NEVER hardcode full URLs in test code. For UI tests: always use relative paths - page.goto('/login'). The runner sets baseURL from the project's BASE_URL variable. For API tests: use relative paths if the API is on the same domain as the frontend (request.post('/api/auth/login')). If the API is on a SEPARATE domain, use process.env.BASE_API_URL! as a test variable (set via set_variable) and construct URLs: request.post(process.env.BASE_API_URL! + '/auth/login'). NEVER hardcode 'https://example.com/...' in test code - use env vars or relative paths.\",\n credentialFallbacks: \"NEVER use process.env.TEST_USER_EMAIL ?? 'fallback' or || 'fallback'. Use ! (non-null assertion). Missing variable = test should fail immediately, not silently use hardcoded value.\",\n },\n templates: {\n needsAuth: \"test('Feature Name - Test Name', {\\n tag: ['@needs-auth', '@smoke'],\\n}, async ({ page }) => {\\n // Login block - ADAPTED from reading the app's login page component\\n // YOU MUST READ the login page component BEFORE writing this block\\n await page.goto('/login'); // read the router for actual login route\\n await page.getByRole('textbox', { name: /email/i }).fill(process.env.TEST_USER_EMAIL!);\\n await page.locator('input[type=\\\"password\\\"]').waitFor({ state: 'visible', timeout: 10000 });\\n await page.locator('input[type=\\\"password\\\"]').fill(process.env.TEST_USER_PASSWORD!);\\n await page.getByRole('button', { name: /sign in|log in|submit/i }).click();\\n await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 30000 });\\n console.log('Logged in, current URL:', page.url());\\n\\n // Actual test\\n await page.goto('/feature-path');\\n console.log('On feature page');\\n await expect(page.getByRole('heading', { name: /feature name/i })).toBeVisible();\\n});\",\n authTest: \"test('Login - Happy Path', {\\n tag: ['@auth'],\\n}, async ({ page }) => {\\n await page.goto('/auth/login');\\n // test the login form itself - selectors from reading the actual component\\n});\",\n apiSerial: \"test.describe.serial('Feature Name', () => {\\n let token: string;\\n test('authenticate', async ({ request }) => {\\n const res = await request.post('/api/auth/login', {\\n data: { email: process.env.TEST_USER_EMAIL!, password: process.env.TEST_USER_PASSWORD! }\\n });\\n const body = await res.json();\\n console.log('__QA_HTTP__:' + JSON.stringify({\\n method: 'POST', url: '/api/auth/login', status: res.status(),\\n requestBody: { email: process.env.TEST_USER_EMAIL!, password: '[REDACTED]' },\\n responseBody: body,\\n }));\\n expect(res.status()).toBe(200);\\n token = body.access_token; // ← adapt to your API (e.g. body.token, body.jwt)\\n });\\n test('get profile', async ({ request }) => {\\n const res = await request.get('/api/users/profile', {\\n headers: { Authorization: `Bearer ${token}` }\\n });\\n const body = await res.json();\\n console.log('__QA_HTTP__:' + JSON.stringify({\\n method: 'GET', url: '/api/users/profile', status: res.status(),\\n responseBody: body,\\n }));\\n expect(res.status()).toBe(200);\\n });\\n});\",\n apiEndpointMapping: '// After reading an Express route handler like:\\n// router.post(\\'/api/auth/login\\', async (req, res) => { ... })\\n// Call add_api_endpoints with:\\n{\\n featureName: \"Authentication\",\\n endpoints: [{\\n method: \"POST\",\\n path: \"/api/auth/login\",\\n requiresAuth: false,\\n requestShape: { \"email\": \"string\", \"password\": \"string\" },\\n responseShapes: { \"200\": { \"token\": \"string\", \"user\": \"object\" }, \"401\": { \"error\": \"string\" } }\\n }]\\n}',\n uiWithMilestones: \"test('Feature - Happy Path', async ({ page }) => {\\n await page.goto('/login');\\n console.log('Navigated to login page');\\n\\n await page.getByRole('textbox', { name: /email/i }).fill(process.env.TEST_USER_EMAIL!);\\n await page.locator('input[type=\\\"password\\\"]').fill(process.env.TEST_USER_PASSWORD!);\\n await page.getByRole('button', { name: /sign in/i }).click();\\n console.log('Credentials submitted');\\n\\n await page.waitForURL(url => !url.pathname.startsWith('/login'), { timeout: 15000 });\\n console.log('Logged in, current URL:', page.url());\\n\\n await page.goto('/feature-path');\\n console.log('On feature page');\\n});\",\n },\n testQuality: [\n 'Test actual user flows, not just \"page loads\" - a test that only checks goto + heading visible is nearly worthless',\n 'Test form submission: fill fields, submit, verify success/redirect',\n 'Test validation: leave required fields empty, enter invalid data, verify error messages',\n 'Test error states: wrong password, expired session, invalid IDs',\n 'Test the happy path end-to-end: login → navigate → perform action → verify result',\n 'Each test should assert something meaningful about the application behavior',\n ],\n securityAudit: {\n workflow: 'After creating UI tests for a feature, call run_security_audit to analyze captured API traffic for auth issues, data exposure, and vulnerabilities.',\n prerequisite: 'Requires prior browser-based test execution (the auto-verification run from create_test counts). If run_security_audit errors about missing data, tell the developer to click \"Security Audit\" in the Tests dropdown on the Dashboard - this launches a live browser to capture real network traffic (HAR) and runs analysis automatically.',\n reviewing: 'Use list_security_findings to review results. Filter by severity (critical/high/medium/low/info) or status (open/acknowledged/resolved/false-positive). Critical and high findings should be flagged to the developer immediately.',\n },\n keyConcepts: {\n features: 'Group related pages and tests (e.g., \"Authentication\" covers /auth/login, /auth/register). MUST include page routes.',\n pages: 'Route URLs on a feature - used for coverage tracking and site map (e.g., [\"/login\", \"/register\"])',\n suites: 'Group tests for batch execution - suite name MUST match feature name exactly (case-sensitive)',\n runner: 'Executes tests - can be local (developer machine) or cloud',\n testData: 'Key-value variables injected into tests (e.g., credentials, URLs)',\n },\n retrievedAt: new Date().toISOString(),\n };\n return { contents: [{ uri: 'project://test-conventions', mimeType: 'application/json', text: JSON.stringify(conventions, null, 2) }] };\n } catch (err) {\n return { contents: [{ uri: 'project://test-conventions', mimeType: 'text/plain', text: `Failed to load conventions: ${err instanceof Error ? err.message : String(err)}` }] };\n }\n },\n);\n\n// Connect via stdio transport\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nexport interface ValidateQAConfig {\n apiUrl: string;\n token: string;\n projectId: string;\n}\n\nfunction parseArgs(argv: string[]): Record<string, string> {\n const args: Record<string, string> = {};\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg.startsWith('--') && i + 1 < argv.length) {\n const key = arg.slice(2);\n args[key] = argv[++i];\n }\n }\n return args;\n}\n\nfunction loadDotFile(): Partial<ValidateQAConfig> {\n try {\n const content = readFileSync(resolve(process.cwd(), '.validateqa.json'), 'utf-8');\n const json = JSON.parse(content);\n return {\n apiUrl: typeof json.apiUrl === 'string' ? json.apiUrl : undefined,\n projectId: typeof json.projectId === 'string' ? json.projectId : undefined,\n };\n } catch {\n return {};\n }\n}\n\nexport function resolveConfig(): ValidateQAConfig {\n const cliArgs = parseArgs(process.argv.slice(2));\n const dotFile = loadDotFile();\n\n const apiUrl =\n cliArgs['api-url'] ??\n process.env.VALIDATEQA_API_URL ??\n dotFile.apiUrl;\n\n const token =\n cliArgs['token'] ??\n process.env.VALIDATEQA_TOKEN;\n\n const projectId =\n cliArgs['project-id'] ??\n process.env.VALIDATEQA_PROJECT_ID ??\n dotFile.projectId;\n\n if (!apiUrl) {\n console.error('Missing required config: api-url (--api-url, VALIDATEQA_API_URL, or .validateqa.json)');\n process.exit(1);\n }\n if (!token) {\n console.error('Missing required config: token (--token or VALIDATEQA_TOKEN)');\n process.exit(1);\n }\n if (!projectId) {\n console.error('Missing required config: project-id (--project-id, VALIDATEQA_PROJECT_ID, or .validateqa.json)');\n process.exit(1);\n }\n\n return { apiUrl: apiUrl.replace(/\\/$/, ''), token, projectId };\n}\n","export class ValidateQAClient {\n constructor(\n private baseUrl: string,\n private token: string,\n private projectId: string,\n ) {}\n\n async executeTool(tool: string, args: Record<string, unknown> = {}): Promise<unknown> {\n let res: Response;\n try {\n res = await fetch(`${this.baseUrl}/api/mcp/execute`, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.token}`,\n 'X-Project-Id': this.projectId,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ tool, args }),\n signal: AbortSignal.timeout(120_000),\n });\n } catch (err) {\n // Network errors, DNS failures, timeouts\n const cause = err instanceof Error && err.cause instanceof Error ? err.cause.message : '';\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Connection failed: ${msg}${cause ? ` (${cause})` : ''}`);\n }\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error((body as any).error || `API error ${res.status}`);\n }\n\n return res.json();\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/health`, {\n signal: AbortSignal.timeout(5000),\n });\n return res.ok;\n } catch {\n return false;\n }\n }\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ValidateQAClient } from '../api-client.js';\n\ntype ToolAnnotations = {\n readOnlyHint?: boolean;\n destructiveHint?: boolean;\n idempotentHint?: boolean;\n openWorldHint?: boolean;\n};\n\nfunction tool(\n server: McpServer,\n client: ValidateQAClient,\n name: string,\n description: string,\n schema: Record<string, z.ZodTypeAny>,\n annotations?: ToolAnnotations,\n) {\n const handler = async (args: Record<string, unknown>) => {\n try {\n const result = await client.executeTool(name, args);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Error: ${err instanceof Error ? err.message : String(err)}` }],\n isError: true,\n };\n }\n };\n\n if (annotations) {\n server.tool(name, description, schema, annotations, handler);\n } else {\n server.tool(name, description, schema, handler);\n }\n}\n\nexport function registerAllTools(server: McpServer, client: ValidateQAClient) {\n // ── Test Management ──────────────────────────────────────────────────\n\n tool(server, client, 'list_tests',\n 'List tests in the current project. Returns test name, status, suite, tags, confidence, and the last run result. Paginated - use limit/offset to page through all tests.',\n {\n status: z.enum(['DRAFT', 'ACTIVE', 'DISABLED', 'QUARANTINED']).optional().describe('Filter by test lifecycle status'),\n suite: z.string().optional().describe('Filter by suite name'),\n search: z.string().optional().describe('Search test names (case-insensitive substring match)'),\n tag: z.string().optional().describe('Filter by tag (e.g. @regression, @smoke, @auth)'),\n limit: z.number().optional().describe('Max results to return (default: 50, max: 200)'),\n offset: z.number().optional().describe('Number of results to skip for pagination (default: 0)'),\n },\n { readOnlyHint: true },\n );\n\n tool(server, client, 'get_test',\n 'Get details of a specific test: name, status, tags, steps, session, and the 5 most recent run results. Code is truncated to 200 lines - use get_test_code for the full Playwright code.',\n { testId: z.string().describe('The test case ID') },\n { readOnlyHint: true },\n );\n\n tool(server, client, 'run_test',\n 'Queue a test for execution. Creates a PENDING test run that will be picked up by an online runner. Returns the run ID.',\n { testId: z.string().describe('The test case ID to run') },\n );\n\n tool(server, client, 'delete_test',\n 'Permanently delete a test case and all its run history. Cannot be undone.',\n { testId: z.string().describe('The test case ID to delete') },\n { destructiveHint: true },\n );\n\n tool(server, client, 'heal_test',\n 'Queue an AI healing run for a failing test. The AI agent replays the test in a real browser, analyzes the failure, and either fixes the Playwright code OR detects an application bug (quarantines the test with @bug-found if the app itself is broken, not the test).',\n { testId: z.string().describe('The failing test case ID') },\n );\n\n tool(server, client, 'edit_test_code',\n 'Update the Playwright code, name, or tags of an existing test case. Read project://test-conventions for patterns and pitfalls. Code is validated - fix any warnings returned.',\n {\n testId: z.string().describe('The test case ID to edit'),\n playwrightCode: z.string().optional().describe('New Playwright test code (full file replacement)'),\n name: z.string().optional().describe('New test name'),\n tags: z.array(z.string()).optional().describe('New tags array (e.g. [\"@regression\", \"@smoke\"])'),\n },\n );\n\n tool(server, client, 'update_test_status',\n 'Change the lifecycle status of one or more tests (e.g. promote DRAFT→ACTIVE, disable a flaky test).',\n {\n testIds: z.array(z.string()).describe('Test IDs to update'),\n status: z.enum(['DRAFT', 'ACTIVE', 'DISABLED', 'QUARANTINED']).describe('New status to set'),\n },\n );\n\n // ── Test Runs ────────────────────────────────────────────────────────\n\n tool(server, client, 'get_test_run',\n 'Get the status and result of a specific test run, including error details if it failed. Can look up by runId or get the latest run for a testId.',\n {\n runId: z.string().optional().describe('The test run ID'),\n testId: z.string().optional().describe('Get the most recent run for this test case ID'),\n },\n { readOnlyHint: true },\n );\n\n // Custom handler - returns MCP image content blocks so the AI can see screenshots\n server.tool(\n 'get_test_screenshots',\n 'Get screenshots from a specific test run as images the AI can actually see. Use this to visually inspect what happened during a failed or passing run.',\n {\n runId: z.string().describe('The test run ID to fetch screenshots from'),\n limit: z.number().optional().describe('Max screenshots to return (default: 5, max: 10)'),\n },\n { readOnlyHint: true },\n async (args) => {\n try {\n const result = await client.executeTool('get_test_screenshots', args) as {\n runId: string;\n status: string;\n total: number;\n returned: number;\n screenshots: Array<{ index: number; data: string; mimeType: string }>;\n };\n\n if (!result.screenshots || result.screenshots.length === 0) {\n return {\n content: [{\n type: 'text' as const,\n text: `No screenshots available for run ${result.runId} (status: ${result.status})`,\n }],\n };\n }\n\n const content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }> = [\n {\n type: 'text' as const,\n text: `Run ${result.runId} - status: ${result.status} - showing ${result.returned} of ${result.total} screenshot(s)`,\n },\n ];\n\n for (const shot of result.screenshots) {\n content.push({ type: 'image' as const, data: shot.data, mimeType: shot.mimeType });\n }\n\n return { content };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Error: ${err instanceof Error ? err.message : String(err)}` }],\n isError: true,\n };\n }\n },\n );\n\n tool(server, client, 'cancel_test_run',\n 'Cancel a pending or running test run. Can cancel by run ID or cancel the latest running batch.',\n {\n runId: z.string().optional().describe('The specific test run ID to cancel'),\n cancelBatch: z.boolean().optional().describe('If true, cancel the latest batch run instead'),\n },\n );\n\n tool(server, client, 'batch_run_tests',\n 'Run multiple tests from the database individually. Filter by test IDs or suite name; omit both to run all ACTIVE tests. Each test executes in its own browser.',\n {\n testIds: z.array(z.string()).optional().describe('Specific test IDs to run'),\n suite: z.string().optional().describe('Run all active tests in this suite'),\n },\n );\n\n // ── Features & API Endpoints ──────────────────────────────────────────\n\n tool(server, client, 'list_features',\n 'List all features with test counts and failure counts (last 24h). Use failingTests > 0 to identify which features need attention, then call get_test_failures({ suite: featureName }) to drill into the full error details for that feature.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'list_api_endpoints',\n 'List captured API endpoints for the current project. Optionally scope to a specific feature name.',\n {\n featureName: z.string().optional().describe('Optional feature name to scope the endpoint list'),\n },\n { readOnlyHint: true },\n );\n\n // ── Infrastructure ───────────────────────────────────────────────────\n\n tool(server, client, 'get_runner_health',\n 'Check whether the project\\'s configured runners are online, including available device capabilities.',\n {},\n { readOnlyHint: true },\n );\n\n // ── Reports & Observability ──────────────────────────────────────────\n\n tool(server, client, 'get_project_overview',\n 'Get a workspace-level QA overview: test counts, pass rates, runner readiness, features, coverage gaps, variables, schedules, and notifications.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'get_reports_summary',\n 'Get a high-level report: total tests (active, never-run), 7-day pass rate, today\\'s run count, and the 10 most recent test run results.',\n {},\n { readOnlyHint: true },\n );\n\n // ── Schedules (Admin) ────────────────────────────────────────────────\n\n tool(server, client, 'list_schedules',\n 'List all scheduled test runs in the project.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'create_schedule',\n 'Create a new recurring test schedule. Runs all matching tests from the database on the configured interval.',\n {\n name: z.string().describe('Name for the schedule (e.g. \"Nightly smoke tests\")'),\n mode: z.enum(['batch']).describe('Run mode: batch (runs project tests from the database)'),\n intervalMinutes: z.union([z.literal(60), z.literal(360), z.literal(720), z.literal(1440), z.literal(2880), z.literal(10080)]).describe('Interval in minutes: 60 (hourly), 360 (6h), 720 (12h), 1440 (daily), 2880 (2 days), 10080 (weekly)'),\n tags: z.array(z.string()).optional().describe('Tags to filter which tests run (e.g. [\"@smoke\"]). Omit to run all ACTIVE tests.'),\n autoHeal: z.boolean().optional().describe('Automatically heal failing tests after each run'),\n enabled: z.boolean().optional().describe('Whether the schedule is active (default: true)'),\n runImmediately: z.boolean().optional().describe('Whether to trigger the first run immediately (default: true)'),\n },\n );\n\n tool(server, client, 'update_schedule',\n 'Update an existing test schedule.',\n {\n scheduleId: z.string().describe('The schedule ID to update'),\n name: z.string().optional().describe('New name for the schedule'),\n intervalMinutes: z.union([z.literal(60), z.literal(360), z.literal(720), z.literal(1440), z.literal(2880), z.literal(10080)]).optional().describe('New interval in minutes'),\n suite: z.string().optional().describe('New suite name to filter tests'),\n tags: z.array(z.string()).optional().describe('New tag filter'),\n autoHeal: z.boolean().optional().describe('Toggle auto-heal'),\n enabled: z.boolean().optional().describe('Enable or disable the schedule'),\n },\n );\n\n tool(server, client, 'delete_schedule',\n 'Delete (remove) a test schedule permanently.',\n { scheduleId: z.string().describe('The schedule ID to delete') },\n { destructiveHint: true },\n );\n\n tool(server, client, 'run_schedule_now',\n 'Trigger a scheduled run immediately instead of waiting for the next scheduled time.',\n { scheduleId: z.string().describe('The schedule ID to trigger') },\n );\n\n // ── Configuration ────────────────────────────────────────────────────\n\n tool(server, client, 'get_variables',\n 'Get all test variables (environment variables) for the current project. Secrets are redacted.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'set_variable',\n 'Set or update a test variable (environment variable) for the project. Used to configure base URLs, credentials, etc.',\n {\n key: z.string().describe('Variable name (e.g. BASE_URL, TEST_USER_EMAIL, API_KEY)'),\n value: z.string().describe('Variable value. Set value to empty string to remove a variable.'),\n secret: z.boolean().optional().describe('Whether this variable is a secret (will be redacted in UI)'),\n },\n );\n\n tool(server, client, 'create_test',\n 'Create a new test case with Playwright code. Read project://test-conventions for templates and pitfalls BEFORE writing code. The test starts as DRAFT - auto-runs and auto-promotes to ACTIVE on pass. IMPORTANT: (1) Set suite to the EXACT feature name. (2) After creating all tests for a feature, you MUST call add_api_endpoints.',\n {\n name: z.string().describe('Test name (e.g. \"Login - Happy Path\")'),\n playwrightCode: z.string().describe('Full Playwright test code'),\n suite: z.string().optional().describe('MUST match an existing feature name exactly to link the test to that feature (e.g. \"Authentication\", not \"Auth Tests\")'),\n tags: z.array(z.string()).optional().describe('Tags (e.g. [\"@smoke\", \"@auth\"])'),\n },\n );\n\n // ── Features ─────────────────────────────────────────────────────────\n\n tool(server, client, 'get_feature',\n 'Get detailed info about a feature including its pages, test plans, existing tests with lastRunStatus, and AI insights.',\n { featureName: z.string().describe('The feature name (case-insensitive match)') },\n { readOnlyHint: true },\n );\n\n tool(server, client, 'create_feature',\n 'Create a new feature with page routes. Read the developer\\'s route files to provide accurate page URLs. Pages are used for coverage tracking and site map.',\n {\n name: z.string().describe('Feature name (e.g. \"Checkout Flow\")'),\n description: z.string().optional().describe('What the feature does and what to test'),\n pages: z.array(z.string()).describe('Page routes this feature covers (e.g. [\"/checkout\", \"/checkout/payment\"]). Required for test generation.'),\n criticality: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Feature criticality level (default: medium)'),\n },\n );\n\n // ── Full Test Code ───────────────────────────────────────────────────\n\n tool(server, client, 'get_test_code',\n 'Get the full (untruncated) Playwright test code for a test case. Use this when you need to read, analyze, or prepare edits for the complete test code.',\n { testId: z.string().describe('The test case ID') },\n { readOnlyHint: true },\n );\n\n // ── Project Settings ─────────────────────────────────────────────────\n\n tool(server, client, 'set_runner_preference',\n 'Switch between cloud and local runner for test execution.',\n { preference: z.enum(['cloud', 'local']).describe('Runner type: cloud (shared infrastructure) or local (runs on your machine)') },\n );\n\n tool(server, client, 'toggle_auto_heal',\n 'Enable or disable automatic AI healing when tests fail in batch or suite runs.',\n { enabled: z.boolean().describe('true to enable auto-heal, false to disable') },\n );\n\n // ── Test Data ────────────────────────────────────────────────────────\n\n tool(server, client, 'list_test_data',\n 'List all test data sets in the project. Each set contains key-value pairs injected into AI prompts and test runs.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'create_test_data',\n 'Create a new test data set with key-value pairs. Max 200 entries per set.',\n {\n name: z.string().describe('Name of the test data set (max 100 chars)'),\n description: z.string().optional().describe('Optional description (max 1000 chars)'),\n data: z.record(z.string()).describe('Key-value pairs (e.g. {\"email\": \"test@example.com\", \"password\": \"secret\"})'),\n },\n );\n\n tool(server, client, 'delete_test_data',\n 'Delete a test data set by ID.',\n { dataSetId: z.string().describe('The test data set ID to delete') },\n { destructiveHint: true },\n );\n\n // ── Feature Management ───────────────────────────────────────────────\n\n tool(server, client, 'update_feature',\n 'Update a feature\\'s name, description, pages, or criticality.',\n {\n featureId: z.string().describe('The feature ID to update'),\n name: z.string().optional().describe('New feature name'),\n description: z.string().optional().describe('New description'),\n pages: z.array(z.string()).optional().describe('Updated page routes'),\n criticality: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Feature criticality level'),\n },\n );\n\n tool(server, client, 'archive_feature',\n 'Archive a feature (soft delete). Archived features are hidden from the dashboard but can be restored.',\n { featureId: z.string().describe('The feature ID to archive') },\n { destructiveHint: true },\n );\n\n // ── Site Map ─────────────────────────────────────────────────────────\n\n tool(server, client, 'get_site_map_coverage',\n 'Get site map coverage statistics: total pages, pages with tests, elements covered, coverage percentage, and per-page breakdown.',\n {},\n { readOnlyHint: true },\n );\n\n // ── Issue Triage ─────────────────────────────────────────────────────\n\n tool(server, client, 'list_bugs',\n 'Get all detected bugs: app bugs (quarantined tests with @bug-found), security findings, and flaky tests.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'flag_as_bug',\n 'Mark a failing test as a confirmed application bug - quarantines it with @bug-found and records the bug description. Use when the failure is caused by broken app behaviour, NOT a test script issue. Do NOT use for selector changes or timeout failures - use heal_test or edit_test_code for those.',\n {\n testId: z.string().describe('The failing test case ID'),\n description: z.string().describe('What is broken in the application (e.g. \"POST /api/checkout returns 500 when payment method is valid\")'),\n },\n );\n\n // ── Security Audit ───────────────────────────────────────────────────\n\n tool(server, client, 'run_security_audit',\n 'Run an AI security audit on captured API traffic. Requires prior browser-based test execution (discovery or capture) - analyzes real network traffic, not just registered endpoints. If no traffic exists, the developer can trigger \"Security Audit\" from the Dashboard which launches a live browser to capture it.',\n {},\n );\n\n tool(server, client, 'list_security_findings',\n 'List security audit findings with optional filters. Returns severity, category, status, and affected endpoints.',\n {\n severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).optional().describe('Filter by severity'),\n status: z.enum(['open', 'acknowledged', 'resolved', 'false-positive']).optional().describe('Filter by status'),\n },\n { readOnlyHint: true },\n );\n\n // ── Project Settings ─────────────────────────────────────────────────\n\n tool(server, client, 'set_app_brief',\n 'Set or update the project\\'s application brief - a description of the app used by AI for better test generation.',\n { appBrief: z.string().describe('Application description. Set empty to clear.') },\n );\n\n // ── Coverage & Draft Verification ────────────────────────────────────\n\n tool(server, client, 'get_coverage_summary',\n 'Get feature-level test coverage: planned vs existing vs passing tests, plus API endpoint coverage. Shows uncoveredEndpoints per feature - use this to find which API endpoints still need tests.',\n {},\n { readOnlyHint: true },\n );\n\n tool(server, client, 'run_draft_tests',\n 'Run all DRAFT tests to verify them. Tests that pass auto-promote to ACTIVE. Optionally scope to a feature.',\n {\n featureName: z.string().optional().describe('Only run drafts for this feature (optional)'),\n },\n );\n\n // ── Project Setup ────────────────────────────────────────────────────\n\n tool(server, client, 'setup_project',\n 'One-shot project setup from codebase knowledge. Sets app brief, base URL, test credentials, and creates features with pages - all in one call. Use this when a developer says \"set up test coverage for my app.\"',\n {\n appBrief: z.string().optional().describe('Description of the application (what it does, key features, tech stack)'),\n baseUrl: z.string().describe('Application base URL (e.g. \"https://myapp.com\" or \"http://localhost:3000\")'),\n testCredentials: z.object({\n email: z.string().optional().describe('Test user email'),\n password: z.string().optional().describe('Test user password'),\n }).optional().describe('Login credentials for test execution'),\n features: z.array(z.object({\n name: z.string().describe('Feature name'),\n description: z.string().optional().describe('What the feature does'),\n pages: z.array(z.string()).describe('Page routes (e.g. [\"/login\", \"/register\"])'),\n criticality: z.enum(['critical', 'high', 'medium', 'low']).optional().describe('Feature criticality'),\n })).describe('Features to create with their page routes'),\n },\n );\n\n // ── API Endpoints ───────────────────────────────────────────────────\n\n tool(server, client, 'add_api_endpoints',\n 'Register API endpoints for a feature from codebase knowledge. Read the developer\\'s route handlers to extract methods, paths, request/response shapes. Populates the API intelligence dashboard.',\n {\n featureName: z.string().describe('Feature name to link these endpoints to'),\n endpoints: z.array(z.object({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method'),\n path: z.string().describe('API path (e.g. \"/api/auth/login\")'),\n requestShape: z.record(z.string()).optional().describe('Request body fields with types (e.g. { \"email\": \"string\" })'),\n responseShapes: z.record(z.record(z.string())).optional().describe('Response shapes by status code (e.g. { \"200\": { \"token\": \"string\" } })'),\n requiresAuth: z.boolean().optional().describe('Whether endpoint requires authentication'),\n })).describe('API endpoints to register'),\n },\n );\n\n // ── Failure Diagnostics ──────────────────────────────────────────────\n\n tool(server, client, 'get_test_failures',\n 'Get recent test failures with full error messages, Playwright code, step-level failures, and heal context. Use this to understand why tests are failing.',\n {\n limit: z.number().optional().describe('Max failures to return (default: 50, max: 100)'),\n suite: z.string().optional().describe('Filter by feature/suite name'),\n mode: z.enum(['run', 'heal', 'all']).optional().describe('run = regular failures, heal = heal attempt failures, all = both (default)'),\n sinceHours: z.number().optional().describe('Only failures from the last N hours (default: 24)'),\n },\n { readOnlyHint: true },\n );\n}\n"],"mappings":"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","names":["readFileSync","resolve","config","tool","server","client","runInit"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@validate.qa/mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MCP server for Validate.QA
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "MCP server for Validate.QA - manage your QA platform from AI IDEs (Claude Code, Cursor, etc.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"validate-mcp": "bin.cjs"
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsup",
|
|
12
|
-
"dev": "tsx src/index.ts"
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
15
16
|
"dist",
|