@esimplicitylabs/katalyst-xspec 0.6.0 → 0.7.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/README.md CHANGED
@@ -12,6 +12,10 @@ Or scaffold a new project:
12
12
 
13
13
  ```bash
14
14
  npx @esimplicitylabs/katalyst-xspec init my-tests
15
+ cd my-tests
16
+ npm install
17
+ npx playwright install chromium # one-time browser download for UI tests
18
+ npm test
15
19
  ```
16
20
 
17
21
  ## What’s included
@@ -20,7 +24,7 @@ npx @esimplicitylabs/katalyst-xspec init my-tests
20
24
  - **Ports**: `ApiPort`, `UiPort`, `AuthPort`, `CleanupPort`.
21
25
  - **Adapters**: Playwright API/UI adapters, default cleanup, example auth adapter.
22
26
  - **Step registrations**: API (auth/http/assertions), UI (basic + wizard), shared vars/cleanup, hybrid helpers.
23
- - **Config helpers**: tag expression helpers for project tagging.
27
+ - **Config helpers**: `tagsForProject` / `resolveExtraTags` for optional tag filtering (`@Skip`/`@ignore`, `TEST_TAGS`).
24
28
 
25
29
  ## Minimal usage
26
30
 
@@ -51,7 +55,7 @@ import { registerApiSteps } from '@esimplicitylabs/katalyst-xspec/steps';
51
55
  registerApiSteps(test);
52
56
  ```
53
57
 
54
- 3) Configure Playwright projects with your features/steps globs and tag expressions. Keep `@playwright/test` and `playwright-bdd` aligned with peer ranges.
58
+ 3) Configure Playwright projects with your features/steps globs (each project selects feature files by folder; tags are optional). Keep `@playwright/test` and `playwright-bdd` aligned with peer ranges.
55
59
 
56
60
  ## Publishing (npm)
57
61
 
@@ -66,4 +70,4 @@ reaches the registry.
66
70
  ## Notes
67
71
  - Peer dependencies: `@playwright/test`, `playwright-bdd`, `typescript` must be installed in the consuming repo.
68
72
  - Defaults (auth/cleanup) are examples; override via `createBddTest` options for app-specific behavior.
69
- - Tagging: supports `@api`, `@ui`, `@hybrid`, plus your own (`@smoke`, `@slow`, `@external`); combine with Playwright’s `maxFailures`/reporters as needed.
73
+ - Tagging: steps are untagged and work in any scenario; projects select features by folder. Tags are optional for your own grouping (`@smoke`, `@slow`, `@external`), filtered via `TEST_TAGS`; combine with Playwright’s `maxFailures`/reporters as needed.
package/cli/init.cjs CHANGED
@@ -274,6 +274,16 @@ function parseArgs(argv) {
274
274
  return args;
275
275
  }
276
276
 
277
+ /** npm-safe package name from a folder name ("My Demo" -> "my-demo"). */
278
+ function packageNameFor(dir) {
279
+ const name = path
280
+ .basename(path.resolve(dir))
281
+ .toLowerCase()
282
+ .replace(/[^a-z0-9._~-]+/g, '-')
283
+ .replace(/^[-._]+|[-.]+$/g, '');
284
+ return name || 'katalyst-xspec-tests';
285
+ }
286
+
277
287
  function templates(packageName) {
278
288
  const pkg = {
279
289
  name: packageName,
@@ -291,7 +301,7 @@ function templates(packageName) {
291
301
  clean: 'rm -rf .features-gen node_modules test-results storage cucumber-report playwright-report'
292
302
  },
293
303
  devDependencies: {
294
- '@esimplicitylabs/katalyst-xspec': '^0.6.0',
304
+ '@esimplicitylabs/katalyst-xspec': '^0.7.0',
295
305
  '@playwright/test': '^1.49.0',
296
306
  'playwright-bdd': '^9.1.0',
297
307
  dotenv: '^16.1.4',
@@ -360,7 +370,7 @@ export { test };
360
370
 
361
371
  const playwrightConfig = `import { defineConfig } from '@playwright/test';
362
372
  import { defineBddProject, cucumberReporter } from 'playwright-bdd';
363
- import { resolveWorkers } from '@esimplicitylabs/katalyst-xspec';
373
+ import { resolveWorkers, tagsForProject, resolveExtraTags } from '@esimplicitylabs/katalyst-xspec';
364
374
  import dotenv from 'dotenv';
365
375
  import fs from 'node:fs';
