@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,881 @@
1
+ import * as playwright_bdd from 'playwright-bdd';
2
+ export { test as baseTest } from 'playwright-bdd';
3
+ import * as _playwright_test from '@playwright/test';
4
+ import { APIResponse, PlaywrightTestArgs, PlaywrightWorkerArgs, APIRequestContext, Page } from '@playwright/test';
5
+ export { isFlagEnabled, registerAllSteps, registerApiAssertionSteps, registerApiAuthSteps, registerApiHttpSteps, registerApiSteps, registerDebugSteps, registerFlagSteps, registerFormSteps, registerHybridSteps, registerHybridSuite, registerLayoutSteps, registerSharedCleanupSteps, registerSharedSteps, registerSharedVarSteps, registerTuiBasicSteps, registerTuiSteps, registerTuiWizardSteps, registerUiAuthSteps, registerUiBasicSteps, registerUiSteps, registerWizardSteps, setFlag } from './steps/index.js';
6
+
7
+ type CleanupItem = {
8
+ method: 'DELETE' | 'POST' | 'PATCH' | 'PUT';
9
+ path: string;
10
+ headers?: Record<string, string>;
11
+ body?: unknown;
12
+ };
13
+ type World = {
14
+ vars: Record<string, string>;
15
+ headers: Record<string, string>;
16
+ cleanup: CleanupItem[];
17
+ skipCleanup?: boolean;
18
+ lastResponse?: APIResponse;
19
+ lastStatus?: number;
20
+ lastText?: string;
21
+ lastJson?: unknown;
22
+ lastHeaders?: Record<string, string>;
23
+ lastContentType?: string;
24
+ };
25
+ declare function initWorld(): World;
26
+
27
+ type ApiResult = {
28
+ status: number;
29
+ text: string;
30
+ json?: unknown;
31
+ headers: Record<string, string>;
32
+ contentType?: string;
33
+ response: APIResponse;
34
+ };
35
+ type ApiMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
36
+ interface ApiPort {
37
+ sendJson(method: ApiMethod, path: string, body?: unknown, headers?: Record<string, string>): Promise<ApiResult>;
38
+ sendForm(method: 'POST' | 'PUT' | 'PATCH', path: string, form: Record<string, string>, headers?: Record<string, string>): Promise<ApiResult>;
39
+ }
40
+
41
+ type UiClickMode = 'click' | 'dispatch click' | 'force click' | 'force dispatch click';
42
+ type UiInputMode = 'type' | 'fill' | 'choose';
43
+ type UiUrlAssertMode = 'contains' | 'doesntContain' | 'equals';
44
+ type UiLocatorMethod = 'text' | 'label' | 'placeholder' | 'role' | 'test ID' | 'alternative text' | 'title' | 'locator';
45
+ type UiElementState = 'visible' | 'hidden' | 'editable' | 'disabled' | 'enabled' | 'read-only';
46
+ interface UiPort {
47
+ goto(path: string): Promise<void>;
48
+ clickButton(name: string): Promise<void>;
49
+ clickLink(name: string): Promise<void>;
50
+ fillPlaceholder(placeholder: string, value: string): Promise<void>;
51
+ fillLabel(label: string, value: string): Promise<void>;
52
+ expectText(text: string): Promise<void>;
53
+ expectUrlContains(part: string): Promise<void>;
54
+ goBack(): Promise<void>;
55
+ reload(): Promise<void>;
56
+ waitSeconds(seconds: number): Promise<void>;
57
+ waitForPageLoad(): Promise<void>;
58
+ getCurrentUrl(): Promise<string>;
59
+ zoomTo(scale: number): Promise<void>;
60
+ typeText(text: string): Promise<void>;
61
+ pressKey(key: string): Promise<void>;
62
+ clickElementThatContains(clickMode: UiClickMode, elementType: string, text: string): Promise<void>;
63
+ clickElementWith(clickMode: UiClickMode, ordinal: string, text: string, method: UiLocatorMethod): Promise<void>;
64
+ fillDropdown(value: string, dropdownLabel: string): Promise<void>;
65
+ inputInElement(action: UiInputMode, value: string, ordinal: string, text: string, method: UiLocatorMethod): Promise<void>;
66
+ expectUrl(mode: UiUrlAssertMode, expected: string): Promise<void>;
67
+ expectNewTabUrl(mode: UiUrlAssertMode, expected: string): Promise<void>;
68
+ expectElementWithTextVisible(elementType: string, text: string, shouldBeVisible: boolean): Promise<void>;
69
+ expectElementState(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState): Promise<void>;
70
+ expectElementStateWithin(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState, seconds: number): Promise<void>;
71
+ }
72
+
73
+ interface AuthPort {
74
+ apiLoginAsAdmin(world: World): Promise<void>;
75
+ apiLoginAsUser(world: World): Promise<void>;
76
+ apiSetBearer(world: World, token: string): void;
77
+ uiLoginAsAdmin(world: World): Promise<void>;
78
+ uiLoginAsUser(world: World): Promise<void>;
79
+ }
80
+
81
+ interface CleanupPort {
82
+ registerFromVar(world: World, varName: string, id: unknown, meta?: unknown): void;
83
+ }
84
+
85
+ /**
86
+ * TUI Port Interface
87
+ *
88
+ * Defines the contract for terminal user interface testing operations.
89
+ * Aligned with UiPort patterns for consistency across testing approaches.
90
+ */
91
+ /** Keyboard modifier keys for key combinations */
92
+ type TuiKeyModifiers = {
93
+ ctrl?: boolean;
94
+ alt?: boolean;
95
+ shift?: boolean;
96
+ meta?: boolean;
97
+ };
98
+ /** Options for wait operations */
99
+ type TuiWaitOptions = {
100
+ /** Maximum time to wait in milliseconds */
101
+ timeout?: number;
102
+ /** Polling interval in milliseconds */
103
+ interval?: number;
104
+ };
105
+ /** Captured screen state */
106
+ type TuiScreenCapture = {
107
+ /** Full screen text content */
108
+ text: string;
109
+ /** Screen content split by lines */
110
+ lines: string[];
111
+ /** Timestamp when capture was taken */
112
+ timestamp: number;
113
+ /** Terminal dimensions */
114
+ size: {
115
+ cols: number;
116
+ rows: number;
117
+ };
118
+ };
119
+ /** Result of snapshot comparison */
120
+ type TuiSnapshotResult = {
121
+ /** Whether the snapshot matched */
122
+ pass: boolean;
123
+ /** Diff output if snapshot didn't match */
124
+ diff?: string;
125
+ /** Path to the snapshot file */
126
+ snapshotPath?: string;
127
+ };
128
+ /** Mouse button types */
129
+ type TuiMouseButton = 'left' | 'right' | 'middle';
130
+ /** Mouse event types */
131
+ type TuiMouseEventType = 'click' | 'down' | 'up' | 'drag' | 'scroll';
132
+ /** Mouse event configuration */
133
+ type TuiMouseEvent = {
134
+ type: TuiMouseEventType;
135
+ position: {
136
+ x: number;
137
+ y: number;
138
+ };
139
+ button?: TuiMouseButton;
140
+ };
141
+ /** Configuration for TUI adapter initialization */
142
+ type TuiConfig = {
143
+ /** Command to start the TUI application (e.g., ['node', 'cli.js']) */
144
+ command: string[];
145
+ /** Terminal size (defaults to 80x24) */
146
+ size?: {
147
+ cols: number;
148
+ rows: number;
149
+ };
150
+ /** Working directory for the command */
151
+ cwd?: string;
152
+ /** Environment variables */
153
+ env?: Record<string, string>;
154
+ /** Enable debug output */
155
+ debug?: boolean;
156
+ /** Directory for snapshot storage */
157
+ snapshotDir?: string;
158
+ /** Shell to use (defaults to system shell) */
159
+ shell?: string;
160
+ };
161
+ /**
162
+ * Port interface for terminal user interface testing.
163
+ *
164
+ * This interface follows the same patterns as UiPort to enable:
165
+ * - Consistent step definitions across UI and TUI tests
166
+ * - Easy mental model for test authors
167
+ * - Potential for shared/hybrid testing scenarios
168
+ */
169
+ interface TuiPort {
170
+ /**
171
+ * Start the TUI application.
172
+ * Creates a new terminal session and launches the configured command.
173
+ */
174
+ start(): Promise<void>;
175
+ /**
176
+ * Stop the TUI application.
177
+ * Terminates the terminal session and cleans up resources.
178
+ */
179
+ stop(): Promise<void>;
180
+ /**
181
+ * Restart the TUI application.
182
+ * Equivalent to stop() followed by start().
183
+ */
184
+ restart(): Promise<void>;
185
+ /**
186
+ * Check if the TUI application is currently running.
187
+ */
188
+ isRunning(): boolean;
189
+ /**
190
+ * Type text into the terminal.
191
+ * Similar to UiPort.typeText - sends characters one by one.
192
+ * @param text - The text to type
193
+ * @param options - Optional typing options
194
+ */
195
+ typeText(text: string, options?: {
196
+ delay?: number;
197
+ }): Promise<void>;
198
+ /**
199
+ * Press a keyboard key, optionally with modifiers.
200
+ * Similar to UiPort.pressKey.
201
+ * @param key - Key name (e.g., 'enter', 'tab', 'up', 'down', 'f1')
202
+ * @param modifiers - Optional modifier keys
203
+ */
204
+ pressKey(key: string, modifiers?: TuiKeyModifiers): Promise<void>;
205
+ /**
206
+ * Send raw text without any interpretation.
207
+ * Useful for pasting content or sending special sequences.
208
+ * @param text - Raw text to send
209
+ */
210
+ sendText(text: string): Promise<void>;
211
+ /**
212
+ * Fill a labeled field in the TUI.
213
+ * Navigates to the field and enters the value.
214
+ * Similar to UiPort.fillLabel.
215
+ * @param fieldLabel - The label or identifier of the field
216
+ * @param value - The value to enter
217
+ */
218
+ fillField(fieldLabel: string, value: string): Promise<void>;
219
+ /**
220
+ * Select an option/menu item.
221
+ * Similar to UiPort.clickButton conceptually.
222
+ * @param option - The option text to select
223
+ */
224
+ selectOption(option: string): Promise<void>;
225
+ /**
226
+ * Send a mouse event to the terminal.
227
+ * Only works if the TUI application supports mouse input.
228
+ * @param event - Mouse event configuration
229
+ */
230
+ sendMouse(event: TuiMouseEvent): Promise<void>;
231
+ /**
232
+ * Click at a specific position.
233
+ * @param x - Column position (0-based)
234
+ * @param y - Row position (0-based)
235
+ * @param button - Mouse button (defaults to 'left')
236
+ */
237
+ click(x: number, y: number, button?: TuiMouseButton): Promise<void>;
238
+ /**
239
+ * Click on text content in the terminal.
240
+ * Finds the text and clicks on its position.
241
+ * @param text - The text to find and click
242
+ */
243
+ clickOnText(text: string): Promise<void>;
244
+ /**
245
+ * Assert that text is visible on the screen.
246
+ * Waits for the text to appear within timeout.
247
+ * Similar to UiPort.expectText.
248
+ * @param text - The expected text
249
+ * @param options - Wait options
250
+ */
251
+ expectText(text: string, options?: TuiWaitOptions): Promise<void>;
252
+ /**
253
+ * Assert that text matching a pattern is visible.
254
+ * @param pattern - Regular expression to match
255
+ * @param options - Wait options
256
+ */
257
+ expectPattern(pattern: RegExp, options?: TuiWaitOptions): Promise<void>;
258
+ /**
259
+ * Assert that text is NOT visible on the screen.
260
+ * @param text - The text that should not be present
261
+ */
262
+ expectNotText(text: string): Promise<void>;
263
+ /**
264
+ * Assert the screen contains specific text (immediate check, no wait).
265
+ * @param text - The expected text
266
+ */
267
+ assertScreenContains(text: string): Promise<void>;
268
+ /**
269
+ * Assert the screen matches a regular expression.
270
+ * @param pattern - Regular expression to match against screen content
271
+ */
272
+ assertScreenMatches(pattern: RegExp): Promise<void>;
273
+ /**
274
+ * Wait for text to appear on screen.
275
+ * @param text - Text to wait for
276
+ * @param options - Wait options
277
+ */
278
+ waitForText(text: string, options?: TuiWaitOptions): Promise<void>;
279
+ /**
280
+ * Wait for text matching pattern to appear.
281
+ * @param pattern - Regular expression to match
282
+ * @param options - Wait options
283
+ */
284
+ waitForPattern(pattern: RegExp, options?: TuiWaitOptions): Promise<void>;
285
+ /**
286
+ * Wait for the application to be ready.
287
+ * Implementation-specific (e.g., wait for prompt, initial screen).
288
+ */
289
+ waitForReady(): Promise<void>;
290
+ /**
291
+ * Wait for a specific duration.
292
+ * Similar to UiPort.waitSeconds.
293
+ * @param seconds - Duration to wait in seconds
294
+ */
295
+ waitSeconds(seconds: number): Promise<void>;
296
+ /**
297
+ * Capture the current screen state.
298
+ * @returns Screen capture with text content and metadata
299
+ */
300
+ captureScreen(): Promise<TuiScreenCapture>;
301
+ /**
302
+ * Get the current screen text content.
303
+ * @returns Plain text representation of the screen
304
+ */
305
+ getScreenText(): Promise<string>;
306
+ /**
307
+ * Get screen content as lines.
308
+ * @returns Array of screen lines
309
+ */
310
+ getScreenLines(): Promise<string[]>;
311
+ /**
312
+ * Take a snapshot of the current screen state.
313
+ * Saves to the configured snapshot directory.
314
+ * @param name - Snapshot identifier
315
+ */
316
+ takeSnapshot(name: string): Promise<void>;
317
+ /**
318
+ * Compare current screen against a saved snapshot.
319
+ * @param name - Snapshot identifier to compare against
320
+ * @returns Comparison result with pass/fail and diff
321
+ */
322
+ matchSnapshot(name: string): Promise<TuiSnapshotResult>;
323
+ /**
324
+ * Clear the terminal screen.
325
+ */
326
+ clear(): Promise<void>;
327
+ /**
328
+ * Resize the terminal.
329
+ * @param size - New dimensions
330
+ */
331
+ resize(size: {
332
+ cols: number;
333
+ rows: number;
334
+ }): Promise<void>;
335
+ /**
336
+ * Get the current terminal size.
337
+ */
338
+ getSize(): {
339
+ cols: number;
340
+ rows: number;
341
+ };
342
+ /**
343
+ * Get the current configuration.
344
+ */
345
+ getConfig(): TuiConfig;
346
+ }
347
+
348
+ /**
349
+ * Callback type for obtaining admin auth headers for cleanup operations.
350
+ * Consumers can provide their own implementation (e.g., Keycloak, Auth0, Okta)
351
+ * via the `getCleanupAuth` option in createBddTest().
352
+ */
353
+ type CleanupAuthProvider = (request: APIRequestContext) => Promise<Record<string, string>>;
354
+ type CreateContext = PlaywrightTestArgs & PlaywrightWorkerArgs & {
355
+ apiRequest: APIRequestContext;
356
+ page: Page;
357
+ };
358
+ /**
359
+ * Factory function type for creating a TUI adapter.
360
+ * Returns undefined if TUI testing is not configured.
361
+ */
362
+ type TuiFactory = () => TuiPort | undefined;
363
+ type CreateBddTestOptions = {
364
+ createApi?: (ctx: CreateContext) => ApiPort;
365
+ createUi?: (ctx: CreateContext) => UiPort;
366
+ createAuth?: (ctx: CreateContext & {
367
+ api: ApiPort;
368
+ ui: UiPort;
369
+ }) => AuthPort;
370
+ createCleanup?: (ctx: CreateContext) => CleanupPort;
371
+ /**
372
+ * Custom auth provider for cleanup operations.
373
+ * Return a record of headers (e.g., { Authorization: 'Bearer ...' }) to
374
+ * authenticate cleanup API calls.
375
+ *
376
+ * Use this to integrate with any auth provider (Keycloak, Auth0, Okta, etc.)
377
+ * without coupling the framework to a specific identity provider.
378
+ *
379
+ * If not provided, the default provider attempts a form-based login using
380
+ * DEFAULT_ADMIN_USERNAME / DEFAULT_ADMIN_PASSWORD env vars, or uses
381
+ * CLEANUP_AUTH_TOKEN if set. If no credentials are configured, cleanup
382
+ * runs unauthenticated.
383
+ */
384
+ getCleanupAuth?: CleanupAuthProvider;
385
+ /**
386
+ * Factory function for creating a TUI adapter.
387
+ * Unlike other adapters, this is a simple factory that doesn't receive context,
388
+ * as TUI testing operates independently of Playwright's browser context.
389
+ *
390
+ * @example
391
+ * ```typescript
392
+ * createTui: () => new TuiTesterAdapter({
393
+ * command: ['node', 'dist/cli.js'],
394
+ * size: { cols: 100, rows: 30 },
395
+ * }),
396
+ * ```
397
+ */
398
+ createTui?: TuiFactory;
399
+ worldFactory?: () => World;
400
+ };
401
+ declare function createBddTest(options?: CreateBddTestOptions): {
402
+ test: _playwright_test.TestType<PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & playwright_bdd.BddTestFixtures & {
403
+ world: World;
404
+ api: ApiPort;
405
+ ui: UiPort;
406
+ auth: AuthPort;
407
+ cleanup: CleanupPort;
408
+ tui: TuiPort | undefined;
409
+ apiRequest: APIRequestContext;
410
+ }, PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions & playwright_bdd.BddWorkerFixtures>;
411
+ expect: _playwright_test.Expect<{}>;
412
+ };
413
+
414
+ declare function interpolate(template: string, vars: Record<string, string>): string;
415
+ declare function tryParseJson(text: string): unknown;
416
+ declare function selectPath(root: unknown, path: string): unknown;
417
+ declare function parseExpected(input: string, world: World): unknown;
418
+ declare function assertMasked(val: unknown): void;
419
+ declare function registerCleanup(world: World, item: {
420
+ method?: 'DELETE' | 'POST' | 'PATCH' | 'PUT';
421
+ path: string;
422
+ body?: unknown;
423
+ }): void;
424
+
425
+ declare class PlaywrightApiAdapter implements ApiPort {
426
+ private readonly request;
427
+ constructor(request: APIRequestContext);
428
+ sendJson(method: ApiMethod, path: string, body?: unknown, headers?: Record<string, string>): Promise<ApiResult>;
429
+ sendForm(method: 'POST' | 'PUT' | 'PATCH', path: string, form: Record<string, string>, headers?: Record<string, string>): Promise<ApiResult>;
430
+ }
431
+
432
+ declare class PlaywrightUiAdapter implements UiPort {
433
+ private readonly page;
434
+ constructor(page: Page);
435
+ goto(path: string): Promise<void>;
436
+ clickButton(name: string): Promise<void>;
437
+ clickLink(name: string): Promise<void>;
438
+ fillPlaceholder(placeholder: string, value: string): Promise<void>;
439
+ fillLabel(label: string, value: string): Promise<void>;
440
+ expectText(text: string): Promise<void>;
441
+ expectUrlContains(part: string): Promise<void>;
442
+ goBack(): Promise<void>;
443
+ reload(): Promise<void>;
444
+ waitSeconds(seconds: number): Promise<void>;
445
+ waitForPageLoad(): Promise<void>;
446
+ getCurrentUrl(): Promise<string>;
447
+ zoomTo(scale: number): Promise<void>;
448
+ typeText(text: string): Promise<void>;
449
+ pressKey(key: string): Promise<void>;
450
+ clickElementThatContains(clickMode: UiClickMode, elementType: string, text: string): Promise<void>;
451
+ clickElementWith(clickMode: UiClickMode, ordinal: string, text: string, method: UiLocatorMethod): Promise<void>;
452
+ fillDropdown(value: string, dropdownLabel: string): Promise<void>;
453
+ inputInElement(action: UiInputMode, value: string, ordinal: string, text: string, method: UiLocatorMethod): Promise<void>;
454
+ expectUrl(mode: UiUrlAssertMode, expected: string): Promise<void>;
455
+ expectNewTabUrl(mode: UiUrlAssertMode, expected: string): Promise<void>;
456
+ expectElementWithTextVisible(elementType: string, text: string, shouldBeVisible: boolean): Promise<void>;
457
+ expectElementState(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState): Promise<void>;
458
+ expectElementStateWithin(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState, seconds: number): Promise<void>;
459
+ private parseOrdinal;
460
+ private locatorBy;
461
+ private performClick;
462
+ private expectState;
463
+ private assertUrlAgainst;
464
+ }
465
+
466
+ declare class UniversalAuthAdapter implements AuthPort {
467
+ private readonly deps;
468
+ constructor(deps: {
469
+ api: ApiPort;
470
+ ui: UiPort;
471
+ });
472
+ apiSetBearer(world: World, token: string): void;
473
+ apiLoginAsAdmin(world: World): Promise<void>;
474
+ apiLoginAsUser(world: World): Promise<void>;
475
+ private apiLogin;
476
+ uiLoginAsAdmin(world: World): Promise<void>;
477
+ uiLoginAsUser(world: World): Promise<void>;
478
+ private uiLogin;
479
+ }
480
+
481
+ type CleanupRule = {
482
+ varMatch: string;
483
+ method?: 'DELETE' | 'POST' | 'PATCH' | 'PUT';
484
+ path: string;
485
+ body?: unknown;
486
+ };
487
+ declare class DefaultCleanupAdapter implements CleanupPort {
488
+ private readonly rules;
489
+ private readonly allowHeuristic;
490
+ constructor(input?: {
491
+ rules?: CleanupRule[];
492
+ allowHeuristic?: boolean;
493
+ });
494
+ registerFromVar(world: World, varName: string, id: unknown, meta?: unknown): void;
495
+ }
496
+
497
+ /**
498
+ * TUI Tester Adapter
499
+ *
500
+ * Implements TuiPort using the tui-tester library (https://github.com/luxquant/tui-tester).
501
+ * Provides end-to-end testing capabilities for terminal user interfaces via tmux.
502
+ */
503
+
504
+ declare class TuiTesterAdapter implements TuiPort {
505
+ private tester;
506
+ private config;
507
+ private running;
508
+ private tuiTesterModule;
509
+ constructor(config: TuiConfig);
510
+ /**
511
+ * Lazily load the tui-tester module to support optional dependency
512
+ */
513
+ private loadTuiTester;
514
+ /**
515
+ * Get the tester instance, throwing if not started
516
+ */
517
+ private getTester;
518
+ start(): Promise<void>;
519
+ stop(): Promise<void>;
520
+ restart(): Promise<void>;
521
+ isRunning(): boolean;
522
+ typeText(text: string, options?: {
523
+ delay?: number;
524
+ }): Promise<void>;
525
+ pressKey(key: string, modifiers?: TuiKeyModifiers): Promise<void>;
526
+ sendText(text: string): Promise<void>;
527
+ fillField(fieldLabel: string, value: string): Promise<void>;
528
+ selectOption(option: string): Promise<void>;
529
+ sendMouse(event: TuiMouseEvent): Promise<void>;
530
+ click(x: number, y: number, button?: TuiMouseButton): Promise<void>;
531
+ clickOnText(text: string): Promise<void>;
532
+ expectText(text: string, options?: TuiWaitOptions): Promise<void>;
533
+ expectPattern(pattern: RegExp, options?: TuiWaitOptions): Promise<void>;
534
+ expectNotText(text: string): Promise<void>;
535
+ assertScreenContains(text: string): Promise<void>;
536
+ assertScreenMatches(pattern: RegExp): Promise<void>;
537
+ waitForText(text: string, options?: TuiWaitOptions): Promise<void>;
538
+ waitForPattern(pattern: RegExp, options?: TuiWaitOptions): Promise<void>;
539
+ waitForReady(): Promise<void>;
540
+ waitSeconds(seconds: number): Promise<void>;
541
+ captureScreen(): Promise<TuiScreenCapture>;
542
+ getScreenText(): Promise<string>;
543
+ getScreenLines(): Promise<string[]>;
544
+ takeSnapshot(name: string): Promise<void>;
545
+ matchSnapshot(name: string): Promise<TuiSnapshotResult>;
546
+ clear(): Promise<void>;
547
+ resize(size: {
548
+ cols: number;
549
+ rows: number;
550
+ }): Promise<void>;
551
+ getSize(): {
552
+ cols: number;
553
+ rows: number;
554
+ };
555
+ getConfig(): TuiConfig;
556
+ }
557
+
558
+ /**
559
+ * Fetch Intercept Auth Adapter
560
+ *
561
+ * Provides UI authentication by intercepting fetch requests and injecting headers.
562
+ * Useful for testing UIs that communicate with APIs using fetch.
563
+ */
564
+
565
+ interface FetchInterceptAuthData {
566
+ userId: string;
567
+ tenantId?: string;
568
+ roles: string[];
569
+ token?: string;
570
+ }
571
+ interface FetchInterceptConfig {
572
+ /**
573
+ * URL pattern to match for header injection.
574
+ * If not provided, headers are added to all fetch requests.
575
+ */
576
+ urlPattern?: string | RegExp;
577
+ /**
578
+ * Headers to inject. Can be static values or functions that receive auth data.
579
+ */
580
+ headers: Record<string, string | ((data: FetchInterceptAuthData) => string)>;
581
+ /**
582
+ * Key in localStorage to store auth data for UI components to read.
583
+ */
584
+ localStorageKey?: string;
585
+ }
586
+ /**
587
+ * Default configuration for bypass auth mode.
588
+ */
589
+ declare const defaultBypassConfig: FetchInterceptConfig;
590
+ /**
591
+ * Default configuration for bearer token auth.
592
+ */
593
+ declare const defaultBearerConfig: FetchInterceptConfig;
594
+ /**
595
+ * Set up fetch interception on a page.
596
+ *
597
+ * @param page - Playwright page
598
+ * @param authData - Authentication data to inject
599
+ * @param config - Configuration for interception
600
+ */
601
+ declare function setupFetchIntercept(page: Page, authData: FetchInterceptAuthData, config?: FetchInterceptConfig): Promise<void>;
602
+ /**
603
+ * Clear fetch interception setup (by reloading the page).
604
+ * Note: There's no clean way to remove addInitScript, so we reload.
605
+ */
606
+ declare function clearFetchIntercept(page: Page): Promise<void>;
607
+ /**
608
+ * Helper to set up bypass auth for a page.
609
+ */
610
+ declare function setupBypassAuth(page: Page, userId: string, roles: string[], tenantId?: string): Promise<void>;
611
+ /**
612
+ * Helper to set up bearer token auth for a page.
613
+ */
614
+ declare function setupBearerAuth(page: Page, token: string): Promise<void>;
615
+
616
+ /**
617
+ * Configuration for OIDC-based cleanup authentication.
618
+ *
619
+ * Reads from env vars by default:
620
+ * - OIDC_TOKEN_URL: Full token endpoint URL (required)
621
+ * - OIDC_CLIENT_ID: OAuth2 client ID (required)
622
+ * - OIDC_CLIENT_SECRET: OAuth2 client secret (optional, for confidential clients)
623
+ * - OIDC_GRANT_TYPE: Grant type (default: 'client_credentials')
624
+ * - OIDC_SCOPE: Requested scopes (optional)
625
+ * - OIDC_USERNAME: Username for password grant (optional)
626
+ * - OIDC_PASSWORD: Password for password grant (optional)
627
+ * - OIDC_EXTRA_HEADERS: JSON object of additional headers to include (optional)
628
+ */
629
+ interface OidcCleanupAuthConfig {
630
+ /** Full URL to the OIDC token endpoint. */
631
+ tokenUrl?: string;
632
+ /** OAuth2 client ID. */
633
+ clientId?: string;
634
+ /** OAuth2 client secret (for confidential clients). */
635
+ clientSecret?: string;
636
+ /** Grant type. Defaults to 'client_credentials'. */
637
+ grantType?: string;
638
+ /** Requested scopes (space-separated). */
639
+ scope?: string;
640
+ /** Username for 'password' grant type. */
641
+ username?: string;
642
+ /** Password for 'password' grant type. */
643
+ password?: string;
644
+ /**
645
+ * Additional headers to include in cleanup API requests
646
+ * (e.g., { 'x-user-roles': 'admin' }).
647
+ */
648
+ extraHeaders?: Record<string, string>;
649
+ }
650
+ /**
651
+ * Creates an OIDC-based cleanup auth provider.
652
+ *
653
+ * Works with any OIDC-compliant provider (Keycloak, Auth0, Okta, Azure AD, etc.)
654
+ * by configuring the token endpoint URL and client credentials.
655
+ *
656
+ * @example Keycloak with password grant:
657
+ * ```typescript
658
+ * import { createBddTest, createOidcCleanupAuth } from '@esimplicitylabs/katalyst-xspec';
659
+ *
660
+ * // Set env vars: OIDC_TOKEN_URL, OIDC_CLIENT_ID, OIDC_USERNAME, OIDC_PASSWORD
661
+ * const test = createBddTest({
662
+ * getCleanupAuth: createOidcCleanupAuth({
663
+ * grantType: 'password',
664
+ * extraHeaders: { 'x-user-roles': 'admin' },
665
+ * }),
666
+ * });
667
+ * ```
668
+ *
669
+ * @example Auth0 with client_credentials grant:
670
+ * ```typescript
671
+ * const test = createBddTest({
672
+ * getCleanupAuth: createOidcCleanupAuth({
673
+ * tokenUrl: 'https://your-tenant.auth0.com/oauth/token',
674
+ * clientId: 'your-client-id',
675
+ * clientSecret: 'your-secret',
676
+ * scope: 'delete:resources',
677
+ * }),
678
+ * });
679
+ * ```
680
+ */
681
+ declare function createOidcCleanupAuth(config?: OidcCleanupAuthConfig): CleanupAuthProvider;
682
+ /**
683
+ * Reset the cached OIDC token (useful for testing or when tokens expire).
684
+ */
685
+ declare function resetOidcCleanupAuth(): void;
686
+
687
+ type TagsForProjectInput = {
688
+ projectTag: string;
689
+ extraTags?: string;
690
+ defaultExcludes?: string;
691
+ };
692
+ declare function tagsForProject({ projectTag, extraTags, defaultExcludes }: TagsForProjectInput): string;
693
+ declare function resolveExtraTags(raw?: string | null): string | undefined;
694
+
695
+ /**
696
+ * Options for configuring worker resolution.
697
+ */
698
+ type ResolveWorkersOptions = {
699
+ /**
700
+ * The type of test being run.
701
+ * When set to `'tui'`, workers is always forced to `1` (TUI tests require sequential execution).
702
+ */
703
+ testType?: 'api' | 'ui' | 'tui' | 'hybrid';
704
+ /**
705
+ * Number of workers to use in CI environments (when `CI` env var is truthy).
706
+ * @default 1
707
+ */
708
+ ciWorkers?: number;
709
+ /**
710
+ * Default number of workers for local development.
711
+ * Set to `undefined` (default) to let Playwright decide (50% of CPU cores).
712
+ * Set to a number to override.
713
+ */
714
+ defaultWorkers?: number;
715
+ };
716
+ /**
717
+ * Resolves the number of Playwright workers based on environment variables,
718
+ * test type, and CI detection.
719
+ *
720
+ * **Environment variable:** `WORKERS`
721
+ * - `'auto'` or unset — uses smart defaults (Playwright decides locally, 1 in CI)
722
+ * - A positive integer (e.g. `'4'`) — uses that exact worker count
723
+ * - `'50%'` style percentage strings are passed through as strings for Playwright
724
+ *
725
+ * **Precedence:**
726
+ * 1. `testType: 'tui'` always returns `1`
727
+ * 2. `WORKERS` env var with a valid number overrides everything else
728
+ * 3. CI environment defaults to `ciWorkers` (default: `1`)
729
+ * 4. Otherwise returns `defaultWorkers` (default: `undefined`, letting Playwright decide)
730
+ *
731
+ * @example
732
+ * ```ts
733
+ * import { resolveWorkers } from '@esimplicitylabs/katalyst-xspec';
734
+ *
735
+ * export default defineConfig({
736
+ * workers: resolveWorkers(),
737
+ * projects: [
738
+ * { name: 'api', ...apiBdd },
739
+ * { name: 'tui', ...tuiBdd, workers: resolveWorkers({ testType: 'tui' }) },
740
+ * ],
741
+ * });
742
+ * ```
743
+ */
744
+ declare function resolveWorkers(options?: ResolveWorkersOptions): number | undefined;
745
+ /**
746
+ * Returns the number of available CPU cores on the current machine.
747
+ * Useful for logging or making informed decisions about worker counts.
748
+ */
749
+ declare function getCpuCount(): number;
750
+
751
+ /**
752
+ * Options for resolving the features directory path.
753
+ */
754
+ type ResolveFeaturesOptions = {
755
+ /**
756
+ * Default path to use when `FEATURES_DIR` env var is not set.
757
+ * @default 'features'
758
+ */
759
+ defaultDir?: string;
760
+ };
761
+ /**
762
+ * Options for resolving the custom steps directory path.
763
+ */
764
+ type ResolveStepsOptions = {
765
+ /**
766
+ * Default path to use when `CUSTOM_STEPS_DIR` env var is not set.
767
+ * @default 'features/steps'
768
+ */
769
+ defaultDir?: string;
770
+ };
771
+ /**
772
+ * Resolves the path to the BDD features directory.
773
+ *
774
+ * **Environment variable:** `FEATURES_DIR`
775
+ * - When set, returns the value as-is (relative or absolute)
776
+ * - When unset, returns `defaultDir` (default: `'features'`)
777
+ *
778
+ * Use this in `playwright.config.ts` with `defineBddProject()` to make
779
+ * feature file locations configurable per-environment.
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * import { resolveFeatures } from '@esimplicitylabs/katalyst-xspec';
784
+ * import { defineBddProject } from 'playwright-bdd';
785
+ *
786
+ * const apiBdd = defineBddProject({
787
+ * name: 'api',
788
+ * features: `${resolveFeatures()}/api/**\/*.feature`,
789
+ * steps: `${resolveSteps()}/**\/*.ts`,
790
+ * });
791
+ * ```
792
+ *
793
+ * @example
794
+ * ```bash
795
+ * # Run from the layer directory (default)
796
+ * just test-bdd-run dev
797
+ *
798
+ * # Override via env var (used by just recipes in multi-layer repos)
799
+ * FEATURES_DIR=../../claims-api/tests/features just test-bdd-run dev
800
+ * ```
801
+ */
802
+ declare function resolveFeatures(options?: ResolveFeaturesOptions): string;
803
+ /**
804
+ * Resolves the path to the custom step definitions directory.
805
+ *
806
+ * **Environment variable:** `CUSTOM_STEPS_DIR`
807
+ * - When set, returns the value as-is (relative or absolute)
808
+ * - When unset, returns `defaultDir` (default: `'features/steps'`)
809
+ *
810
+ * Custom steps are layer-specific Given/When/Then definitions that extend
811
+ * the library's pre-built steps. They live alongside the feature files in
812
+ * each test layer.
813
+ *
814
+ * @example
815
+ * ```ts
816
+ * import { resolveSteps } from '@esimplicitylabs/katalyst-xspec';
817
+ * import { defineBddProject } from 'playwright-bdd';
818
+ *
819
+ * const apiBdd = defineBddProject({
820
+ * name: 'api',
821
+ * features: `${resolveFeatures()}/api/**\/*.feature`,
822
+ * steps: `${resolveSteps()}/**\/*.ts`,
823
+ * });
824
+ * ```
825
+ */
826
+ declare function resolveSteps(options?: ResolveStepsOptions): string;
827
+ /**
828
+ * Builds glob patterns for use with `defineBddProject()` based on resolved paths.
829
+ *
830
+ * A convenience wrapper combining `resolveFeatures()` and `resolveSteps()` into
831
+ * the shape expected by playwright-bdd's `defineBddProject()`.
832
+ *
833
+ * @example
834
+ * ```ts
835
+ * import { resolveBddPaths } from '@esimplicitylabs/katalyst-xspec';
836
+ * import { defineBddProject } from 'playwright-bdd';
837
+ *
838
+ * const { features, steps } = resolveBddPaths({ tag: 'api' });
839
+ * const apiBdd = defineBddProject({ name: 'api', features, steps });
840
+ * ```
841
+ */
842
+ declare function resolveBddPaths(options?: {
843
+ /**
844
+ * Test tag to scope feature file glob (e.g., 'api', 'ui').
845
+ * Creates pattern: `{featuresDir}/{tag}/**\/*.feature`
846
+ * If omitted, uses `{featuresDir}/**\/*.feature`
847
+ */
848
+ tag?: string;
849
+ /** Options for features resolution */
850
+ featuresOptions?: ResolveFeaturesOptions;
851
+ /** Options for steps resolution */
852
+ stepsOptions?: ResolveStepsOptions;
853
+ }): {
854
+ features: string;
855
+ steps: string;
856
+ };
857
+
858
+ /**
859
+ * Network target resolution for the API request context.
860
+ *
861
+ * macOS resolves `*.localhost` to `::1` (IPv6) first, but a local kind cluster
862
+ * binds its ingress on `0.0.0.0` (IPv4 only), so requests to `::1` fail with
863
+ * ECONNRESET. For plain-http `*.localhost` targets we connect to `127.0.0.1`
864
+ * instead and send the original host as the `Host` header so host-based
865
+ * ingress routing (Istio Gateway/VirtualService, nginx, etc.) still matches.
866
+ *
867
+ * Deliberately NOT rewritten:
868
+ * - bare `localhost`: local dev servers (Vite, Node >=17) often bind `::1` only.
869
+ * - `https:`: TLS SNI and certificate checks use the URL host, so connecting to
870
+ * 127.0.0.1 would break verification regardless of the Host header.
871
+ *
872
+ * Disable entirely with `KATALYST_XSPEC_FORCE_IPV4=false` (or `0`). The
873
+ * pre-rename `STACK_TESTS_FORCE_IPV4` is still honoured.
874
+ */
875
+ type ApiRequestTarget = {
876
+ baseURL: string;
877
+ extraHTTPHeaders?: Record<string, string>;
878
+ };
879
+ declare function resolveApiRequestTarget(baseURL: string, env?: Record<string, string | undefined>): ApiRequestTarget;
880
+
881
+ export { type ApiMethod, type ApiPort, type ApiRequestTarget, type ApiResult, type AuthPort, type CleanupAuthProvider, type CleanupItem, type CleanupPort, type CleanupRule, type CreateBddTestOptions, DefaultCleanupAdapter, type FetchInterceptAuthData, type FetchInterceptConfig, type OidcCleanupAuthConfig, PlaywrightApiAdapter, PlaywrightUiAdapter, type ResolveFeaturesOptions, type ResolveStepsOptions, type ResolveWorkersOptions, type TuiConfig, type TuiFactory, type TuiKeyModifiers, type TuiMouseButton, type TuiMouseEvent, type TuiMouseEventType, type TuiPort, type TuiScreenCapture, type TuiSnapshotResult, TuiTesterAdapter, type TuiWaitOptions, type UiClickMode, type UiElementState, type UiInputMode, type UiLocatorMethod, type UiPort, type UiUrlAssertMode, UniversalAuthAdapter, type World, assertMasked, clearFetchIntercept, createBddTest, createOidcCleanupAuth, defaultBearerConfig, defaultBypassConfig, getCpuCount, initWorld, interpolate, parseExpected, registerCleanup, resetOidcCleanupAuth, resolveApiRequestTarget, resolveBddPaths, resolveExtraTags, resolveFeatures, resolveSteps, resolveWorkers, selectPath, setupBearerAuth, setupBypassAuth, setupFetchIntercept, tagsForProject, tryParseJson };