genat-mcp 2.1.2 → 2.2.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/README.md CHANGED
@@ -35,6 +35,9 @@ npm install /path/to/n8n_playwright_tests/mcp-genat
35
35
  | **N8N_WEBHOOK_URL** | No | `http://localhost:5678/webhook-test/webhook-genat-login` | n8n GenAT webhook URL. Set this if your n8n instance is elsewhere (e.g. `https://your-n8n-host/webhook-test/webhook-genat-login`). |
36
36
  | **N8N_INSECURE_TLS** | No | (off) | Set to `1`, `true`, or `yes` to skip TLS certificate verification for HTTPS webhooks. Use only for dev/internal n8n with self-signed or internal CA certs (e.g. K8s). Not recommended for production. |
37
37
  | **SERVICE_BASE_URL** | No | (none) | Base URL for DOM Analyzer and Accessibility Analyzer (e.g. `http://host.docker.internal` or `http://your-tunnel.ngrok.io`). Required when n8n runs on a remote host (K8s) so the workflow can reach the services. The workflow appends `:3456/analyze-dom` and `:3458/analyze-accessibility`. |
38
+ | **N8N_MAX_ASSERTIONS** | No | (none) | Override assertion count for complex pages (e.g. `25`). Used as default when the tool is invoked without `maxAssertions`; also used by run-genat.mjs. |
39
+ | **N8N_SCOPE_TO_REGIONS** | No | (none) | Comma-separated regions to focus tests on (e.g. `header,main,table`). Used as default when the tool is invoked without `scopeToRegions`; also used by run-genat.mjs. |
40
+ | **N8N_ANALYZE_STATES** | No | (off) | Set to `1`, `true`, or `yes` to analyze dropdown/combobox expanded states and include state-specific violations in generated tests. Used as default when the tool is invoked without `analyzeStates`; also used by run-genat.mjs. Increases analysis time. |
38
41
 
39
42
  ## Cursor MCP config
40
43
 
@@ -63,6 +66,45 @@ npm install /path/to/n8n_playwright_tests/mcp-genat
63
66
  }
64
67
  ```
65
68
 
69
+ **With environment variables** (e.g. remote n8n, self-signed certs):
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "GenAT": {
75
+ "command": "npx",
76
+ "args": ["genat-mcp"],
77
+ "env": {
78
+ "N8N_WEBHOOK_URL": "https://n8n.example.com/webhook-test/webhook-genat-login",
79
+ "N8N_INSECURE_TLS": "1",
80
+ "SERVICE_BASE_URL": "http://your-tunnel.ngrok.io"
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ **With optional params for complex pages** (`N8N_MAX_ASSERTIONS`, `N8N_SCOPE_TO_REGIONS`, `N8N_ANALYZE_STATES`):
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "GenAT": {
93
+ "command": "npx",
94
+ "args": ["genat-mcp"],
95
+ "env": {
96
+ "N8N_WEBHOOK_URL": "https://n8n.example.com/webhook-test/webhook-genat-login",
97
+ "N8N_MAX_ASSERTIONS": "30",
98
+ "N8N_SCOPE_TO_REGIONS": "header,main,table",
99
+ "N8N_ANALYZE_STATES": "1"
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ These env vars are passed to the webhook: `maxAssertions` (number), `scopeToRegions` (array), `analyzeStates` (boolean). Use `N8N_MAX_ASSERTIONS` for complex pages that need more assertions; `N8N_SCOPE_TO_REGIONS` to focus tests on specific regions; `N8N_ANALYZE_STATES` to run axe in expanded dropdown/combobox states (increases analysis time).
107
+
66
108
  If the global binary is not on PATH, use the full path, e.g. `node $(npm root -g)/genat-mcp/index.js`.
67
109
 
68
110
  ## Requirements
@@ -83,6 +125,15 @@ N8N_WEBHOOK_URL='https://your-n8n/webhook-test/webhook-genat-login' N8N_INSECURE
83
125
  N8N_WEBHOOK_URL='https://your-n8n/webhook-test/webhook-genat-login' N8N_INSECURE_TLS=1 node run-genat.mjs https://example.com /path/to/project
84
126
  ```
85
127
 