366
376
  import path from 'node:path';
@@ -378,33 +388,32 @@ if (fs.existsSync(localEnvPath)) {
378
388
  dotenv.config();
379
389
  }
380
390
 
391
+ // Each project runs the feature files in its folder. Any scenario can use any
392
+ // step (API, UI, shared). Tags are optional: use your own (e.g. @smoke) and
393
+ // filter with TEST_TAGS="@smoke"; scenarios tagged @Skip or @ignore are skipped.
394
+ const tags = tagsForProject({ extraTags: resolveExtraTags(process.env.TEST_TAGS) });
395
+
381
396
  const apiBdd = defineBddProject({
382
397
  name: 'api',
383
398
  features: 'features/api/**/*.feature',
384
399
  steps: 'features/steps/**/*.ts',
385
- tags: '@api',
400
+ tags,
386
401
  });
387
402
 
388
403
  const uiBdd = defineBddProject({
389
404
  name: 'ui',
390
405
  features: 'features/ui/**/*.feature',
391
406
  steps: 'features/steps/**/*.ts',
392
- tags: '@ui',
407
+ tags,
393
408
  });
394
409
 
395
- const hybridBdd = defineBddProject({
396
- name: 'hybrid',
397
- features: 'features/hybrid/**/*.feature',
398
- steps: 'features/steps/**/*.ts',
399
- tags: '@hybrid',
400
- });
401
-
402
- // TUI project (optional - uncomment when TUI testing is configured)
410
+ // TUI project (optional - requires tui-tester and tmux; also enable
411
+ // registerTuiSteps in features/steps/steps.ts and createTui in fixtures.ts)
403
412
  // const tuiBdd = defineBddProject({
404
413
  // name: 'tui',
405
414
  // features: 'features/tui/**/*.feature',
406
415
  // steps: 'features/steps/**/*.ts',
407
- // tags: '@tui',
416
+ // tags,
408
417
  // });
409
418
 
