@esimplicitylabs/katalyst-xspec 0.6.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.
Files changed (29) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +69 -0
  3. package/bin/katalyst-xspec.cjs +54 -0
  4. package/cli/init.cjs +679 -0
  5. package/cli/stubs.cjs +365 -0
  6. package/cli/upgrade.cjs +1014 -0
  7. package/dist/chunk-ACAXOGKZ.js +1611 -0
  8. package/dist/index.d.ts +881 -0
  9. package/dist/index.js +1091 -0
  10. package/dist/steps/index.d.ts +151 -0
  11. package/dist/steps/index.js +50 -0
  12. package/package.json +80 -0
  13. package/scripts/postinstall.cjs +85 -0
  14. package/skills/katalyst-bdd-architecture/SKILL.md +517 -0
  15. package/skills/katalyst-bdd-architecture/references/adapters.md +310 -0
  16. package/skills/katalyst-bdd-architecture/references/custom-steps.md +360 -0
  17. package/skills/katalyst-bdd-architecture/references/ports.md +256 -0
  18. package/skills/katalyst-bdd-create-test/SKILL.md +366 -0
  19. package/skills/katalyst-bdd-create-test/references/api-patterns.md +371 -0
  20. package/skills/katalyst-bdd-create-test/references/hybrid-patterns.md +420 -0
  21. package/skills/katalyst-bdd-create-test/references/tui-patterns.md +458 -0
  22. package/skills/katalyst-bdd-create-test/references/ui-patterns.md +415 -0
  23. package/skills/katalyst-bdd-quickstart/SKILL.md +292 -0
  24. package/skills/katalyst-bdd-step-reference/SKILL.md +147 -0
  25. package/skills/katalyst-bdd-step-reference/references/api-steps.md +247 -0
  26. package/skills/katalyst-bdd-step-reference/references/shared-steps.md +340 -0
  27. package/skills/katalyst-bdd-step-reference/references/tui-steps.md +483 -0
  28. package/skills/katalyst-bdd-step-reference/references/ui-steps.md +521 -0
  29. package/skills/katalyst-bdd-troubleshooting/SKILL.md +449 -0