128
+ **Optional env vars** (same as MCP tool parameters):
129
+ - `N8N_MAX_ASSERTIONS` – Override assertion count (e.g. `25`)
130
+ - `N8N_SCOPE_TO_REGIONS` – Comma-separated regions (e.g. `header,main,table`)
131
+ - `N8N_ANALYZE_STATES` – Set to `1` to analyze expanded dropdown/combobox states
132
+
133
+ ```bash
134
+ N8N_MAX_ASSERTIONS=30 N8N_SCOPE_TO_REGIONS=header,main N8N_ANALYZE_STATES=1 node node_modules/genat-mcp/run-genat.mjs https://example.com .
135
+ ```
136
+
86
137
  **Important:** Use `webhook-genat-login` (not `webhook-genat`). Import and activate [genat-accessibility-tests-with-login.json](../workflows/genat-accessibility-tests-with-login.json) in n8n.
87
138
 
88
139
  **Optional – Cursor rule:** Copy the rule so the AI uses the correct webhook when creating workarounds (the MCP error responses also include this hint):
@@ -111,9 +162,41 @@ When creating run-genat.mjs: Use webhook-genat-login, NOT webhook-genat. Prefer
111
162
  - **url** (string): Page URL to analyze for accessibility.
112
163
  - **parentProjectFolder** (string): Path to the project root (TypeScript, JavaScript, or Python Playwright).
113
164
  - **writeToProject** (boolean, optional): If `true`, write generated files under `tests/accessibility/` in the project folder.
165
+ - **maxAssertions** (number, optional): Override default assertion count for complex pages (e.g. `25`).
166
+ - **scopeToRegions** (string[], optional): Limit tests to specific regions (e.g. `["header", "main", "table"]`).
167
+ - **analyzeStates** (boolean, optional): If `true`, run axe in expanded dropdown/combobox states and include state-specific violations. Increases analysis time.
168
+ - **loginUsername** (string, optional): Login username. Overrides .env when provided. Requires loginPassword.
169
+ - **loginPassword** (string, optional): Login password. Overrides .env when provided. Requires loginUsername.
170
+ - **loginUrl** (string, optional): Login page URL if different from target URL.
171
+
172
+ 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.
114
173
 
115
174
  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).
116
175
 
176
+ 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.
177
+
178
+ ## Sample prompts
179
+
180
+ **With login credentials in the prompt:**
181
+
182
+ ```
183
+ Use GenAT to generate accessibility tests for https://myapp.example.com/dashboard. Use parentProjectFolder . and writeToProject true. Login with username myuser and password mypass.
184
+ ```
185
+
186
+ ```
187
+ Use GenAT with url https://myapp.example.com, loginUsername admin, loginPassword secret123, parentProjectFolder . Write the files to the project.
188
+ ```
189
+
190
+ **With credentials in .env:**
191
+
192
+ ```
193
+ Use GenAT for https://myapp.example.com/dashboard. Credentials are in .env.
194
+ ```
195
+
196
+ ```
197
+ Use GenAT to generate accessibility tests for https://myapp.example.com/dashboard using this project folder: . Write the files to the project.
198
+ ```
199
+
117
200
  ## Publishing to npm
118
201
 
119
202
  Until the package is published, `npm install genat-mcp` returns 404. To publish:
