genat-mcp 2.3.2 → 2.3.3
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/README.md +11 -0
- package/detect-incomplete-step-def.js +140 -0
- package/index.js +8 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -225,8 +225,19 @@ Login credentials: provide in the prompt (loginUsername, loginPassword) or in pr
|
|
|
225
225
|
|
|
226
226
|
Framework detection: language (TypeScript vs JavaScript vs Python), BDD (Cucumber, pytest-bdd, Behave), and Page Object pattern (e.g. `pages/` directory or `*Page.ts`, `*Page.js`, `*_page.py` files). Returns JSON with `testCode`, `featureFile`, `stepDefCode`, `pageObjectCode` (and `_written` paths when writing).
|
|
227
227
|
|
|
228
|
+
**Response fields for calling applications:** When `stepDefCode` is present, the MCP may set **`_stepDefIncomplete`** (boolean) and **`_stepDefIncompleteReason`** (string) using a lightweight heuristic (unclosed strings/delimiters, line continuation at EOF). Treat `true` as a signal to warn the user, retry generation, or block merge—do not assume the step file is runnable. Other optional fields from the workflow include **`usedLogin`** and **`loginReason`** when using login-capable workflows.
|
|
229
|
+
|
|
228
230
|
Generated tests include keyboard accessibility checks (Tab order, focus) by default. For complex pages, the workflow uses page complexity (regions, tables, widgets) to scale assertion count.
|
|
229
231
|
|
|
232
|
+
### Feature ↔ step parity (consumer CI)
|
|
233
|
+
|
|
234
|
+
GenAT does not guarantee that every Gherkin line has an implemented step. For **pytest-bdd** projects, the calling application or CI can:
|
|
235
|
+
|
|
236
|
+
1. Parse `*.feature` step lines (after `Given`/`When`/`Then`/`And`/`But`).
|
|
237
|
+
2. Confirm `accessibility_steps.py` (or your step module) defines matching `@given`/`@when`/`@then` patterns (or run `pytest --collect-only` / a dry run).
|
|
238
|
+
|
|
239
|
+
Keep this as an optional pre-merge check alongside `_stepDefIncomplete`.
|
|
240
|
+
|
|
230
241
|
## Sample prompts
|
|
231
242
|
|
|
232
243
|
See [prompts.md](prompts.md) for a full list of prompts including specific parent folders, login, JIRA, and combined options.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight heuristic: detect likely truncated or syntactically incomplete pytest-bdd step Python.
|
|
3
|
+
* For calling apps to surface _stepDefIncomplete and retry or warn.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} code
|
|
8
|
+
* @returns {{ incomplete: boolean, reason: string | null }}
|
|
9
|
+
*/
|
|
10
|
+
export function detectStepDefIncomplete(code) {
|
|
11
|
+
if (code == null || typeof code !== 'string') {
|
|
12
|
+
return { incomplete: false, reason: null };
|
|
13
|
+
}
|
|
14
|
+
const t = code.replace(/\r\n/g, '\n').trimEnd();
|
|
15
|
+
if (t.length === 0) {
|
|
16
|
+
return { incomplete: true, reason: 'empty' };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const triples = (t.match(/"""/g) || []).length;
|
|
20
|
+
if (triples % 2 !== 0) {
|
|
21
|
+
return { incomplete: true, reason: 'unclosed_triple_double_quoted_string' };
|
|
22
|
+
}
|
|
23
|
+
const tripleSingle = (t.match(/'''/g) || []).length;
|
|
24
|
+
if (tripleSingle % 2 !== 0) {
|
|
25
|
+
return { incomplete: true, reason: 'unclosed_triple_single_quoted_string' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const lines = t.split('\n');
|
|
29
|
+
const lastLine = lines[lines.length - 1];
|
|
30
|
+
if (/\\\s*$/.test(lastLine)) {
|
|
31
|
+
return { incomplete: true, reason: 'line_continuation_at_eof' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const depth = bracketDepthAtEnd(t);
|
|
35
|
+
if (depth !== 0) {
|
|
36
|
+
return { incomplete: true, reason: 'unclosed_delimiters' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { incomplete: false, reason: null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Rough balance of (), [], {} at end of file, skipping strings and comments.
|
|
44
|
+
* @param {string} s
|
|
45
|
+
* @returns {number} 0 if balanced, non-zero if likely incomplete
|
|
46
|
+
*/
|
|
47
|
+
function bracketDepthAtEnd(s) {
|
|
48
|
+
let i = 0;
|
|
49
|
+
const stack = [];
|
|
50
|
+
let state = 'code';
|
|
51
|
+
|
|
52
|
+
while (i < s.length) {
|
|
53
|
+
const c = s[i];
|
|
54
|
+
|
|
55
|
+
if (state === 'code') {
|
|
56
|
+
if (c === '#') {
|
|
57
|
+
while (i < s.length && s[i] !== '\n') i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (s.slice(i, i + 3) === '"""') {
|
|
61
|
+
state = 'triple_double';
|
|
62
|
+
i += 3;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (s.slice(i, i + 3) === "'''") {
|
|
66
|
+
state = 'triple_single';
|
|
67
|
+
i += 3;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (c === '"' || c === "'") {
|
|
71
|
+
state = c === '"' ? 'string_double' : 'string_single';
|
|
72
|
+
i++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (c === '(') stack.push('(');
|
|
76
|
+
else if (c === '[') stack.push('[');
|
|
77
|
+
else if (c === '{') stack.push('{');
|
|
78
|
+
else if (c === ')' || c === ']' || c === '}') {
|
|
79
|
+
const want = c === ')' ? '(' : c === ']' ? '[' : '{';
|
|
80
|
+
if (stack.length === 0 || stack[stack.length - 1] !== want) {
|
|
81
|
+
return -1;
|
|
82
|
+
}
|
|
83
|
+
stack.pop();
|
|
84
|
+
}
|
|
85
|
+
i++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (state === 'string_single') {
|
|
90
|
+
if (c === '\\') {
|
|
91
|
+
i += 2;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (c === "'") {
|
|
95
|
+
state = 'code';
|
|
96
|
+
}
|
|
97
|
+
i++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (state === 'string_double') {
|
|
102
|
+
if (c === '\\') {
|
|
103
|
+
i += 2;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (c === '"') {
|
|
107
|
+
state = 'code';
|
|
108
|
+
}
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (state === 'triple_double') {
|
|
114
|
+
if (s.slice(i, i + 3) === '"""') {
|
|
115
|
+
state = 'code';
|
|
116
|
+
i += 3;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
i++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (state === 'triple_single') {
|
|
124
|
+
if (s.slice(i, i + 3) === "'''") {
|
|
125
|
+
state = 'code';
|
|
126
|
+
i += 3;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (state !== 'code') {
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
return stack.length;
|
|
140
|
+
}
|
package/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import { detectFramework } from './detect-framework.js';
|
|
13
13
|
import { detectLogin } from './detect-login.js';
|
|
14
|
+
import { detectStepDefIncomplete } from './detect-incomplete-step-def.js';
|
|
14
15
|
import { finalizePytestBddStepDef } from './finalize-pytest-bdd-steps.js';
|
|
15
16
|
import { resolvePytestBddPaths } from './pytest-bdd-paths.js';
|
|
16
17
|
import { writeGeneratedFiles } from './write-files.js';
|
|
@@ -58,7 +59,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body, signal })
|
|
|
58
59
|
const server = new McpServer(
|
|
59
60
|
{
|
|
60
61
|
name: 'GenAT',
|
|
61
|
-
version: '2.3.
|
|
62
|
+
version: '2.3.3',
|
|
62
63
|
},
|
|
63
64
|
{
|
|
64
65
|
capabilities: {
|
|
@@ -283,6 +284,12 @@ server.registerTool(
|
|
|
283
284
|
if (data.testCode) data.testCode = finalizePytestBddStepDef(resolvedPath, data.testCode, pathOverrides);
|
|
284
285
|
}
|
|
285
286
|
|
|
287
|
+
if (data.stepDefCode && typeof data.stepDefCode === 'string') {
|
|
288
|
+
const inc = detectStepDefIncomplete(data.stepDefCode);
|
|
289
|
+
data._stepDefIncomplete = inc.incomplete;
|
|
290
|
+
if (inc.reason) data._stepDefIncompleteReason = inc.reason;
|
|
291
|
+
}
|
|
292
|
+
|
|
286
293
|
if (writeToProject && resolvedPath && data) {
|
|
287
294
|
if (data.bddFramework == null) data.bddFramework = framework.bddFramework;
|
|
288
295
|
try {
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genat-mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"mcpName": "io.github.asokans@oclc.org/genat",
|
|
5
5
|
"description": "MCP server GenAT: generate accessibility tests via n8n workflow (url + project folder)",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"bin": { "genat-mcp": "index.js" },
|
|
9
9
|
"engines": { "node": ">=20" },
|
|
10
|
-
"files": ["index.js", "detect-framework.js", "detect-login.js", "write-files.js", "strip-gherkin-from-python-steps.js", "finalize-pytest-bdd-steps.js", "fix-pytest-bdd-scenario-path.js", "pytest-bdd-paths.js", "oclc-context.js", "oclc-accessibility-confluence-context.md", "run-genat.mjs", "rules/genat-mcp.mdc"],
|
|
10
|
+
"files": ["index.js", "detect-framework.js", "detect-login.js", "detect-incomplete-step-def.js", "write-files.js", "strip-gherkin-from-python-steps.js", "finalize-pytest-bdd-steps.js", "fix-pytest-bdd-scenario-path.js", "pytest-bdd-paths.js", "oclc-context.js", "oclc-accessibility-confluence-context.md", "run-genat.mjs", "rules/genat-mcp.mdc"],
|
|
11
11
|
"keywords": ["mcp", "accessibility", "playwright", "n8n", "model-context-protocol", "a11y", "testing"],
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|