410
419
  export default defineConfig({
@@ -414,7 +423,7 @@ export default defineConfig({
414
423
  cucumberReporter('json', { outputFile: 'cucumber-report/report.json' }),
415
424
  ],
416
425
  // Add tuiBdd to this array when TUI testing is enabled
417
- projects: [apiBdd, uiBdd, hybridBdd /* , tuiBdd */],
426
+ projects: [apiBdd, uiBdd /* , tuiBdd */],
418
427
  use: {
419
428
  baseURL: process.env.BASE_URL || process.env.FRONTEND_URL || 'http://localhost:3000',
420
429
  headless: process.env.HEADLESS === 'false' ? false : true,
@@ -422,74 +431,44 @@ export default defineConfig({
422
431
  });
423
432
  `;
424
433
 
434
+ // Examples hit public demo sites with absolute URLs so a fresh project passes
435
+ // with no .env. Point FRONTEND_URL / API_BASE_URL at your app and switch to
436
+ // relative paths (e.g. "/login") when you write real tests.
425
437
  const apiFeature = `Feature: API example
426
- As an API consumer
427
- I want to call the service
428
- So that I can verify responses
438
+ Calls JSONPlaceholder, a free public fake REST API.
429
439
 
430
- @api
431
- Scenario: GET health
432
- When I GET "/health"
440
+ Scenario: Get a user
441
+ When I GET "https://jsonplaceholder.typicode.com/users/1"
433
442
  Then the response status should be 200
443
+ And the value at "username" should equal "Bret"
444
+
445
+ Scenario: Create a post
446
+ When I POST "https://jsonplaceholder.typicode.com/posts" with JSON body:
447
+ """
448
+ { "title": "hello from katalyst-xspec", "userId": 1 }
449
+ """
450
+ Then the response status should be 201
451
+ And the value at "title" should equal "hello from katalyst-xspec"
434
452
  `;
435
453
 
436
454
  const uiFeature = `Feature: UI example
437
- As a user
438
- I want to load the homepage
439
- So that I can see content
440
-
441
- @ui
442
- Scenario: Visit homepage
443
- Given I navigate to "/"
444
- Then the URL should contain "/"
445
- `;
446
-
447
- const hybridFeature = `Feature: Hybrid example
448
- As a tester
449
- I want to mix API and UI steps
450
- So that I can cover flows end-to-end
451
-
452
- @hybrid
453
- Scenario: API then UI
454
- When I GET "/health"
455
- Then the response status should be 200
456
- Given I navigate to "/"
457
- Then the URL should contain "/"
458
- `;
459
-
460
- const tuiFeature = `Feature: TUI example
461
- As a CLI user
462
- I want to interact with the terminal application
463
- So that I can verify TUI functionality
464
-
465
- @tui
466
- Scenario: Start and verify TUI application
467
- Given I start the TUI application
468
- Then I should see "Welcome"
469
- When I type "help"
470
- And I press enter
471
- Then I should see "Available commands"
472
-
473
- @tui
474
- Scenario: Navigate menu with keyboard
475
- Given I start the TUI application
476
- When I navigate down 2 times
477
- And I press enter
478
- Then I should see "Selected option"
479
-
480
- @tui
481
- Scenario: Fill form in TUI
482
- Given I start the TUI application
483
- When I enter "John Doe" in the "Name" field
484
- And I press tab
485
- And I enter "john@example.com" in the "Email" field
486
- And I submit the form
487
- Then I should see "Form submitted successfully"
488
-
489
- @tui
490
- Scenario: Verify screen snapshot
491
- Given I start the TUI application
492
- Then the screen should match snapshot "main-menu"
455
+ Uses Sauce Demo, a public shop built for test automation.
456
+
457
+ Background:
458
+ Given I navigate to "https://www.saucedemo.com/"
459
+
460
+ Scenario: Log in
461
+ When I fill the placeholder "Username" with "standard_user"
462
+ And I fill the placeholder "Password" with "secret_sauce"
463
+ And I click the button "Login"
464
+ Then the URL should contain "/inventory.html"
465
+ And I should see text "Products"
466
+
467
+ Scenario: Locked-out user sees an error
468
+ When I fill the placeholder "Username" with "locked_out_user"
469
+ And I fill the placeholder "Password" with "secret_sauce"
470
+ And I click the button "Login"
471
+ Then I should see text "Sorry, this user has been locked out."
493
472
  `;
494
473
 
495
474
  const gitignore = `node_modules
@@ -524,27 +503,38 @@ DEBUG=false
524
503
  # WORKERS=auto
525
504
  `;
526
505
 
527
- const readme = `# katalyst-xspec
506
+ const readme = `# ${packageName}
528
507
 
529
- Generated Playwright + BDD test package powered by @esimplicitylabs/katalyst-xspec.
508
+ Playwright + BDD tests powered by [@esimplicitylabs/katalyst-xspec](https://www.npmjs.com/package/@esimplicitylabs/katalyst-xspec).
530
509
 
531
- ## Install
532
- Install deps in this folder (see commands printed by the generator).
510
+ ## Setup
511
+ \`\`\`bash
512
+ npm install
513
+ npx playwright install chromium # one-time browser download for UI tests
514
+ \`\`\`
533
515
 
534
516
  ## Run
535
- - Generate tests: \
536
- \`npm run gen\`
537
- - Run tests: \
538
- \`npm test\`
517
+ \`\`\`bash
518
+ npm test # generate specs from features, then run them
519
+ npx playwright test --project ui # just one folder
520
+ TEST_TAGS=@smoke npm test # just scenarios you tagged @smoke
521
+ \`\`\`
522
+
523
+ The example features call public demo sites, so they pass with no configuration.
524
+ To test your own app, copy \`.env.example\` to \`.env\`, set \`FRONTEND_URL\` and
525
+ \`API_BASE_URL\`, and use relative paths such as \`Given I navigate to "/login"\`.
539
526
 
540
527
  ## Structure
541
- - \`features/api|ui|hybrid|tui\`: feature files
542
- - \`features/steps/steps.ts\`: registers steps from @esimplicitylabs/katalyst-xspec
543
- - \`features/steps/fixtures.ts\`: creates the Playwright-BDD test with adapters
544
- - \`playwright.config.ts\`: BDD-aware Playwright config with reporters
528
+ - \`features/api/\`, \`features/ui/\`: feature files; each folder is a Playwright project
529
+ - \`features/steps/steps.ts\`: registers the built-in steps (any step works in any scenario)
530
+ - \`features/steps/fixtures.ts\`: wires adapters into the Playwright-BDD test
531
+ - \`playwright.config.ts\`: projects, reporters, base URL
532
+
533
+ Tags are optional. Add your own (\`@smoke\`, \`@slow\`) and filter with \`TEST_TAGS\`;
534
+ scenarios tagged \`@Skip\` or \`@ignore\` don't run.
545
535
 
546
536
  ## Notes
547
- - Edit \`playwright.config.ts\` projects/tags to match your repo.
537
+ - Undefined steps? Run \`npm run gen:stubs\` to generate stubs.
548
538
  - Keep @playwright/test and playwright-bdd versions aligned with @esimplicitylabs/katalyst-xspec peer ranges.
549
539
 
550
540
  ## TUI Testing (Optional)
@@ -569,7 +559,7 @@ To enable terminal user interface testing:
569
559
  - \`features/steps/steps.ts\`: Uncomment registerTuiSteps(test)
570
560
  - \`playwright.config.ts\`: Uncomment tuiBdd project and add to projects array
571
561
 
572
- 4. Write @tui tagged feature files in \`features/tui/\`
562
+ 4. Write feature files in \`features/tui/\`
573
563
  `;
574
564
 
575
565
  return {
@@ -578,10 +568,8 @@ To enable terminal user interface testing:
578
568
  'playwright.config.ts': playwrightConfig,
579
569
  'features/steps/fixtures.ts': fixturesTs,
580
570
  'features/steps/steps.ts': stepsTs,
581
- 'features/api/00_api_examples.feature': apiFeature,
582
- 'features/ui/00_ui_examples.feature': uiFeature,
583
- 'features/hybrid/00_hybrid_examples.feature': hybridFeature,
584
- 'features/tui/00_tui_examples.feature': tuiFeature,
571
+ 'features/api/example.feature': apiFeature,
572
+ 'features/ui/example.feature': uiFeature,
585
573
  '.gitignore': gitignore,
586
574
  '.env.example': envExample,
587
575
  'README.md': readme,
@@ -593,7 +581,7 @@ async function main() {
593
581
  const targetDir = path.resolve(process.cwd(), args.dir);
594
582
  const detectedPm = await detectPackageManager(process.cwd());
595
583
  const pm = commandsFor(detectedPm);
596
- const files = templates('katalyst-xspec');
584
+ const files = templates(packageNameFor(targetDir));
597
585
 
598
586
  console.log(`Detected package manager: ${detectedPm}`);
599
587
 
@@ -659,7 +647,8 @@ async function main() {
659
647
  console.log('\nNext steps:');
660
648
  console.log(` 1) cd ${path.relative(process.cwd(), targetDir) || '.'}`);
661
649
  console.log(` 2) ${pm.install}`);
662
- console.log(` 3) ${pm.test}`);
650
+ console.log(' 3) npx playwright install chromium (one-time browser download for UI tests)');
651
+ console.log(` 4) ${pm.test}`);
663
652
 
664
653
  if (results.skills.length) {
665
654
  console.log('\nSkills installed! Your AI agent can now help you:');
package/cli/upgrade.cjs CHANGED
@@ -117,6 +117,56 @@ function removeGithubPackagesNpmrc(cwd, { dryRun = false } = {}) {
117
117
  return true;
118
118
  }
119
119
 
120
+ /**
121
+ * Before 0.7.0, steps and Playwright projects were scoped by type tags
122
+ * (@api/@ui/@hybrid/@tui). Steps no longer use them, and a project filter like
123
+ * `tags: '@ui'` silently drops any scenario without the tag. Remove those
124
+ * filters from playwright.config.* (other tag expressions are left alone).
125
+ */
126
+ const TYPE_TAG = String.raw`@(?:api|ui|hybrid|tui)`;
127
+ const TYPE_TAG_FILTER_RES = [
128
+ // Whole-line `tags: '@ui',` inside a multi-line object.
129
+ new RegExp(String.raw`^[ \t]*tags:\s*(['"])${TYPE_TAG}\1,?[ \t]*\r?\n`, 'gm'),
130
+ // Inline `, tags: '@ui'` in a one-line object.
131
+ new RegExp(String.raw`,\s*tags:\s*(['"])${TYPE_TAG}\1(?=\s*[,}])`, 'g'),
132
+ // Leading `tags: '@ui'` right after `{` in a one-line object.
133
+ new RegExp(String.raw`(?<=\{)\s*tags:\s*(['"])${TYPE_TAG}\1\s*,?`, 'g'),
134
+ // `projectTag: '@ui'` inside tagsForProject({ ... }), with its separator.
135
+ new RegExp(String.raw`projectTag:\s*(['"])${TYPE_TAG}\1\s*,?\s*`, 'g'),
136
+ ];
137
+
138
+ function playwrightConfigFiles(cwd) {
139
+ if (!fs.existsSync(cwd)) return [];
140
+ return fs.readdirSync(cwd).filter((f) => /^playwright\.config\.(ts|js|mts|cts|mjs|cjs)$/.test(f));
141
+ }
142
+
143
+ function stripTypeTagFiltersFromText(text) {
144
+ const stripped = TYPE_TAG_FILTER_RES.reduce((acc, re) => acc.replace(re, ''), text);
145
+ // Tidy objects left empty, e.g. `tagsForProject({ })` -> `tagsForProject({})`.
146
+ return stripped === text ? text : stripped.replace(/\(\{\s+\}\)/g, '({})');
147
+ }
148
+
149
+ function hasTypeTagFilters(cwd) {
150
+ return playwrightConfigFiles(cwd).some((f) => {
151
+ const text = fs.readFileSync(path.join(cwd, f), 'utf8');
152
+ return stripTypeTagFiltersFromText(text) !== text;
153
+ });
154
+ }
155
+
156
+ function stripTypeTagFilters(cwd, { dryRun = false } = {}) {
157
+ const changed = [];
158
+ for (const f of playwrightConfigFiles(cwd)) {
159
+ const full = path.join(cwd, f);
160
+ const text = fs.readFileSync(full, 'utf8');
161
+ const updated = stripTypeTagFiltersFromText(text);
162
+ if (updated !== text) {
163
+ if (!dryRun) fs.writeFileSync(full, updated);
164
+ changed.push(f);
165
+ }
166
+ }
167
+ return changed;
168
+ }
169
+
120
170
  /**
121
171
  * Before 0.5.0 the CLIs shipped in a separate package with their own binaries
122
172
  * (create-/upgrade-stack-tests, upgrade-katalyst-xspec, generate-step-stubs).
@@ -503,7 +553,7 @@ function getTemplates() {
503
553
  clean: 'rm -rf .features-gen node_modules test-results storage cucumber-report playwright-report'
504
554
  },
505
555
  devDependencies: {
506
- '@esimplicitylabs/katalyst-xspec': '^0.6.0',
556
+ '@esimplicitylabs/katalyst-xspec': '^0.7.0',
507
557
  '@playwright/test': '^1.49.0',
508
558
  'playwright-bdd': '^9.1.0',
509
559
  dotenv: '^16.1.4',
@@ -658,6 +708,7 @@ async function migrate(cwd, options) {
658
708
  }
659
709
 
660
710
  // Backup original steps.ts and fixtures.ts for reference
711
+ fs.mkdirSync(path.join(backupDir, 'steps'), { recursive: true });
661
712
  if (existingStepsTs) {
662
713
  fs.writeFileSync(path.join(backupDir, 'steps', 'steps.ts.original'), existingStepsTs);
663
714
  }
@@ -733,6 +784,11 @@ async function migrate(cwd, options) {
733
784
  if (rewriteLegacyScripts(cwd, { dryRun: options.dryRun })) {
734
785
  console.log(' package.json: scripts now use the katalyst-xspec command');
735
786
  }
787
+ const untagged = stripTypeTagFilters(cwd, { dryRun: options.dryRun });
788
+ if (untagged.length) {
789
+ console.log(` ${untagged.join(', ')}: removed @api/@ui/@hybrid/@tui project tag filters`);
790
+ if (!options.dryRun) results.updated.push(...untagged);
791
+ }
736
792
  if (options.dryRun) {
737
793
  console.log(' (dry run - no files actually written)');
738
794
  }
@@ -945,6 +1001,10 @@ async function main() {
945
1001
  if (!installed.legacy && !options.check && rewriteLegacyScripts(cwd)) {
946
1002
  log('Updated package.json scripts to use the katalyst-xspec command');
947
1003
  }
1004
+ if (hasTypeTagFilters(cwd)) {
1005
+ log('Note: playwright.config filters projects by @api/@ui/@hybrid/@tui tags, which are no longer');
1006
+ log('needed and silently skip untagged scenarios. Run `katalyst-xspec upgrade --migrate` to remove them.');
1007
+ }
948
1008
 
949
1009
  if (installed.legacy) {
950
1010
  if (options.check) {
@@ -1009,6 +1069,8 @@ module.exports = {
1009
1069
  getInstalledVersion,
1010
1070
  rewriteLegacyImports,
1011
1071
  rewriteLegacyScripts,
1072
+ stripTypeTagFilters,
1073
+ hasTypeTagFilters,
1012
1074
  removeGithubPackagesNpmrc,
1013
1075
  mergePackageJson,
1014
1076
  };