genat-mcp 2.2.6 → 2.2.8
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 +25 -1
- package/detect-framework.js +60 -5
- package/index.js +14 -4
- package/package.json +1 -1
- package/run-genat.mjs +9 -1
- package/write-files.js +95 -6
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ MCP server that exposes a **GenAT** tool: generate accessibility tests for a URL
|
|
|
4
4
|
|
|
5
5
|
Full setup (n8n workflow, services, usage) is in the [project README](../README.md).
|
|
6
6
|
|
|
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
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
**If the package is published to npm** (otherwise you get 404):
|
|
@@ -39,6 +41,7 @@ npm install /path/to/n8n_playwright_tests/mcp-genat
|
|
|
39
41
|
| **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
42
|
| **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. |
|
|
41
43
|
| **GENAT_PROJECT_ROOT** | No | (cwd) | Base path for resolving `parentProjectFolder`. When set, `parentProjectFolder` is resolved relative to this path. Use when your project is outside the MCP's working directory. Example: if your pytest project is at `/Users/me/projects/ill_tests`, set `GENAT_PROJECT_ROOT=/Users/me/projects` and use `parentProjectFolder ill_tests` in the prompt. |
|
|
44
|
+
| **GENAT_FETCH_TIMEOUT_MS** | No | 600000 (10 min) | Max time in milliseconds to wait for the n8n webhook response. Increase if GenAT times out before the workflow completes (e.g. complex pages with JIRA + AI Agent). Set in MCP env, e.g. `GENAT_FETCH_TIMEOUT_MS=900000` for 15 minutes. |
|
|
42
45
|
|
|
43
46
|
## Framework detection and auto-discovery
|
|
44
47
|
|
|
@@ -48,6 +51,8 @@ GenAT detects **language** (TypeScript, JavaScript, Python), **BDD framework** (
|
|
|
48
51
|
|
|
49
52
|
**Path guidance:** Pass the project root (e.g. `.` or `/path/to/project`). GenAT will auto-discover the best folder when tests are in `tests/` or `e2e/`. For monorepos, pass the specific test project folder (e.g. `packages/e2e-tests`) when the root has mixed content.
|
|
50
53
|
|
|
54
|
+
**pytest-bdd (Python):** When `scriptType` is Python and `bddFramework` is `pytest-bdd`, detection also scans `conftest.py` for common Playwright fixtures (`browser_page`, `page`, `base_url`, etc.) and prefers `features/` or `tests/features/` and `step_defs/` or `steps/` for layout hints. Those hints are sent as `projectLayoutHints` in the webhook body (and merged into `projectSummary`) so the n8n workflow can steer the model. Generated code uses **axe-playwright-python** (`from axe_playwright_python.sync_playwright import Axe`); ensure `pip install axe-playwright-python pytest-bdd pytest-playwright` (or your lockfile equivalent) in the project.
|
|
55
|
+
|
|
51
56
|
## Cursor MCP config
|
|
52
57
|
|
|
53
58
|
**Using npx (local install):**
|
|
@@ -108,6 +113,24 @@ GenAT detects **language** (TypeScript, JavaScript, Python), **BDD framework** (
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
```
|
|
116
|
+
|
|
117
|
+
**With GENAT_FETCH_TIMEOUT_MS** (increase timeout when workflow takes longer):
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"mcpServers": {
|
|
122
|
+
"GenAT": {
|
|
123
|
+
"command": "npx",
|
|
124
|
+
"args": ["genat-mcp"],
|
|
125
|
+
"env": {
|
|
126
|
+
"GENAT_FETCH_TIMEOUT_MS": "900000",
|
|
127
|
+
"N8N_WEBHOOK_URL": "https://n8n.example.com/webhook-test/webhook-genat-login"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
(900000 = 15 minutes. Default is 600000 = 10 minutes.)
|
|
111
134
|
Then use `parentProjectFolder ill_tests` in the prompt to target `/Users/me/projects/ill_tests`.
|
|
112
135
|
|
|
113
136
|
**With optional params for complex pages** (`N8N_MAX_ASSERTIONS`, `N8N_SCOPE_TO_REGIONS`, `N8N_ANALYZE_STATES`):
|
|
@@ -137,6 +160,7 @@ If the global binary is not on PATH, use the full path, e.g. `node $(npm root -g
|
|
|
137
160
|
|
|
138
161
|
- **Node.js** 20+
|
|
139
162
|
- **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.
|
|
163
|
+
- **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.
|
|
140
164
|
- **DOM Analyzer** and **Accessibility Analyzer** services running (see main README: `npm run services` from repo root)
|
|
141
165
|
|
|
142
166
|
## Standalone script: run-genat.mjs
|
|
@@ -187,7 +211,7 @@ When creating run-genat.mjs: Use webhook-genat-login, NOT webhook-genat. Prefer
|
|
|
187
211
|
|
|
188
212
|
- **url** (string): Page URL to analyze for accessibility.
|
|
189
213
|
- **parentProjectFolder** (string): Path to the project root (TypeScript, JavaScript, or Python Playwright).
|
|
190
|
-
- **writeToProject** (boolean, optional): If `true`, write generated files under `tests/accessibility/`
|
|
214
|
+
- **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.
|
|
191
215
|
- **maxAssertions** (number, optional): Override default assertion count for complex pages (e.g. `25`).
|
|
192
216
|
- **scopeToRegions** (string[], optional): Limit tests to specific regions (e.g. `["header", "main", "table"]`).
|
|
193
217
|
- **analyzeStates** (boolean, optional): If `true`, run axe in expanded dropdown/combobox states and include state-specific violations. Increases analysis time.
|
package/detect-framework.js
CHANGED
|
@@ -57,9 +57,61 @@ function findBestProjectRoot(root) {
|
|
|
57
57
|
return best;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Layout + conftest hints for pytest-bdd Python projects (empty string otherwise).
|
|
62
|
+
* @param {string} root
|
|
63
|
+
* @param {string} scriptType
|
|
64
|
+
* @param {string} bddFramework
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
function collectProjectLayoutHints(root, scriptType, bddFramework) {
|
|
68
|
+
if (scriptType !== 'python' || bddFramework !== 'pytest-bdd') return '';
|
|
69
|
+
|
|
70
|
+
const parts = [];
|
|
71
|
+
if (existsSync(join(root, 'features'))) {
|
|
72
|
+
parts.push('Put new .feature files under features/');
|
|
73
|
+
} else if (existsSync(join(root, 'tests', 'features'))) {
|
|
74
|
+
parts.push('Put new .feature files under tests/features/');
|
|
75
|
+
} else {
|
|
76
|
+
parts.push('Prefer features/ at project root for Gherkin (.feature) files');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (existsSync(join(root, 'step_defs'))) {
|
|
80
|
+
parts.push('Put step definitions under step_defs/ (e.g. accessibility_steps.py)');
|
|
81
|
+
} else if (existsSync(join(root, 'steps'))) {
|
|
82
|
+
parts.push('Put step definitions under steps/');
|
|
83
|
+
} else if (existsSync(join(root, 'tests', 'step_defs'))) {
|
|
84
|
+
parts.push('Put step definitions under tests/step_defs/');
|
|
85
|
+
} else {
|
|
86
|
+
parts.push('Prefer step_defs/ for pytest-bdd step modules');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const conftestPaths = walkWithPaths(root, (f) => f === 'conftest.py');
|
|
90
|
+
const fixtureNames = new Set();
|
|
91
|
+
for (const p of conftestPaths.slice(0, 3)) {
|
|
92
|
+
try {
|
|
93
|
+
const content = readFileSync(p, 'utf8');
|
|
94
|
+
const re = /def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
|
|
95
|
+
let m;
|
|
96
|
+
while ((m = re.exec(content)) !== null) {
|
|
97
|
+
const name = m[1];
|
|
98
|
+
if (['page', 'browser_page', 'base_url', 'context', 'browser', 'playwright', 'request'].includes(name) || /_page$/.test(name)) {
|
|
99
|
+
fixtureNames.add(name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (_) {}
|
|
103
|
+
}
|
|
104
|
+
if (fixtureNames.size) {
|
|
105
|
+
parts.push(`Use these pytest fixtures from conftest (parameter names): ${[...fixtureNames].join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
parts.push('Dependencies: pip install axe-playwright-python pytest-bdd pytest-playwright');
|
|
109
|
+
return parts.join('; ');
|
|
110
|
+
}
|
|
111
|
+
|
|
60
112
|
/**
|
|
61
113
|
* @param {string} parentProjectFolder - absolute or relative path to project root
|
|
62
|
-
* @returns {{ scriptType: 'typescript'|'javascript'|'python', bddFramework: string, pageObject: boolean, projectSummary: string }}
|
|
114
|
+
* @returns {{ scriptType: 'typescript'|'javascript'|'python', bddFramework: string, pageObject: boolean, projectSummary: string, projectLayoutHints?: string }}
|
|
63
115
|
*/
|
|
64
116
|
export function detectFramework(parentProjectFolder) {
|
|
65
117
|
let root = resolve(parentProjectFolder);
|
|
@@ -69,6 +121,7 @@ export function detectFramework(parentProjectFolder) {
|
|
|
69
121
|
bddFramework: 'none',
|
|
70
122
|
pageObject: false,
|
|
71
123
|
projectSummary: `Folder not found or empty; using defaults. (resolved: ${root})`,
|
|
124
|
+
projectLayoutHints: '',
|
|
72
125
|
};
|
|
73
126
|
}
|
|
74
127
|
|
|
@@ -284,16 +337,18 @@ export function detectFramework(parentProjectFolder) {
|
|
|
284
337
|
}
|
|
285
338
|
} catch (_) {}
|
|
286
339
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
340
|
+
const projectLayoutHints = collectProjectLayoutHints(root, scriptType, bddFramework);
|
|
341
|
+
const summaryWithHints = [...(summaryParts.length ? summaryParts : ['No framework hints detected'])];
|
|
342
|
+
if (projectLayoutHints) summaryWithHints.push(projectLayoutHints);
|
|
343
|
+
|
|
344
|
+
const projectSummary = [...summaryWithHints, `(scanned: ${root})`].join('; ');
|
|
291
345
|
|
|
292
346
|
return {
|
|
293
347
|
scriptType,
|
|
294
348
|
bddFramework: ALLOWED_BDD.includes(bddFramework) ? bddFramework : 'none',
|
|
295
349
|
pageObject,
|
|
296
350
|
projectSummary,
|
|
351
|
+
...(projectLayoutHints ? { projectLayoutHints } : {}),
|
|
297
352
|
};
|
|
298
353
|
}
|
|
299
354
|
|
package/index.js
CHANGED
|
@@ -16,12 +16,14 @@ import { writeGeneratedFiles } from './write-files.js';
|
|
|
16
16
|
const N8N_WEBHOOK_URL = process.env.N8N_WEBHOOK_URL || 'http://localhost:5678/webhook-test/webhook-genat-login';
|
|
17
17
|
const SERVICE_BASE_URL = process.env.SERVICE_BASE_URL || null;
|
|
18
18
|
const N8N_INSECURE_TLS = /^1|true|yes$/i.test(process.env.N8N_INSECURE_TLS || '');
|
|
19
|
+
const GENAT_FETCH_TIMEOUT_MS = Math.max(60000, parseInt(process.env.GENAT_FETCH_TIMEOUT_MS || '600000', 10) || 600000);
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* HTTPS fetch with rejectUnauthorized: false for self-signed/internal CA certs.
|
|
22
23
|
* Returns a Response-like object { ok, status, text(), json() }.
|
|
24
|
+
* Supports signal for timeout.
|
|
23
25
|
*/
|
|
24
|
-
function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
|
|
26
|
+
function insecureHttpsFetch(url, { method = 'GET', headers = {}, body, signal }) {
|
|
25
27
|
return new Promise((resolve, reject) => {
|
|
26
28
|
const req = https.request(
|
|
27
29
|
url,
|
|
@@ -29,6 +31,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
|
|
|
29
31
|
method,
|
|
30
32
|
headers: { 'Content-Type': 'application/json', ...headers },
|
|
31
33
|
agent: new https.Agent({ rejectUnauthorized: false }),
|
|
34
|
+
signal,
|
|
32
35
|
},
|
|
33
36
|
(res) => {
|
|
34
37
|
let data = '';
|
|
@@ -52,7 +55,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
|
|
|
52
55
|
const server = new McpServer(
|
|
53
56
|
{
|
|
54
57
|
name: 'GenAT',
|
|
55
|
-
version: '2.2.
|
|
58
|
+
version: '2.2.8',
|
|
56
59
|
},
|
|
57
60
|
{
|
|
58
61
|
capabilities: {
|
|
@@ -155,6 +158,7 @@ server.registerTool(
|
|
|
155
158
|
bddFramework: framework.bddFramework,
|
|
156
159
|
pageObject: framework.pageObject,
|
|
157
160
|
projectSummary: framework.projectSummary,
|
|
161
|
+
...(framework.projectLayoutHints && { projectLayoutHints: framework.projectLayoutHints }),
|
|
158
162
|
...(SERVICE_BASE_URL && { serviceBaseUrl: SERVICE_BASE_URL }),
|
|
159
163
|
...(maxAssertionsResolved != null && !isNaN(maxAssertionsResolved) && { maxAssertions: maxAssertionsResolved }),
|
|
160
164
|
...(scopeToRegionsResolved?.length && { scopeToRegions: scopeToRegionsResolved }),
|
|
@@ -171,10 +175,13 @@ server.registerTool(
|
|
|
171
175
|
};
|
|
172
176
|
|
|
173
177
|
const useInsecureTls = N8N_INSECURE_TLS && N8N_WEBHOOK_URL.startsWith('https://');
|
|
178
|
+
const controller = new AbortController();
|
|
179
|
+
const timeoutId = setTimeout(() => controller.abort(), GENAT_FETCH_TIMEOUT_MS);
|
|
174
180
|
const fetchOpts = {
|
|
175
181
|
method: 'POST',
|
|
176
182
|
headers: { 'Content-Type': 'application/json' },
|
|
177
183
|
body: JSON.stringify(body),
|
|
184
|
+
signal: controller.signal,
|
|
178
185
|
};
|
|
179
186
|
let response;
|
|
180
187
|
try {
|
|
@@ -190,13 +197,15 @@ server.registerTool(
|
|
|
190
197
|
text: JSON.stringify({
|
|
191
198
|
error: 'Failed to call n8n webhook',
|
|
192
199
|
message: err instanceof Error ? err.message : String(err),
|
|
193
|
-
hint: 'Set N8N_WEBHOOK_URL to your n8n webhook URL. For self-signed/internal certs, set N8N_INSECURE_TLS=1',
|
|
200
|
+
hint: err?.name === 'AbortError' ? `Request timed out after ${GENAT_FETCH_TIMEOUT_MS / 1000}s. Set GENAT_FETCH_TIMEOUT_MS in MCP env to increase (default 600000).` : 'Set N8N_WEBHOOK_URL to your n8n webhook URL. For self-signed/internal certs, set N8N_INSECURE_TLS=1',
|
|
194
201
|
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).`,
|
|
195
202
|
}),
|
|
196
203
|
},
|
|
197
204
|
],
|
|
198
205
|
isError: true,
|
|
199
206
|
};
|
|
207
|
+
} finally {
|
|
208
|
+
clearTimeout(timeoutId);
|
|
200
209
|
}
|
|
201
210
|
|
|
202
211
|
if (!response.ok) {
|
|
@@ -231,8 +240,9 @@ server.registerTool(
|
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
if (writeToProject && resolvedPath && data) {
|
|
243
|
+
if (data.bddFramework == null) data.bddFramework = framework.bddFramework;
|
|
234
244
|
try {
|
|
235
|
-
const written = writeGeneratedFiles(resolvedPath, data);
|
|
245
|
+
const written = await writeGeneratedFiles(resolvedPath, data);
|
|
236
246
|
data._written = written;
|
|
237
247
|
} catch (err) {
|
|
238
248
|
data._writeError = err instanceof Error ? err.message : String(err);
|
package/package.json
CHANGED
package/run-genat.mjs
CHANGED
|
@@ -66,7 +66,13 @@ async function main() {
|
|
|
66
66
|
try {
|
|
67
67
|
framework = detectFramework(parentProjectFolder);
|
|
68
68
|
} catch (err) {
|
|
69
|
-
framework = {
|
|
69
|
+
framework = {
|
|
70
|
+
scriptType: 'typescript',
|
|
71
|
+
bddFramework: 'none',
|
|
72
|
+
pageObject: false,
|
|
73
|
+
projectSummary: '',
|
|
74
|
+
projectLayoutHints: '',
|
|
75
|
+
};
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
let login = {};
|
|
@@ -86,6 +92,7 @@ async function main() {
|
|
|
86
92
|
bddFramework: framework.bddFramework,
|
|
87
93
|
pageObject: framework.pageObject,
|
|
88
94
|
projectSummary: framework.projectSummary,
|
|
95
|
+
...(framework.projectLayoutHints && { projectLayoutHints: framework.projectLayoutHints }),
|
|
89
96
|
...(maxAssertions != null && !isNaN(maxAssertions) && { maxAssertions }),
|
|
90
97
|
...(scopeToRegions?.length && { scopeToRegions }),
|
|
91
98
|
...(analyzeStates && { analyzeStates: true }),
|
|
@@ -116,6 +123,7 @@ async function main() {
|
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
const data = await response.json();
|
|
126
|
+
if (data.bddFramework == null) data.bddFramework = framework.bddFramework;
|
|
119
127
|
const written = await writeGeneratedFiles(parentProjectFolder, data);
|
|
120
128
|
console.log('GenAT wrote:', written.join(', '));
|
|
121
129
|
}
|
package/write-files.js
CHANGED
|
@@ -2,14 +2,36 @@
|
|
|
2
2
|
* Write generated accessibility test files into the project folder.
|
|
3
3
|
* Uses suggested paths from the workflow response when present.
|
|
4
4
|
*/
|
|
5
|
+
import { existsSync } from 'fs';
|
|
5
6
|
import { writeFile, mkdir } from 'fs/promises';
|
|
6
|
-
import { join, resolve } from 'path';
|
|
7
|
+
import { dirname, join, resolve } from 'path';
|
|
7
8
|
|
|
8
9
|
const DEFAULT_DIR = 'tests/accessibility';
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} root
|
|
13
|
+
* @returns {{ featureRel: string, stepRel: string, specRel: string }}
|
|
14
|
+
*/
|
|
15
|
+
function resolvePytestBddPaths(root) {
|
|
16
|
+
let featureRel;
|
|
17
|
+
if (existsSync(join(root, 'features'))) featureRel = join('features', 'accessibility.feature');
|
|
18
|
+
else if (existsSync(join(root, 'tests', 'features'))) featureRel = join('tests', 'features', 'accessibility.feature');
|
|
19
|
+
else featureRel = join('features', 'accessibility.feature');
|
|
20
|
+
|
|
21
|
+
let stepRel;
|
|
22
|
+
if (existsSync(join(root, 'step_defs'))) stepRel = join('step_defs', 'accessibility_steps.py');
|
|
23
|
+
else if (existsSync(join(root, 'steps'))) stepRel = join('steps', 'accessibility_steps.py');
|
|
24
|
+
else if (existsSync(join(root, 'tests', 'step_defs'))) stepRel = join('tests', 'step_defs', 'accessibility_steps.py');
|
|
25
|
+
else if (existsSync(join(root, 'tests', 'steps'))) stepRel = join('tests', 'steps', 'accessibility_steps.py');
|
|
26
|
+
else stepRel = join('step_defs', 'accessibility_steps.py');
|
|
27
|
+
|
|
28
|
+
const specRel = join(DEFAULT_DIR, 'accessibility_test.py');
|
|
29
|
+
return { featureRel, stepRel, specRel };
|
|
30
|
+
}
|
|
31
|
+
|
|
10
32
|
/**
|
|
11
33
|
* @param {string} parentProjectFolder - project root path
|
|
12
|
-
* @param {{ testCode?: string, scriptType?: string, featureFile?: string, stepDefCode?: string, pageObjectCode?: string, suggestedPaths?: Record<string, string> }} data - workflow response
|
|
34
|
+
* @param {{ testCode?: string, scriptType?: string, bddFramework?: string, featureFile?: string, stepDefCode?: string, pageObjectCode?: string, suggestedPaths?: Record<string, string> }} data - workflow response
|
|
13
35
|
* @returns {string[]} list of written file paths
|
|
14
36
|
*/
|
|
15
37
|
export async function writeGeneratedFiles(parentProjectFolder, data) {
|
|
@@ -19,6 +41,8 @@ export async function writeGeneratedFiles(parentProjectFolder, data) {
|
|
|
19
41
|
|
|
20
42
|
const written = [];
|
|
21
43
|
const scriptType = (data.scriptType || 'typescript').toLowerCase();
|
|
44
|
+
const bddFramework = (data.bddFramework || 'none').toLowerCase();
|
|
45
|
+
const usePytestBddLayout = scriptType === 'python' && bddFramework === 'pytest-bdd';
|
|
22
46
|
|
|
23
47
|
const defaultNames = {
|
|
24
48
|
typescript: { spec: 'accessibility.spec.ts', steps: 'steps.ts', page: 'AccessibilityPage.ts' },
|
|
@@ -28,27 +52,92 @@ export async function writeGeneratedFiles(parentProjectFolder, data) {
|
|
|
28
52
|
const names = defaultNames[scriptType] || defaultNames.typescript;
|
|
29
53
|
const suggested = data.suggestedPaths || {};
|
|
30
54
|
|
|
55
|
+
if (usePytestBddLayout) {
|
|
56
|
+
const pb = resolvePytestBddPaths(root);
|
|
57
|
+
const dup =
|
|
58
|
+
data.testCode &&
|
|
59
|
+
data.stepDefCode &&
|
|
60
|
+
data.testCode.trim() === data.stepDefCode.trim();
|
|
61
|
+
|
|
62
|
+
if (data.featureFile) {
|
|
63
|
+
const relPath = suggested.feature || pb.featureRel;
|
|
64
|
+
const abs = join(root, relPath);
|
|
65
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
66
|
+
await writeFile(abs, data.featureFile, 'utf8');
|
|
67
|
+
written.push(relPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (dup) {
|
|
71
|
+
const relPath = suggested.stepDef || pb.stepRel;
|
|
72
|
+
const abs = join(root, relPath);
|
|
73
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
74
|
+
await writeFile(abs, data.stepDefCode || data.testCode || '', 'utf8');
|
|
75
|
+
written.push(relPath);
|
|
76
|
+
} else {
|
|
77
|
+
if (data.stepDefCode) {
|
|
78
|
+
const relPath = suggested.stepDef || pb.stepRel;
|
|
79
|
+
const abs = join(root, relPath);
|
|
80
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
81
|
+
await writeFile(abs, data.stepDefCode, 'utf8');
|
|
82
|
+
written.push(relPath);
|
|
83
|
+
}
|
|
84
|
+
if (data.testCode) {
|
|
85
|
+
if (data.stepDefCode) {
|
|
86
|
+
const relPath = suggested.spec || pb.specRel;
|
|
87
|
+
const abs = join(root, relPath);
|
|
88
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
89
|
+
await writeFile(abs, data.testCode, 'utf8');
|
|
90
|
+
written.push(relPath);
|
|
91
|
+
} else {
|
|
92
|
+
const relPath = suggested.stepDef || pb.stepRel;
|
|
93
|
+
const abs = join(root, relPath);
|
|
94
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
95
|
+
await writeFile(abs, data.testCode, 'utf8');
|
|
96
|
+
written.push(relPath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (data.pageObjectCode) {
|
|
102
|
+
const relPath = suggested.pageObject || join(DEFAULT_DIR, names.page);
|
|
103
|
+
const abs = join(root, relPath);
|
|
104
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
105
|
+
await writeFile(abs, data.pageObjectCode, 'utf8');
|
|
106
|
+
written.push(relPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return written;
|
|
110
|
+
}
|
|
111
|
+
|
|
31
112
|
if (data.testCode) {
|
|
32
113
|
const relPath = suggested.spec || join(DEFAULT_DIR, names.spec);
|
|
33
|
-
|
|
114
|
+
const abs = join(root, relPath);
|
|
115
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
116
|
+
await writeFile(abs, data.testCode, 'utf8');
|
|
34
117
|
written.push(relPath);
|
|
35
118
|
}
|
|
36
119
|
|
|
37
120
|
if (data.featureFile) {
|
|
38
121
|
const relPath = suggested.feature || join(DEFAULT_DIR, 'accessibility.feature');
|
|
39
|
-
|
|
122
|
+
const abs = join(root, relPath);
|
|
123
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
124
|
+
await writeFile(abs, data.featureFile, 'utf8');
|
|
40
125
|
written.push(relPath);
|
|
41
126
|
}
|
|
42
127
|
|
|
43
128
|
if (data.stepDefCode) {
|
|
44
129
|
const relPath = suggested.stepDef || join(DEFAULT_DIR, names.steps);
|
|
45
|
-
|
|
130
|
+
const abs = join(root, relPath);
|
|
131
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
132
|
+
await writeFile(abs, data.stepDefCode, 'utf8');
|
|
46
133
|
written.push(relPath);
|
|
47
134
|
}
|
|
48
135
|
|
|
49
136
|
if (data.pageObjectCode) {
|
|
50
137
|
const relPath = suggested.pageObject || join(DEFAULT_DIR, names.page);
|
|
51
|
-
|
|
138
|
+
const abs = join(root, relPath);
|
|
139
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
140
|
+
await writeFile(abs, data.pageObjectCode, 'utf8');
|
|
52
141
|
written.push(relPath);
|
|
53
142
|
}
|
|
54
143
|
|