@esimplicity/create-stack-tests 0.1.0
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/create-stack-tests.js +402 -0
- package/package.json +17 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs/promises');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
async function pathExists(p) {
|
|
8
|
+
try {
|
|
9
|
+
await fs.access(p);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function ensureDir(dir) {
|
|
17
|
+
await fs.mkdir(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function writeFileSafe(filePath, content, { force, results }) {
|
|
21
|
+
const exists = await pathExists(filePath);
|
|
22
|
+
if (exists && !force) {
|
|
23
|
+
results.skipped.push(filePath);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await ensureDir(path.dirname(filePath));
|
|
27
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
28
|
+
results.created.push(filePath);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function detectPackageManager(startDir) {
|
|
32
|
+
let dir = startDir;
|
|
33
|
+
while (true) {
|
|
34
|
+
if (await pathExists(path.join(dir, 'bun.lockb'))) return 'bun';
|
|
35
|
+
if (await pathExists(path.join(dir, 'bun.lock'))) return 'bun';
|
|
36
|
+
if (await pathExists(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
37
|
+
if (await pathExists(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
38
|
+
if (await pathExists(path.join(dir, 'package-lock.json'))) return 'npm';
|
|
39
|
+
const parent = path.dirname(dir);
|
|
40
|
+
if (parent === dir) break;
|
|
41
|
+
dir = parent;
|
|
42
|
+
}
|
|
43
|
+
return 'npm';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function commandsFor(pm) {
|
|
47
|
+
switch (pm) {
|
|
48
|
+
case 'bun':
|
|
49
|
+
return { install: 'bun install', test: 'bun run test' };
|
|
50
|
+
case 'pnpm':
|
|
51
|
+
return { install: 'pnpm install', test: 'pnpm test' };
|
|
52
|
+
case 'yarn':
|
|
53
|
+
return { install: 'yarn install', test: 'yarn test' };
|
|
54
|
+
default:
|
|
55
|
+
return { install: 'npm install', test: 'npm test' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseArgs(argv) {
|
|
60
|
+
const args = { dir: 'stack-tests', force: false };
|
|
61
|
+
for (let i = 0; i < argv.length; i++) {
|
|
62
|
+
const arg = argv[i];
|
|
63
|
+
if (arg === '--dir' && argv[i + 1]) {
|
|
64
|
+
args.dir = argv[++i];
|
|
65
|
+
} else if (arg === '--force') {
|
|
66
|
+
args.force = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return args;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function templates(packageName) {
|
|
73
|
+
const pkg = {
|
|
74
|
+
name: packageName,
|
|
75
|
+
private: true,
|
|
76
|
+
version: '0.1.0',
|
|
77
|
+
type: 'module',
|
|
78
|
+
scripts: {
|
|
79
|
+
gen: 'bddgen',
|
|
80
|
+
test: 'bddgen && playwright test',
|
|
81
|
+
'clean:gen': 'rm -rf .features-gen',
|
|
82
|
+
clean: 'rm -rf .features-gen node_modules test-results storage cucumber-report playwright-report'
|
|
83
|
+
},
|
|
84
|
+
devDependencies: {
|
|
85
|
+
'@kata/stack-tests': '^0.1.0',
|
|
86
|
+
'@playwright/test': '^1.49.0',
|
|
87
|
+
'playwright-bdd': '^8.3.0',
|
|
88
|
+
dotenv: '^16.1.4',
|
|
89
|
+
typescript: '^5.6.3'
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const tsconfig = {
|
|
94
|
+
compilerOptions: {
|
|
95
|
+
target: 'ES2021',
|
|
96
|
+
module: 'NodeNext',
|
|
97
|
+
moduleResolution: 'NodeNext',
|
|
98
|
+
strict: true,
|
|
99
|
+
types: ['node', '@playwright/test']
|
|
100
|
+
},
|
|
101
|
+
include: ['features/**/*.ts', 'playwright.config.ts']
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const fixturesTs = `import {
|
|
105
|
+
createBddTest,
|
|
106
|
+
PlaywrightApiAdapter,
|
|
107
|
+
PlaywrightUiAdapter,
|
|
108
|
+
UniversalAuthAdapter,
|
|
109
|
+
DefaultCleanupAdapter,
|
|
110
|
+
TuiTesterAdapter,
|
|
111
|
+
} from '@kata/stack-tests';
|
|
112
|
+
|
|
113
|
+
export const test = createBddTest({
|
|
114
|
+
createApi: ({ apiRequest }) => new PlaywrightApiAdapter(apiRequest),
|
|
115
|
+
createUi: ({ page }) => new PlaywrightUiAdapter(page),
|
|
116
|
+
createAuth: ({ api, ui }) => new UniversalAuthAdapter({ api, ui }),
|
|
117
|
+
createCleanup: () => new DefaultCleanupAdapter(),
|
|
118
|
+
// TUI testing (optional - requires tui-tester and tmux installed)
|
|
119
|
+
// Uncomment and configure for your CLI application:
|
|
120
|
+
// createTui: () => new TuiTesterAdapter({
|
|
121
|
+
// command: ['node', 'dist/cli.js'],
|
|
122
|
+
// size: { cols: 100, rows: 30 },
|
|
123
|
+
// debug: process.env.DEBUG === 'true',
|
|
124
|
+
// }),
|
|
125
|
+
});
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const stepsTs = `import { test } from './fixtures';
|
|
129
|
+
import {
|
|
130
|
+
registerApiSteps,
|
|
131
|
+
registerUiSteps,
|
|
132
|
+
registerSharedSteps,
|
|
133
|
+
registerHybridSuite,
|
|
134
|
+
registerTuiSteps,
|
|
135
|
+
} from '@kata/stack-tests/steps';
|
|
136
|
+
|
|
137
|
+
registerApiSteps(test);
|
|
138
|
+
registerUiSteps(test);
|
|
139
|
+
registerSharedSteps(test);
|
|
140
|
+
registerHybridSuite(test);
|
|
141
|
+
|
|
142
|
+
// TUI steps (optional - requires tui-tester and tmux installed)
|
|
143
|
+
// Uncomment when you have TUI testing configured:
|
|
144
|
+
// registerTuiSteps(test);
|
|
145
|
+
|
|
146
|
+
export { test };
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
const playwrightConfig = `import { defineConfig } from '@playwright/test';
|
|
150
|
+
import { defineBddProject, cucumberReporter } from 'playwright-bdd';
|
|
151
|
+
import dotenv from 'dotenv';
|
|
152
|
+
import fs from 'node:fs';
|
|
153
|
+
import path from 'node:path';
|
|
154
|
+
import { fileURLToPath } from 'node:url';
|
|
155
|
+
|
|
156
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
157
|
+
const localEnvPath = path.resolve(__dirname, '.env');
|
|
158
|
+
const rootEnvPath = path.resolve(process.cwd(), '.env');
|
|
159
|
+
|
|
160
|
+
if (fs.existsSync(localEnvPath)) {
|
|
161
|
+
dotenv.config({ path: localEnvPath });
|
|
162
|
+
} else if (fs.existsSync(rootEnvPath)) {
|
|
163
|
+
dotenv.config({ path: rootEnvPath });
|
|
164
|
+
} else {
|
|
165
|
+
dotenv.config();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const apiBdd = defineBddProject({
|
|
169
|
+
name: 'api',
|
|
170
|
+
features: 'features/api/**/*.feature',
|
|
171
|
+
steps: 'features/steps/**/*.ts',
|
|
172
|
+
tags: '@api',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const uiBdd = defineBddProject({
|
|
176
|
+
name: 'ui',
|
|
177
|
+
features: 'features/ui/**/*.feature',
|
|
178
|
+
steps: 'features/steps/**/*.ts',
|
|
179
|
+
tags: '@ui',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const hybridBdd = defineBddProject({
|
|
183
|
+
name: 'hybrid',
|
|
184
|
+
features: 'features/hybrid/**/*.feature',
|
|
185
|
+
steps: 'features/steps/**/*.ts',
|
|
186
|
+
tags: '@hybrid',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// TUI project (optional - uncomment when TUI testing is configured)
|
|
190
|
+
// const tuiBdd = defineBddProject({
|
|
191
|
+
// name: 'tui',
|
|
192
|
+
// features: 'features/tui/**/*.feature',
|
|
193
|
+
// steps: 'features/steps/**/*.ts',
|
|
194
|
+
// tags: '@tui',
|
|
195
|
+
// });
|
|
196
|
+
|
|
197
|
+
export default defineConfig({
|
|
198
|
+
reporter: [
|
|
199
|
+
cucumberReporter('html', { outputFile: 'cucumber-report/index.html' }),
|
|
200
|
+
cucumberReporter('json', { outputFile: 'cucumber-report/report.json' }),
|
|
201
|
+
],
|
|
202
|
+
// Add tuiBdd to this array when TUI testing is enabled
|
|
203
|
+
projects: [apiBdd, uiBdd, hybridBdd /* , tuiBdd */],
|
|
204
|
+
use: {
|
|
205
|
+
baseURL: process.env.BASE_URL || process.env.FRONTEND_URL || 'http://localhost:3000',
|
|
206
|
+
headless: process.env.HEADLESS === 'false' ? false : true,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const apiFeature = `Feature: API example
|
|
212
|
+
As an API consumer
|
|
213
|
+
I want to call the service
|
|
214
|
+
So that I can verify responses
|
|
215
|
+
|
|
216
|
+
@api
|
|
217
|
+
Scenario: GET health
|
|
218
|
+
When I GET "/health"
|
|
219
|
+
Then the response status should be 200
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
const uiFeature = `Feature: UI example
|
|
223
|
+
As a user
|
|
224
|
+
I want to load the homepage
|
|
225
|
+
So that I can see content
|
|
226
|
+
|
|
227
|
+
@ui
|
|
228
|
+
Scenario: Visit homepage
|
|
229
|
+
Given I navigate to "/"
|
|
230
|
+
Then the URL should contain "/"
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
const hybridFeature = `Feature: Hybrid example
|
|
234
|
+
As a tester
|
|
235
|
+
I want to mix API and UI steps
|
|
236
|
+
So that I can cover flows end-to-end
|
|
237
|
+
|
|
238
|
+
@hybrid
|
|
239
|
+
Scenario: API then UI
|
|
240
|
+
When I GET "/health"
|
|
241
|
+
Then the response status should be 200
|
|
242
|
+
Given I navigate to "/"
|
|
243
|
+
Then the URL should contain "/"
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
const tuiFeature = `Feature: TUI example
|
|
247
|
+
As a CLI user
|
|
248
|
+
I want to interact with the terminal application
|
|
249
|
+
So that I can verify TUI functionality
|
|
250
|
+
|
|
251
|
+
@tui
|
|
252
|
+
Scenario: Start and verify TUI application
|
|
253
|
+
Given I start the TUI application
|
|
254
|
+
Then I should see "Welcome"
|
|
255
|
+
When I type "help"
|
|
256
|
+
And I press enter
|
|
257
|
+
Then I should see "Available commands"
|
|
258
|
+
|
|
259
|
+
@tui
|
|
260
|
+
Scenario: Navigate menu with keyboard
|
|
261
|
+
Given I start the TUI application
|
|
262
|
+
When I navigate down 2 times
|
|
263
|
+
And I press enter
|
|
264
|
+
Then I should see "Selected option"
|
|
265
|
+
|
|
266
|
+
@tui
|
|
267
|
+
Scenario: Fill form in TUI
|
|
268
|
+
Given I start the TUI application
|
|
269
|
+
When I enter "John Doe" in the "Name" field
|
|
270
|
+
And I press tab
|
|
271
|
+
And I enter "john@example.com" in the "Email" field
|
|
272
|
+
And I submit the form
|
|
273
|
+
Then I should see "Form submitted successfully"
|
|
274
|
+
|
|
275
|
+
@tui
|
|
276
|
+
Scenario: Verify screen snapshot
|
|
277
|
+
Given I start the TUI application
|
|
278
|
+
Then the screen should match snapshot "main-menu"
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
const gitignore = `node_modules
|
|
282
|
+
.features-gen
|
|
283
|
+
playwright-report
|
|
284
|
+
test-results
|
|
285
|
+
cucumber-report
|
|
286
|
+
storage
|
|
287
|
+
.env
|
|
288
|
+
`;
|
|
289
|
+
|
|
290
|
+
const envExample = `# API defaults used by the auth and cleanup helpers
|
|
291
|
+
DEFAULT_ADMIN_USERNAME=admin@prima.com
|
|
292
|
+
DEFAULT_ADMIN_PASSWORD=admin1234
|
|
293
|
+
API_AUTH_LOGIN_PATH=/auth/login
|
|
294
|
+
API_BASE_URL=http://localhost:4000
|
|
295
|
+
|
|
296
|
+
# UI defaults
|
|
297
|
+
FRONTEND_URL=http://localhost:3000
|
|
298
|
+
HEADLESS=true
|
|
299
|
+
|
|
300
|
+
# TUI testing (optional)
|
|
301
|
+
# Set DEBUG=true to see TUI tester output
|
|
302
|
+
DEBUG=false
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
const readme = `# stack-tests
|
|
306
|
+
|
|
307
|
+
Generated Playwright + BDD test package powered by @kata/stack-tests.
|
|
308
|
+
|
|
309
|
+
## Install
|
|
310
|
+
- Install deps in this folder: (see commands printed by the generator)
|
|
311
|
+
|
|
312
|
+
## Run
|
|
313
|
+
- Generate tests: \
|
|
314
|
+
\`npm run gen\`
|
|
315
|
+
- Run tests: \
|
|
316
|
+
\`npm test\`
|
|
317
|
+
|
|
318
|
+
## Structure
|
|
319
|
+
- \`features/api|ui|hybrid|tui\`: feature files
|
|
320
|
+
- \`features/steps/steps.ts\`: registers steps from @kata/stack-tests
|
|
321
|
+
- \`features/steps/fixtures.ts\`: creates the Playwright-BDD test with adapters
|
|
322
|
+
- \`playwright.config.ts\`: BDD-aware Playwright config with reporters
|
|
323
|
+
|
|
324
|
+
## Notes
|
|
325
|
+
- Edit \`playwright.config.ts\` projects/tags to match your repo.
|
|
326
|
+
- Keep @playwright/test and playwright-bdd versions aligned with @kata/stack-tests peer ranges.
|
|
327
|
+
|
|
328
|
+
## TUI Testing (Optional)
|
|
329
|
+
To enable terminal user interface testing:
|
|
330
|
+
|
|
331
|
+
1. Install tmux (required by tui-tester):
|
|
332
|
+
\`\`\`bash
|
|
333
|
+
# macOS
|
|
334
|
+
brew install tmux
|
|
335
|
+
|
|
336
|
+
# Ubuntu/Debian
|
|
337
|
+
apt-get install tmux
|
|
338
|
+
\`\`\`
|
|
339
|
+
|
|
340
|
+
2. Install tui-tester:
|
|
341
|
+
\`\`\`bash
|
|
342
|
+
npm install tui-tester
|
|
343
|
+
\`\`\`
|
|
344
|
+
|
|
345
|
+
3. Uncomment TUI configuration in:
|
|
346
|
+
- \`features/steps/fixtures.ts\`: Configure TuiTesterAdapter with your CLI command
|
|
347
|
+
- \`features/steps/steps.ts\`: Uncomment registerTuiSteps(test)
|
|
348
|
+
- \`playwright.config.ts\`: Uncomment tuiBdd project and add to projects array
|
|
349
|
+
|
|
350
|
+
4. Write @tui tagged feature files in \`features/tui/\`
|
|
351
|
+
`;
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
'package.json': JSON.stringify(pkg, null, 2) + '\n',
|
|
355
|
+
'tsconfig.json': JSON.stringify(tsconfig, null, 2) + '\n',
|
|
356
|
+
'playwright.config.ts': playwrightConfig,
|
|
357
|
+
'features/steps/fixtures.ts': fixturesTs,
|
|
358
|
+
'features/steps/steps.ts': stepsTs,
|
|
359
|
+
'features/api/00_api_examples.feature': apiFeature,
|
|
360
|
+
'features/ui/00_ui_examples.feature': uiFeature,
|
|
361
|
+
'features/hybrid/00_hybrid_examples.feature': hybridFeature,
|
|
362
|
+
'features/tui/00_tui_examples.feature': tuiFeature,
|
|
363
|
+
'.gitignore': gitignore,
|
|
364
|
+
'.env.example': envExample,
|
|
365
|
+
'README.md': readme,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function main() {
|
|
370
|
+
const args = parseArgs(process.argv.slice(2));
|
|
371
|
+
const targetDir = path.resolve(process.cwd(), args.dir);
|
|
372
|
+
const pm = commandsFor(await detectPackageManager(process.cwd()));
|
|
373
|
+
const files = templates('stack-tests');
|
|
374
|
+
|
|
375
|
+
const results = { created: [], skipped: [] };
|
|
376
|
+
await ensureDir(targetDir);
|
|
377
|
+
|
|
378
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
379
|
+
const filePath = path.join(targetDir, rel);
|
|
380
|
+
await writeFileSafe(filePath, content, { force: args.force, results });
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log(`\nScaffold complete at ${targetDir}`);
|
|
384
|
+
if (results.created.length) {
|
|
385
|
+
console.log('Created files:');
|
|
386
|
+
results.created.forEach((f) => console.log(` + ${path.relative(process.cwd(), f)}`));
|
|
387
|
+
}
|
|
388
|
+
if (results.skipped.length) {
|
|
389
|
+
console.log('Skipped existing files (use --force to overwrite):');
|
|
390
|
+
results.skipped.forEach((f) => console.log(` ~ ${path.relative(process.cwd(), f)}`));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log('\nNext steps:');
|
|
394
|
+
console.log(` 1) cd ${path.relative(process.cwd(), targetDir) || '.'}`);
|
|
395
|
+
console.log(` 2) ${pm.install}`);
|
|
396
|
+
console.log(` 3) ${pm.test}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
main().catch((err) => {
|
|
400
|
+
console.error(err);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@esimplicity/create-stack-tests",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-stack-tests": "bin/create-stack-tests.js"
|
|
8
|
+
},
|
|
9
|
+
"description": "Scaffold a Playwright-BDD stack-tests project",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "echo 'No build required'"
|
|
12
|
+
},
|
|
13
|
+
"files": ["bin"],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
}
|
|
17
|
+
}
|