@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.
@@ -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
+ }