package/index.js CHANGED
@@ -51,7 +51,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
51
51
  const server = new McpServer(
52
52
  {
53
53
  name: 'GenAT',
54
- version: '2.1.2',
54
+ version: '2.2.1',
55
55
  },
56
56
  {
57
57
  capabilities: {
@@ -64,7 +64,7 @@ server.registerTool(
64
64
  'GenAT',
65
65
  {
66
66
  description:
67
- 'Generate accessibility tests for a URL based on the project folder. Analyzes the page (DOM + a11y), detects project framework (TypeScript, JavaScript, or Python, BDD, Page Object), and returns generated test files (feature, spec/step defs, page objects). Optionally write files to the project folder. On failure: use node node_modules/genat-mcp/run-genat.mjs (webhook-genat-login). Do not create run-genat.mjs with webhook-genat.',
67
+ 'Generate accessibility tests for a URL based on the project folder. Analyzes the page (DOM + a11y), detects project framework (TypeScript, JavaScript, or Python, BDD, Page Object), and returns generated test files (feature, spec/step defs, page objects). Optionally write files to the project folder. On failure: run node node_modules/genat-mcp/run-genat.mjs (NOT run-genat.mjs in project root). Use webhook-genat-login in N8N_WEBHOOK_URL (never webhook-genat).',
68
68
  inputSchema: {
69
69
  url: z.string().describe('Page URL to analyze for accessibility (e.g. https://example.com)'),
70
70
  parentProjectFolder: z
@@ -74,9 +74,33 @@ server.registerTool(
74
74
  .boolean()
75
75
  .optional()
76
76
  .describe('If true, write generated files into the project folder (e.g. tests/accessibility/)'),
77
+ maxAssertions: z
78
+ .number()
79
+ .optional()
80
+ .describe('Override default assertion count (e.g. 25 for complex pages)'),
81
+ scopeToRegions: z
82
+ .array(z.string())
83
+ .optional()
84
+ .describe('Limit tests to specific regions (e.g. ["header", "main", "table"])'),
85
+ analyzeStates: z
86
+ .boolean()
87
+ .optional()
88
+ .describe('Enable interaction-state analysis (dropdowns, modals). Requires Phase 2.'),
89
+ loginUsername: z
90
+ .string()
91
+ .optional()
92
+ .describe('Login username (overrides .env when provided)'),
93
+ loginPassword: z
94
+ .string()
95
+ .optional()
96
+ .describe('Login password (overrides .env when provided)'),
97
+ loginUrl: z
98
+ .string()
99
+ .optional()
100
+ .describe('Login page URL if different from target URL'),
77
101
  },
78
102
  },
79
- async ({ url, parentProjectFolder, writeToProject }) => {
103
+ async ({ url, parentProjectFolder, writeToProject, maxAssertions, scopeToRegions, analyzeStates, loginUsername, loginPassword, loginUrl }) => {
80
104
  if (!url || typeof url !== 'string') {
81
105
  return {
82
106
  content: [{ type: 'text', text: JSON.stringify({ error: 'Missing or invalid "url"' }) }],
@@ -107,6 +131,16 @@ server.registerTool(
107
131
  try {
108
132
  login = detectLogin(parentProjectFolder || '.') || {};
109
133
  } catch (_) {}
134
+
135
+ // Tool params override .env
136
+ if (loginUsername != null && loginUsername !== '') login.username = loginUsername;
137
+ if (loginPassword != null && loginPassword !== '') login.password = loginPassword;
138
+ if (loginUrl != null && loginUrl !== '') login.loginUrl = loginUrl;
139
+
140
+ const maxAssertionsResolved = maxAssertions ?? (process.env.N8N_MAX_ASSERTIONS ? parseInt(process.env.N8N_MAX_ASSERTIONS, 10) : null);
141
+ const scopeToRegionsResolved = scopeToRegions?.length ? scopeToRegions : (process.env.N8N_SCOPE_TO_REGIONS ? process.env.N8N_SCOPE_TO_REGIONS.split(',').map((s) => s.trim()).filter(Boolean) : null);
142
+ const analyzeStatesResolved = analyzeStates || /^1|true|yes$/i.test(process.env.N8N_ANALYZE_STATES || '');
143
+
110
144
  const body = {
111
145
  url,
112
146
  scriptType: framework.scriptType,
@@ -114,12 +148,17 @@ server.registerTool(
114
148
  pageObject: framework.pageObject,
115
149
  projectSummary: framework.projectSummary,
116
150
  ...(SERVICE_BASE_URL && { serviceBaseUrl: SERVICE_BASE_URL }),
117
- ...(login.username && { loginUsername: login.username }),
118
- ...(login.password && { loginPassword: login.password }),
119
- ...(login.loginUrl && { loginUrl: login.loginUrl }),
120
- ...(login.usernameSelector && { loginUsernameSelector: login.usernameSelector }),
121
- ...(login.passwordSelector && { loginPasswordSelector: login.passwordSelector }),
122
- ...(login.submitSelector && { loginSubmitSelector: login.submitSelector }),
151
+ ...(maxAssertionsResolved != null && !isNaN(maxAssertionsResolved) && { maxAssertions: maxAssertionsResolved }),
152
+ ...(scopeToRegionsResolved?.length && { scopeToRegions: scopeToRegionsResolved }),
153
+ ...(analyzeStatesResolved && { analyzeStates: true }),
154
+ ...(login.username && login.password && {
155
+ loginUsername: login.username,
156
+ loginPassword: login.password,
157
+ ...(login.loginUrl && { loginUrl: login.loginUrl }),
158
+ ...(login.usernameSelector && { loginUsernameSelector: login.usernameSelector }),
159
+ ...(login.passwordSelector && { loginPasswordSelector: login.passwordSelector }),
160
+ ...(login.submitSelector && { loginSubmitSelector: login.submitSelector }),
161
+ }),
123
162
  };
124
163
 
125
164
  const useInsecureTls = N8N_INSECURE_TLS && N8N_WEBHOOK_URL.startsWith('https://');
@@ -134,6 +173,7 @@ server.registerTool(
134
173
  ? await insecureHttpsFetch(N8N_WEBHOOK_URL, fetchOpts)
135
174
  : await fetch(N8N_WEBHOOK_URL, fetchOpts);
136
175
  } catch (err) {
176
+ const loginUrl = N8N_WEBHOOK_URL.replace(/webhook-genat\/?$/, 'webhook-genat-login');
137
177
  return {
138
178
  content: [
139
179
  {
@@ -142,7 +182,7 @@ server.registerTool(
142
182
  error: 'Failed to call n8n webhook',
143
183
  message: err instanceof Error ? err.message : String(err),
144
184
  hint: 'Set N8N_WEBHOOK_URL to your n8n webhook URL. For self-signed/internal certs, set N8N_INSECURE_TLS=1',
145
- workaround: 'Run: N8N_WEBHOOK_URL=\'https://your-n8n/webhook-test/webhook-genat-login\' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Use webhook-genat-login (not webhook-genat). Do not create run-genat.mjs with webhook-genat.',
185
+ workaround: `Run: N8N_WEBHOOK_URL='${loginUrl}' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Use node_modules/genat-mcp/run-genat.mjs only (delete any run-genat.mjs in project root that uses webhook-genat).`,
146
186
  }),
147
187
  },
148
188
  ],
@@ -153,6 +193,7 @@ server.registerTool(
153
193
  if (!response.ok) {
154
194
  const text = await response.text();
155
195
  const is404 = response.status === 404;
196
+ const loginUrl = N8N_WEBHOOK_URL.replace(/webhook-genat\/?$/, 'webhook-genat-login');
156
197
  return {
157
198
  content: [
158
199
  {
@@ -161,7 +202,7 @@ server.registerTool(
161
202
  error: `n8n returned ${response.status}`,
162
203
  body: text.slice(0, 1000),
163
204
  ...(is404 && {
164
- workaround: 'Use webhook-genat-login (not webhook-genat). Run: N8N_WEBHOOK_URL=\'https://your-n8n/webhook-test/webhook-genat-login\' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Do not create run-genat.mjs with webhook-genat.',
205
+ workaround: `Run: N8N_WEBHOOK_URL='${loginUrl}' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Use node_modules/genat-mcp/run-genat.mjs only (delete any run-genat.mjs in project root that uses webhook-genat).`,
165
206
  }),
166
207
  }),
167
208
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genat-mcp",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
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",
package/run-genat.mjs CHANGED
@@ -10,6 +10,9 @@
10
10
  * Env:
11
11
  * N8N_WEBHOOK_URL - webhook URL (default: http://localhost:5678/webhook-test/webhook-genat-login)
12
12
  * N8N_INSECURE_TLS - set to 1 for self-signed/internal certs
13
+ * N8N_MAX_ASSERTIONS - override assertion count (e.g. 25)
14
+ * N8N_SCOPE_TO_REGIONS - comma-separated regions (e.g. header,main,table)
15
+ * N8N_ANALYZE_STATES - set to 1 to analyze dropdown/combobox expanded states
13
16
  */
14
17
  import https from 'node:https';
15
18
  import { resolve } from 'node:path';
@@ -71,12 +74,21 @@ async function main() {
71
74
  login = detectLogin(parentProjectFolder) || {};
72
75
  } catch (_) {}
73
76
 
77
+ const maxAssertions = process.env.N8N_MAX_ASSERTIONS ? parseInt(process.env.N8N_MAX_ASSERTIONS, 10) : null;
78
+ const scopeToRegions = process.env.N8N_SCOPE_TO_REGIONS
79
+ ? process.env.N8N_SCOPE_TO_REGIONS.split(',').map((s) => s.trim()).filter(Boolean)
80
+ : null;
81
+ const analyzeStates = /^1|true|yes$/i.test(process.env.N8N_ANALYZE_STATES || '');
82
+
74
83
  const body = {
75
84
  url,
76
85
  scriptType: framework.scriptType,
77
86
  bddFramework: framework.bddFramework,
78
87
  pageObject: framework.pageObject,
79
88
  projectSummary: framework.projectSummary,
89
+ ...(maxAssertions != null && !isNaN(maxAssertions) && { maxAssertions }),
90
+ ...(scopeToRegions?.length && { scopeToRegions }),
91
+ ...(analyzeStates && { analyzeStates: true }),
80
92
  ...(login.username && { loginUsername: login.username }),
81
93
  ...(login.password && { loginPassword: login.password }),
82
94
  ...(login.loginUrl && { loginUrl: login.loginUrl }),