@@ -0,0 +1,256 @@
1
+ # Ports Reference
2
+
3
+ Complete reference for all port interfaces in the Katalyst BDD framework.
4
+
5
+ ## ApiPort
6
+
7
+ Handles HTTP API interactions.
8
+
9
+ ```typescript
10
+ interface ApiPort {
11
+ sendJson(
12
+ method: ApiMethod,
13
+ path: string,
14
+ body?: unknown,
15
+ headers?: Record<string, string>
16
+ ): Promise<ApiResult>;
17
+
18
+ sendForm(
19
+ method: 'POST' | 'PUT' | 'PATCH',
20
+ path: string,
21
+ form: Record<string, string>,
22
+ headers?: Record<string, string>
23
+ ): Promise<ApiResult>;
24
+ }
25
+
26
+ type ApiMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
27
+
28
+ type ApiResult = {
29
+ status: number;
30
+ text: string;
31
+ json?: unknown;
32
+ headers: Record<string, string>;
33
+ contentType?: string;
34
+ response: APIResponse; // Playwright's raw response
35
+ };
36
+ ```
37
+
38
+ ### Usage in Steps
39
+
40
+ ```typescript
41
+ When('I GET {string}', async ({ api, world }, path) => {
42
+ const result = await api.sendJson('GET', path, undefined, world.headers);
43
+ world.lastStatus = result.status;
44
+ world.lastJson = result.json;
45
+ });
46
+ ```
47
+
48
+ ## UiPort
49
+
50
+ Handles browser UI interactions.
51
+
52
+ ```typescript
53
+ interface UiPort {
54
+ // Navigation
55
+ goto(path: string): Promise<void>;
56
+ goBack(): Promise<void>;
57
+ reload(): Promise<void>;
58
+ getCurrentUrl(): Promise<string>;
59
+
60
+ // Clicks
61
+ clickButton(name: string): Promise<void>;
62
+ clickLink(name: string): Promise<void>;
63
+ clickElementThatContains(
64
+ clickMode: UiClickMode,
65
+ elementType: string,
66
+ text: string
67
+ ): Promise<void>;
68
+ clickElementWith(
69
+ clickMode: UiClickMode,
70
+ ordinal: string,
71
+ text: string,
72
+ method: UiLocatorMethod
73
+ ): Promise<void>;
74
+
75
+ // Inputs
76
+ fillPlaceholder(placeholder: string, value: string): Promise<void>;
77
+ fillLabel(label: string, value: string): Promise<void>;
78
+ fillDropdown(value: string, dropdownLabel: string): Promise<void>;
79
+ inputInElement(
80
+ action: UiInputMode,
81
+ value: string,
82
+ ordinal: string,
83
+ text: string,
84
+ method: UiLocatorMethod
85
+ ): Promise<void>;
86
+
87
+ // Keyboard
88
+ typeText(text: string): Promise<void>;
89
+ pressKey(key: string): Promise<void>;
90
+
91
+ // Waits
92
+ waitSeconds(seconds: number): Promise<void>;
93
+ waitForPageLoad(): Promise<void>;
94
+
95
+ // Assertions
96
+ expectText(text: string): Promise<void>;
97
+ expectUrlContains(part: string): Promise<void>;
98
+ expectUrl(mode: UiUrlAssertMode, expected: string): Promise<void>;
99
+ expectNewTabUrl(mode: UiUrlAssertMode, expected: string): Promise<void>;
100
+ expectElementWithTextVisible(
101
+ elementType: string,
102
+ text: string,
103
+ shouldBeVisible: boolean
104
+ ): Promise<void>;
105
+ expectElementState(
106
+ ordinal: string,
107
+ text: string,
108
+ method: UiLocatorMethod,
109
+ state: UiElementState
110
+ ): Promise<void>;
111
+ expectElementStateWithin(
112
+ ordinal: string,
113
+ text: string,
114
+ method: UiLocatorMethod,
115
+ state: UiElementState,
116
+ seconds: number
117
+ ): Promise<void>;
118
+
119
+ // Utilities
120
+ zoomTo(scale: number): Promise<void>;
121
+ }
122
+
123
+ type UiClickMode = 'click' | 'dispatch click' | 'force click' | 'force dispatch click';
124
+ type UiInputMode = 'type' | 'fill' | 'choose';
125
+ type UiUrlAssertMode = 'contains' | 'doesntContain' | 'equals';
126
+ type UiLocatorMethod = 'text' | 'label' | 'placeholder' | 'role' | 'test ID' | 'alternative text' | 'title' | 'locator';
127
+ type UiElementState = 'visible' | 'hidden' | 'editable' | 'disabled' | 'enabled' | 'read-only';
128
+ ```
129
+
130
+ ## TuiPort
131
+
132
+ Handles terminal UI interactions.
133
+
134
+ ```typescript
135
+ interface TuiPort {
136
+ // Lifecycle
137
+ start(): Promise<void>;
138
+ stop(): Promise<void>;
139
+ restart(): Promise<void>;
140
+ isRunning(): boolean;
141
+
142
+ // Input
143
+ typeText(text: string, options?: { delay?: number }): Promise<void>;
144
+ pressKey(key: string, modifiers?: TuiKeyModifiers): Promise<void>;
145
+ sendText(text: string): Promise<void>;
146
+ fillField(fieldLabel: string, value: string): Promise<void>;
147
+ selectOption(option: string): Promise<void>;
148
+
149
+ // Mouse
150
+ sendMouse(event: TuiMouseEvent): Promise<void>;
151
+ click(x: number, y: number, button?: TuiMouseButton): Promise<void>;
152
+ clickOnText(text: string): Promise<void>;
153
+
154
+ // Assertions
155
+ expectText(text: string, options?: TuiWaitOptions): Promise<void>;
156
+ expectPattern(pattern: RegExp, options?: TuiWaitOptions): Promise<void>;
157
+ expectNotText(text: string): Promise<void>;
158
+ assertScreenContains(text: string): Promise<void>;
159
+ assertScreenMatches(pattern: RegExp): Promise<void>;
160
+
161
+ // Waits
162
+ waitForText(text: string, options?: TuiWaitOptions): Promise<void>;
163
+ waitForPattern(pattern: RegExp, options?: TuiWaitOptions): Promise<void>;
164
+ waitForReady(): Promise<void>;
165
+ waitSeconds(seconds: number): Promise<void>;
166
+
167
+ // Screen capture
168
+ captureScreen(): Promise<TuiScreenCapture>;
169
+ getScreenText(): Promise<string>;
170
+ getScreenLines(): Promise<string[]>;
171
+
172
+ // Snapshots
173
+ takeSnapshot(name: string): Promise<void>;
174
+ matchSnapshot(name: string): Promise<TuiSnapshotResult>;
175
+
176
+ // Utilities
177
+ clear(): Promise<void>;
178
+ resize(size: { cols: number; rows: number }): Promise<void>;
179
+ getSize(): { cols: number; rows: number };
180
+ getConfig(): TuiConfig;
181
+ }
182
+
183
+ type TuiConfig = {
184
+ command: string[];
185
+ size?: { cols: number; rows: number };
186
+ cwd?: string;
187
+ env?: Record<string, string>;
188
+ debug?: boolean;
189
+ snapshotDir?: string;
190
+ shell?: string;
191
+ };
192
+
193
+ type TuiKeyModifiers = {
194
+ ctrl?: boolean;
195
+ alt?: boolean;
196
+ shift?: boolean;
197
+ };
198
+
199
+ type TuiWaitOptions = {
200
+ timeout?: number;
201
+ interval?: number;
202
+ };
203
+
204
+ type TuiMouseButton = 'left' | 'middle' | 'right';
205
+ ```
206
+
207
+ ## AuthPort
208
+
209
+ Handles authentication across layers.
210
+
211
+ ```typescript
212
+ interface AuthPort {
213
+ apiLoginAsAdmin(world: World): Promise<void>;
214
+ apiLoginAsUser(world: World): Promise<void>;
215
+ apiSetBearer(world: World, token: string): void;
216
+
217
+ uiLoginAsAdmin(world: World): Promise<void>;
218
+ uiLoginAsUser(world: World): Promise<void>;
219
+ }
220
+ ```
221
+
222
+ ### Implementation Notes
223
+
224
+ The default `UniversalAuthAdapter`:
225
+ - API login: POSTs to `API_AUTH_LOGIN_PATH` with username/password from env
226
+ - UI login: Fills form at `/login` with username/password
227
+
228
+ ## CleanupPort
229
+
230
+ Handles resource cleanup after tests.
231
+
232
+ ```typescript
233
+ interface CleanupPort {
234
+ registerFromVar(
235
+ world: World,
236
+ varName: string,
237
+ id: unknown,
238
+ meta?: unknown
239
+ ): void;
240
+ }
241
+ ```
242
+
243
+ ### Implementation Notes
244
+
245
+ The default `DefaultCleanupAdapter`:
246
+ - Matches variable names to cleanup patterns
247
+ - Uses `CLEANUP_RULES` env variable for custom rules
248
+ - Executes DELETE requests at test teardown
249
+
250
+ ```bash
251
+ # Example CLEANUP_RULES
252
+ CLEANUP_RULES='[
253
+ {"varMatch": "userId", "path": "/admin/users/{id}"},
254
+ {"varMatch": "projectId", "path": "/projects/{id}"}
255
+ ]'
256
+ ```
@@ -0,0 +1,366 @@
1
+ ---
2
+ name: katalyst-bdd-create-test
3
+ description: Create BDD tests for the Katalyst framework. Use when writing new feature files, creating test scenarios, choosing test types (@api, @ui, @tui, @hybrid), or implementing common testing patterns like CRUD operations, login flows, form handling, or API+UI verification workflows.
4
+ ---
5
+
6
+ # Katalyst BDD Test Creation Guide
7
+
8
+ This skill guides you through creating BDD tests with the Katalyst framework.
9
+
10
+ ## Test Type Decision Tree
11
+
12
+ Choose the right tag based on what you're testing:
13
+
14
+ ```
15
+ What are you testing?
16
+ │
17
+ ├─ HTTP API only → @api
18
+ │ (REST endpoints, JSON responses, status codes)
19
+ │
20
+ ├─ Browser UI only → @ui
21
+ │ (Pages, forms, buttons, navigation)
22
+ │
23
+ ├─ Terminal UI only → @tui
24
+ │ (CLI apps, interactive terminal programs)
25
+ │
26
+ └─ Multiple layers → @hybrid
27
+ (Create via API, verify in UI)
28
+ (Setup data, then test UI flows)
29
+ ```
30
+
31
+ ## Feature File Structure
32
+
33
+ Every feature file follows this structure:
34
+
35
+ ```gherkin
36
+ @tag
37
+ Feature: Feature Name
38
+ As a [role]
39
+ I want [capability]
40
+ So that [benefit]
41
+
42
+ Background:
43
+ # Shared setup for all scenarios
44
+
45
+ Scenario: Scenario Name
46
+ Given [precondition]
47
+ When [action]
48
+ Then [expected outcome]
49
+ ```
50
+
51
+ ## Creating API Tests (`@api`)
52
+
53
+ ### Step 1: Create Feature File
54
+
55
+ Location: `features/api/[resource].feature`
56
+
57
+ ```gherkin
58
+ @api
59
+ Feature: [Resource] API
60
+ As a developer
61
+ I want to test the [Resource] API
62
+ So that I can verify CRUD operations work correctly
63
+ ```
64
+
65
+ ### Step 2: Add Background (if authenticated)
66
+
67
+ ```gherkin
68
+ Background:
69
+ Given I am authenticated as an admin via API
70
+ ```
71
+
72
+ ### Step 3: Write Scenarios
73
+
74
+ **Pattern: Simple GET**
75
+ ```gherkin
76
+ Scenario: Fetch [resource]
77
+ When I GET "/[endpoint]"
78
+ Then the response status should be 200
79
+ And the response should be a JSON [array|object]
80
+ ```
81
+
82
+ **Pattern: Create Resource**
83
+ ```gherkin
84
+ Scenario: Create [resource]
85
+ Given I generate a UUID and store as "runId"
86
+ When I POST "/[endpoint]" with JSON body:
87
+ """
88
+ {
89
+ "field": "value-{runId}"
90
+ }
91
+ """
92
+ Then the response status should be 201
93
+ And I store the value at "id" as "[resource]Id"
94
+ And the value at "field" should equal "value-{runId}"
95
+ ```
96
+
97
+ **Pattern: Full CRUD**
98
+ ```gherkin
99
+ Scenario: Full [resource] lifecycle
100
+ # Create
101
+ Given I generate a UUID and store as "runId"
102
+ When I POST "/[endpoint]" with JSON body:
103
+ """
104
+ { "name": "Test {runId}" }
105
+ """
106
+ Then the response status should be 201
107
+ And I store the value at "id" as "resourceId"
108
+ Given I register cleanup DELETE "/[endpoint]/{resourceId}"
109
+
110
+ # Read
111
+ When I GET "/[endpoint]/{resourceId}"
112
+ Then the response status should be 200
113
+ And the value at "name" should equal "Test {runId}"
114
+
115
+ # Update
116
+ When I PATCH "/[endpoint]/{resourceId}" with JSON body:
117
+ """
118
+ { "name": "Updated {runId}" }
119
+ """
120
+ Then the response status should be 200
121
+
122
+ # Delete
123
+ When I DELETE "/[endpoint]/{resourceId}"
124
+ Then the response status should be 204
125
+ ```
126
+
127
+ See [API Patterns](references/api-patterns.md) for more examples.
128
+
129
+ ## Creating UI Tests (`@ui`)
130
+
131
+ ### Step 1: Create Feature File
132
+
133
+ Location: `features/ui/[page].feature`
134
+
135
+ ```gherkin
136
+ @ui
137
+ Feature: [Page Name]
138
+ As a user
139
+ I want to [action on page]
140
+ So that I can [benefit]
141
+ ```
142
+
143
+ ### Step 2: Write Navigation Scenario
144
+
145
+ ```gherkin
146
+ Scenario: Navigate to [page]
147
+ Given I navigate to "/[path]"
148
+ Then I should see text "[expected text]"
149
+ And the URL should contain "/[path]"
150
+ ```
151
+
152
+ ### Step 3: Write Interaction Scenarios
153
+
154
+ **Pattern: Form Submit**
155
+ ```gherkin
156
+ Scenario: Submit [form name]
157
+ Given I navigate to "/[path]"
158
+ When I fill the form:
159
+ | Field | Value |
160
+ | Field1 | value1 |
161
+ | Field2 | value2 |
162
+ And I click the button "Submit"
163
+ Then I should see text "Success"
164
+ ```
165
+
166
+ **Pattern: Login Flow**
167
+ ```gherkin
168
+ Scenario: User login
169
+ Given I navigate to "/login"
170
+ When I fill in "Email" with "user@example.com"
171
+ And I fill in "Password" with "password123"
172
+ And I click the button "Sign In"
173
+ Then I should see text "Welcome"
174
+ And the URL should contain "/dashboard"
175
+ ```
176
+
177
+ See [UI Patterns](references/ui-patterns.md) for more examples.
178
+
179
+ ## Creating TUI Tests (`@tui`)
180
+
181
+ ### Step 1: Create Feature File
182
+
183
+ Location: `features/tui/[command].feature`
184
+
185
+ ```gherkin
186
+ @tui
187
+ Feature: [CLI Command]
188
+ As a user
189
+ I want to use the [command] CLI
190
+ So that I can [benefit]
191
+ ```
192
+
193
+ ### Step 2: Configure TUI in Fixtures
194
+
195
+ Ensure your `fixtures.ts` has TUI configured:
196
+
197
+ ```typescript
198
+ createTui: () => new TuiTesterAdapter({
199
+ command: ['node', 'dist/cli.js'],
200
+ size: { cols: 100, rows: 30 },
201
+ }),
202
+ ```
203
+
204
+ ### Step 3: Write Scenarios
205
+
206
+ **Pattern: Basic Command**
207
+ ```gherkin
208
+ Scenario: Run [command]
209
+ Given I start the TUI application
210
+ When I type "[command]"
211
+ And I press enter
212
+ Then I should see "[expected output]"
213
+ ```
214
+
215
+ **Pattern: Interactive Menu**
216
+ ```gherkin
217
+ Scenario: Navigate menu
218
+ Given I start the TUI application
219
+ When I wait for "Main Menu"
220
+ And I navigate down 2 times
221
+ And I press enter
222
+ Then I should see "[selected option screen]"
223
+ ```
224
+
225
+ See [TUI Patterns](references/tui-patterns.md) for more examples.
226
+
227
+ ## Creating Hybrid Tests (`@hybrid`)
228
+
229
+ ### Step 1: Create Feature File
230
+
231
+ Location: `features/hybrid/[workflow].feature`
232
+
233
+ ```gherkin
234
+ @hybrid
235
+ Feature: [Workflow Name]
236
+ As a tester
237
+ I want to combine API and UI testing
238
+ So that I can verify end-to-end workflows
239
+ ```
240
+
241
+ ### Step 2: Structure Your Scenario
242
+
243
+ ```gherkin
244
+ Scenario: [Workflow description]
245
+ # --- API SETUP PHASE ---
246
+ Given I am authenticated as an admin via API
247
+ [API steps to create test data]
248
+
249
+ # --- UI VERIFICATION PHASE ---
250
+ Given I navigate to "[page]"
251
+ [UI steps to verify the data]
252
+ ```
253
+
254
+ **Pattern: Create via API, Verify in UI**
255
+ ```gherkin
256
+ Scenario: Create user via API, verify in admin panel
257
+ # API: Create user
258
+ Given I am authenticated as an admin via API
259
+ Given I generate a UUID and store as "testId"
260
+ When I POST "/admin/users" with JSON body:
261
+ """
262
+ {
263
+ "email": "test-{testId}@example.com",
264
+ "name": "Test User {testId}"
265
+ }
266
+ """
267
+ Then the response status should be 201
268
+ And I store the value at "id" as "userId"
269
+ Given I register cleanup DELETE "/admin/users/{userId}"
270
+
271
+ # UI: Verify user appears
272
+ Given I navigate to "/admin/users"
273
+ Then I should see text "test-{testId}@example.com"
274
+ Then I should see text "Test User {testId}"
275
+ ```
276
+
277
+ See [Hybrid Patterns](references/hybrid-patterns.md) for more examples.
278
+
279
+ ## Best Practices
280
+
281
+ ### 1. Always Use Unique Test Data
282
+
283
+ ```gherkin
284
+ # Good: Unique data per run
285
+ Given I generate a UUID and store as "runId"
286
+ Given I set variable "email" to "test-{runId}@example.com"
287
+
288
+ # Bad: Hardcoded data that may conflict
289
+ Given I set variable "email" to "test@example.com"
290
+ ```
291
+
292
+ ### 2. Register Cleanup for Created Resources
293
+
294
+ ```gherkin
295
+ When I POST "/users" with JSON body:
296
+ """
297
+ { "email": "{email}" }
298
+ """
299
+ Then the response status should be 201
300
+ And I store the value at "id" as "userId"
301
+ Given I register cleanup DELETE "/users/{userId}" # Important!
302
+ ```
303
+
304
+ ### 3. Use Background for Common Setup
305
+
306
+ ```gherkin
307
+ Background:
308
+ Given I am authenticated as an admin via API
309
+ Given I generate a UUID and store as "runId"
310
+
311
+ Scenario: Test 1
312
+ # No need to repeat auth and UUID generation
313
+
314
+ Scenario: Test 2
315
+ # Background runs before each scenario
316
+ ```
317
+
318
+ ### 4. Keep Scenarios Independent
319
+
320
+ Each scenario should be able to run in isolation. Don't rely on state from previous scenarios.
321
+
322
+ ### 5. Use Descriptive Variable Names
323
+
324
+ ```gherkin
325
+ # Good
326
+ And I store the value at "id" as "createdUserId"
327
+ And I store the value at "token" as "authToken"
328
+
329
+ # Bad
330
+ And I store the value at "id" as "x"
331
+ And I store the value at "token" as "t"
332
+ ```
333
+
334
+ ## Running Your Tests
335
+
336
+ After creating feature files:
337
+
338
+ ```bash
339
+ # 1. Generate Playwright tests (REQUIRED)
340
+ npm run gen
341
+
342
+ # 2. Run all tests
343
+ npm test
344
+
345
+ # 3. Run specific project
346
+ npx playwright test --project=api
347
+ npx playwright test --project=ui
348
+ npx playwright test --project=hybrid
349
+
350
+ # 4. Run specific feature
351
+ npx playwright test features/api/users.feature
352
+
353
+ # 5. Debug mode
354
+ npx playwright test --debug
355
+ ```
356
+
357
+ ## Common Mistakes to Avoid
358
+
359
+ | Mistake | Solution |
360
+ |---------|----------|
361
+ | Forgot to run `npm run gen` | Always run after creating/modifying features |
362
+ | Steps not available | Check you have the correct tag (`@api`, `@ui`, etc.) |
363
+ | Hardcoded test data | Use UUID generation for unique data |
364
+ | No cleanup registered | Always register cleanup for created resources |
365
+ | Scenarios depend on each other | Make each scenario independent |
366
+ | Missing Background auth | Add auth to Background if all scenarios need it |