genat-mcp 2.3.3 → 2.3.5
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 +14 -3
- package/detect-framework.js +10 -4
- package/index.js +16 -1
- package/package.json +1 -1
- package/pytest-bdd-paths.js +23 -5
package/README.md
CHANGED
|
@@ -6,6 +6,10 @@ Full setup (n8n workflow, services, usage) is in the [project README](../README.
|
|
|
6
6
|
|
|
7
7
|
**For teams:** See [GENAT_OVERVIEW.md](GENAT_OVERVIEW.md) for a concise, presentation-ready overview. Suitable as a source for NotebookLM or other AI tools to create slide decks.
|
|
8
8
|
|
|
9
|
+
**Coverage claims:** Teams can copy [examples/coverage-manifest.example.yaml](examples/coverage-manifest.example.yaml) into their own repo (e.g. as `coverage-manifest.yaml`) and adjust it to document which standards, axe tags, workflows, and verification steps apply to their deployment. The manifest does not replace manual accessibility review; it aligns documented claims with the analyzer defaults and GenAT workflow behavior.
|
|
10
|
+
|
|
11
|
+
**CI (Phase D):** The parent repo includes [scripts/ci/a11y-analyzer-scan.mjs](../scripts/ci/a11y-analyzer-scan.mjs) and a [GitHub Actions example](../templates/github-actions/a11y-analyzer-url.example.yml) to save analyzer JSON and gate on violation impact—useful even when GenAT is not invoked.
|
|
12
|
+
|
|
9
13
|
## Install
|
|
10
14
|
|
|
11
15
|
**If the package is published to npm** (otherwise you get 404):
|
|
@@ -162,6 +166,7 @@ If the global binary is not on PATH, use the full path, e.g. `node $(npm root -g
|
|
|
162
166
|
- **Node.js** 20+
|
|
163
167
|
- **n8n** with a GenAT workflow imported and activated. Use [genat-accessibility-tests-with-login-jira.json](../workflows/genat-accessibility-tests-with-login-jira.json) (path `webhook-test/webhook-genat-login-jira`) for JIRA support, or [genat-accessibility-tests-with-login.json](../workflows/genat-accessibility-tests-with-login.json) (path `webhook-test/webhook-genat-login`). Set **N8N_WEBHOOK_URL** to the workflow's webhook URL.
|
|
164
168
|
- **Optional v2 pipeline (Python / pytest-bdd quality):** Import [GenAT Accessibility Tests (with Login + JIRA) v2.json](../workflows/GenAT%20Accessibility%20Tests%20(with%20Login%20+%20JIRA)%20v2.json) and set **N8N_WEBHOOK_URL** to `…/webhook-test/webhook-genat-login-jira-v2`. v2 adds stricter pytest-bdd + Python rules (no nested markdown in code, layout hints, fixture-aware steps) and can run alongside the original workflow. The original v1 workflow is unchanged.
|
|
169
|
+
- **Optional v3 pipeline (a11y contract validation):** Import [GenAT Accessibility Tests (with Login + JIRA) v3.json](../workflows/GenAT%20Accessibility%20Tests%20(with%20Login%20+%20JIRA)%20v3.json) and set **N8N_WEBHOOK_URL** to `…/webhook-test/webhook-genat-login-jira-v3`. Same as v2, plus a terminal **A11y contract validator** node that sets **`a11yContractOk`** (boolean) and **`a11yContractErrors`** (string[]) on the webhook JSON. The MCP also exposes **`_a11yContractOk`** / **`_a11yContractErrors`**. Optional JSON body **`failOnA11yContract`: true** fails the execution when the contract check fails (default is warn-only: still returns generated code). To rebuild v3 from an updated v2 in this repo, run `node scripts/build-genat-v3-workflow.mjs` from the **n8n_playwright_tests** root (regenerates **`webhookId`**; re-import or update the webhook in n8n if needed).
|
|
165
170
|
- **DOM Analyzer** and **Accessibility Analyzer** services running (see main README: `npm run services` from repo root)
|
|
166
171
|
|
|
167
172
|
## Standalone script: run-genat.mjs
|
|
@@ -212,7 +217,7 @@ When creating run-genat.mjs: Use webhook-genat-login, NOT webhook-genat. Prefer
|
|
|
212
217
|
|
|
213
218
|
- **url** (string): Page URL to analyze for accessibility.
|
|
214
219
|
- **parentProjectFolder** (string): Path to the project root (TypeScript, JavaScript, or Python Playwright).
|
|
215
|
-
- **writeToProject** (boolean, optional): If `true`, write generated files under `tests/accessibility/` by default; for **pytest-bdd** Python projects, files go under `features/` / `step_defs/` (or `tests/features`, `steps`, etc.) when those directories exist.
|
|
220
|
+
- **writeToProject** (boolean, optional): If `true`, write generated files under `tests/accessibility/` by default; for **pytest-bdd** Python projects, files go under `features/` / `step_defs/` (or `tests/features`, `steps`, etc.) when those directories exist. New projects get **`step_defs/test_accessibility_steps.py`** when `accessibility_steps.py` is not already present.
|
|
216
221
|
- **maxAssertions** (number, optional): Override default assertion count for complex pages (e.g. `25`).
|
|
217
222
|
- **scopeToRegions** (string[], optional): Limit tests to specific regions (e.g. `["header", "main", "table"]`).
|
|
218
223
|
- **analyzeStates** (boolean, optional): If `true`, run axe in expanded dropdown/combobox states and include state-specific violations. Increases analysis time.
|
|
@@ -220,12 +225,13 @@ When creating run-genat.mjs: Use webhook-genat-login, NOT webhook-genat. Prefer
|
|
|
220
225
|
- **loginPassword** (string, optional): Login password. Overrides .env when provided. Requires loginUsername.
|
|
221
226
|
- **loginUrl** (string, optional): Login page URL if different from target URL.
|
|
222
227
|
- **jiraNumber** (string, optional): JIRA issue key (e.g. PROJ-123). When provided, the workflow fetches the issue and injects acceptance criteria into the AI prompt.
|
|
228
|
+
- **failOnA11yContract** (boolean, optional): When **N8N_WEBHOOK_URL** points at the **v3** JIRA workflow (`webhook-genat-login-jira-v3`), set `true` to make n8n error if the **A11y contract validator** fails (default is warn-only: JSON still returns generated files with `a11yContractOk` / `a11yContractErrors`).
|
|
223
229
|
|
|
224
230
|
Login credentials: provide in the prompt (loginUsername, loginPassword) or in project `.env` (TEST_USER, TEST_PASSWORD, LOGIN_USER, LOGIN_PASSWORD). Tool params override .env. For sensitive environments, prefer .env (gitignored) since credentials in the prompt are visible in chat history.
|
|
225
231
|
|
|
226
232
|
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
233
|
|
|
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.
|
|
234
|
+
**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. With the **v3** JIRA workflow, responses may include **`a11yContractOk`** / **`a11yContractErrors`** and mirrored **`_a11yContractOk`** / **`_a11yContractErrors`** (static checks for `withTags` / `AxeBuilder` on TS/JS and axe-playwright-python usage on Python).
|
|
229
235
|
|
|
230
236
|
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.
|
|
231
237
|
|
|
@@ -234,10 +240,15 @@ Generated tests include keyboard accessibility checks (Tab order, focus) by defa
|
|
|
234
240
|
GenAT does not guarantee that every Gherkin line has an implemented step. For **pytest-bdd** projects, the calling application or CI can:
|
|
235
241
|
|
|
236
242
|
1. Parse `*.feature` step lines (after `Given`/`When`/`Then`/`And`/`But`).
|
|
237
|
-
2. Confirm `accessibility_steps.py` (
|
|
243
|
+
2. Confirm `test_accessibility_steps.py` or `accessibility_steps.py` (your step module) defines matching `@given`/`@when`/`@then` patterns (or run `pytest --collect-only` / a dry run).
|
|
238
244
|
|
|
239
245
|
Keep this as an optional pre-merge check alongside `_stepDefIncomplete`.
|
|
240
246
|
|
|
247
|
+
### pytest-bdd discovery and axe-playwright-python
|
|
248
|
+
|
|
249
|
+
- **Pytest** collects `test_*.py` by default. GenAT now prefers writing **`step_defs/test_accessibility_steps.py`** for new projects; if **`accessibility_steps.py`** already exists, that path is used. Ensure **`@scenario`** test functions are in a collected module (often the same file when named `test_*.py`) or import step defs from **`conftest.py`** / **`pytest_plugins`**.
|
|
250
|
+
- **axe-playwright-python (sync):** `from axe_playwright_python.sync_playwright import Axe`; `axe = Axe()`; `results = axe.run(page)`; read **`results.response`** (axe-core JSON) and **`results.violations_count`**. Do not invent other APIs—see [axe-playwright-python docs](https://pamelafox.github.io/axe-playwright-python/).
|
|
251
|
+
|
|
241
252
|
## Sample prompts
|
|
242
253
|
|
|
243
254
|
See [prompts.md](prompts.md) for a full list of prompts including specific parent folders, login, JIRA, and combined options.
|
package/detect-framework.js
CHANGED
|
@@ -77,15 +77,21 @@ function collectProjectLayoutHints(root, scriptType, bddFramework) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (existsSync(join(root, 'step_defs'))) {
|
|
80
|
-
parts.push(
|
|
80
|
+
parts.push(
|
|
81
|
+
'Put step definitions under step_defs/; prefer test_accessibility_steps.py (test_*.py) so pytest discovers @scenario tests, or keep accessibility_steps.py and import that module from conftest.py'
|
|
82
|
+
);
|
|
81
83
|
} else if (existsSync(join(root, 'steps'))) {
|
|
82
|
-
parts.push('Put step definitions under steps
|
|
84
|
+
parts.push('Put step definitions under steps/; prefer test_*.py naming for pytest collection or import via conftest.py');
|
|
83
85
|
} else if (existsSync(join(root, 'tests', 'step_defs'))) {
|
|
84
|
-
parts.push('Put step definitions under tests/step_defs
|
|
86
|
+
parts.push('Put step definitions under tests/step_defs/; prefer test_accessibility_steps.py or conftest import');
|
|
85
87
|
} else {
|
|
86
|
-
parts.push('Prefer step_defs/ for pytest-bdd
|
|
88
|
+
parts.push('Prefer step_defs/ for pytest-bdd; use test_accessibility_steps.py for pytest discovery of scenario tests');
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
parts.push(
|
|
92
|
+
'Pytest collects test_*.py by default; ensure BDD scenario functions (@scenario) live in a collected module or are pulled in via conftest/pytest_plugins'
|
|
93
|
+
);
|
|
94
|
+
|
|
89
95
|
const conftestPaths = walkWithPaths(root, (f) => f === 'conftest.py');
|
|
90
96
|
const fixtureNames = new Set();
|
|
91
97
|
for (const p of conftestPaths.slice(0, 3)) {
|
package/index.js
CHANGED
|
@@ -59,7 +59,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body, signal })
|
|
|
59
59
|
const server = new McpServer(
|
|
60
60
|
{
|
|
61
61
|
name: 'GenAT',
|
|
62
|
-
version: '2.3.
|
|
62
|
+
version: '2.3.5',
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
capabilities: {
|
|
@@ -122,6 +122,12 @@ server.registerTool(
|
|
|
122
122
|
.string()
|
|
123
123
|
.optional()
|
|
124
124
|
.describe('JIRA issue key (e.g. PROJ-123, RB-40039). Extract from prompts like "JIRA RB-40039" or "JIRA ticket PROJ-123". Pass only the key (e.g. RB-40039), not "JIRA RB-40039".'),
|
|
125
|
+
failOnA11yContract: z
|
|
126
|
+
.boolean()
|
|
127
|
+
.optional()
|
|
128
|
+
.describe(
|
|
129
|
+
'v3 workflow only (webhook-genat-login-jira-v3): if true, n8n errors when static a11y contract checks fail on generated code. Default false: response still includes a11yContractOk / a11yContractErrors.'
|
|
130
|
+
),
|
|
125
131
|
},
|
|
126
132
|
},
|
|
127
133
|
async ({
|
|
@@ -137,6 +143,7 @@ server.registerTool(
|
|
|
137
143
|
requiresAuth,
|
|
138
144
|
forceLogin,
|
|
139
145
|
jiraNumber,
|
|
146
|
+
failOnA11yContract,
|
|
140
147
|
}) => {
|
|
141
148
|
if (!url || typeof url !== 'string') {
|
|
142
149
|
return {
|
|
@@ -205,6 +212,7 @@ server.registerTool(
|
|
|
205
212
|
...(requiresAuth === true && { requiresAuth: true }),
|
|
206
213
|
...(forceLogin === true && { forceLogin: true }),
|
|
207
214
|
...(jiraNumber && jiraNumber.trim() && { jiraNumber: jiraNumber.trim() }),
|
|
215
|
+
...(failOnA11yContract === true && { failOnA11yContract: true }),
|
|
208
216
|
};
|
|
209
217
|
|
|
210
218
|
const useInsecureTls = N8N_INSECURE_TLS && N8N_WEBHOOK_URL.startsWith('https://');
|
|
@@ -290,6 +298,13 @@ server.registerTool(
|
|
|
290
298
|
if (inc.reason) data._stepDefIncompleteReason = inc.reason;
|
|
291
299
|
}
|
|
292
300
|
|
|
301
|
+
if (typeof data.a11yContractOk === 'boolean') {
|
|
302
|
+
data._a11yContractOk = data.a11yContractOk;
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(data.a11yContractErrors)) {
|
|
305
|
+
data._a11yContractErrors = data.a11yContractErrors;
|
|
306
|
+
}
|
|
307
|
+
|
|
293
308
|
if (writeToProject && resolvedPath && data) {
|
|
294
309
|
if (data.bddFramework == null) data.bddFramework = framework.bddFramework;
|
|
295
310
|
try {
|
package/package.json
CHANGED
package/pytest-bdd-paths.js
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve pytest-bdd feature / step / spec paths from project layout (same rules as write-files).
|
|
3
|
+
* Step file: prefer legacy accessibility_steps.py if present; otherwise test_accessibility_steps.py
|
|
4
|
+
* so pytest discovers scenario tests (test_*.py).
|
|
3
5
|
*/
|
|
4
6
|
import { existsSync } from 'fs';
|
|
5
7
|
import { join } from 'path';
|
|
6
8
|
|
|
7
9
|
const DEFAULT_DIR = 'tests/accessibility';
|
|
8
10
|
|
|
11
|
+
const LEGACY_STEP = 'accessibility_steps.py';
|
|
12
|
+
const TEST_STEP = 'test_accessibility_steps.py';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} root
|
|
16
|
+
* @param {string[]} dirSegments - e.g. ['step_defs']
|
|
17
|
+
* @returns {string} relative path to step file
|
|
18
|
+
*/
|
|
19
|
+
function pickStepFileUnderDir(root, dirSegments) {
|
|
20
|
+
const legacy = join(...dirSegments, LEGACY_STEP);
|
|
21
|
+
const testNamed = join(...dirSegments, TEST_STEP);
|
|
22
|
+
if (existsSync(join(root, legacy))) return legacy;
|
|
23
|
+
if (existsSync(join(root, testNamed))) return testNamed;
|
|
24
|
+
return testNamed;
|
|
25
|
+
}
|
|
26
|
+
|
|
9
27
|
/**
|
|
10
28
|
* @param {string} root
|
|
11
29
|
* @returns {{ featureRel: string, stepRel: string, specRel: string }}
|
|
@@ -17,11 +35,11 @@ export function resolvePytestBddPaths(root) {
|
|
|
17
35
|
else featureRel = join('features', 'accessibility.feature');
|
|
18
36
|
|
|
19
37
|
let stepRel;
|
|
20
|
-
if (existsSync(join(root, 'step_defs'))) stepRel =
|
|
21
|
-
else if (existsSync(join(root, 'steps'))) stepRel =
|
|
22
|
-
else if (existsSync(join(root, 'tests', 'step_defs'))) stepRel =
|
|
23
|
-
else if (existsSync(join(root, 'tests', 'steps'))) stepRel =
|
|
24
|
-
else stepRel = join('step_defs',
|
|
38
|
+
if (existsSync(join(root, 'step_defs'))) stepRel = pickStepFileUnderDir(root, ['step_defs']);
|
|
39
|
+
else if (existsSync(join(root, 'steps'))) stepRel = pickStepFileUnderDir(root, ['steps']);
|
|
40
|
+
else if (existsSync(join(root, 'tests', 'step_defs'))) stepRel = pickStepFileUnderDir(root, ['tests', 'step_defs']);
|
|
41
|
+
else if (existsSync(join(root, 'tests', 'steps'))) stepRel = pickStepFileUnderDir(root, ['tests', 'steps']);
|
|
42
|
+
else stepRel = join('step_defs', TEST_STEP);
|
|
25
43
|
|
|
26
44
|
const specRel = join(DEFAULT_DIR, 'accessibility_test.py');
|
|
27
45
|
return { featureRel, stepRel, specRel };
|