@esimplicitylabs/katalyst-xspec 0.7.1 → 0.8.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.
@@ -28,7 +28,7 @@ const test = createBddTest({
28
28
  ### Environment Variables
29
29
 
30
30
  ```bash
31
- API_BASE_URL=http://localhost:3000
31
+ API_BASE_URL=http://localhost:3000 # optional: defaults to FRONTEND_URL (older alias TARGET_BASE_URL)
32
32
  ```
33
33
 
34
34
  ### Features
@@ -62,8 +62,7 @@ const test = createBddTest({
62
62
  ### Environment Variables
63
63
 
64
64
  ```bash
65
- FRONTEND_URL=http://localhost:3000
66
- BASE_URL=http://localhost:3000
65
+ FRONTEND_URL=http://localhost:3000 # older alias BASE_URL
67
66
  HEADLESS=true
68
67
  ```
69
68
 
@@ -73,6 +72,7 @@ HEADLESS=true
73
72
  - Automatic waiting for elements
74
73
  - Multiple click modes (normal, force, dispatch)
75
74
  - Screenshot and debugging support
75
+ - Login helpers used by `UniversalAuthAdapter` (optional on `UiPort`): `fillField(name, value, { timeoutMs })` (label, placeholder or `name`), `waitForUrl(predicate, timeoutMs)`, `waitForText(text, timeoutMs)`, `saveSession()`, `restoreSession(state)`
76
76
 
77
77
  ## TuiTesterAdapter
78
78
 
@@ -146,9 +146,12 @@ Implements `AuthPort` for both API and UI authentication.
146
146
  ### Constructor
147
147
 
148
148
  ```typescript
149
- class UniversalAuthAdapter implements AuthPort {
150
- constructor(private readonly deps: { api: ApiPort; ui: UiPort }) {}
151
- }
149
+ new UniversalAuthAdapter({
150
+ api, // ApiPort
151
+ ui, // UiPort
152
+ roles?, // { pm: { username, password } } - overrides AUTH_<ROLE>_* env vars
153
+ env?, // settings source, default process.env
154
+ })
152
155
  ```
153
156
 
154
157
  ### Configuration
@@ -164,32 +167,48 @@ const test = createBddTest({
164
167
  ### Environment Variables
165
168
 
166
169
  ```bash
167
- # Admin credentials
168
- DEFAULT_ADMIN_USERNAME=admin@example.com
169
- DEFAULT_ADMIN_PASSWORD=changeme
170
-
171
- # User credentials
172
- DEFAULT_USER_USERNAME=user@example.com
173
- DEFAULT_USER_PASSWORD=user123
174
-
175
- # Auth endpoint
170
+ # Credentials per role (any role name; "project manager" -> AUTH_PROJECT_MANAGER_*)
171
+ AUTH_ADMIN_USERNAME=admin@example.com
172
+ AUTH_ADMIN_PASSWORD=changeme
173
+ AUTH_PM_USERNAME=pm@example.com
174
+ AUTH_PM_PASSWORD=secret
175
+ # admin/user also accept the older DEFAULT_ADMIN_*, DEFAULT_USER_*, NON_ADMIN_*
176
+
177
+ # API login (defaults)
176
178
  API_AUTH_LOGIN_PATH=/auth/login
179
+ API_AUTH_BODY=form # or json
180
+ API_AUTH_USERNAME_FIELD=username
181
+ API_AUTH_PASSWORD_FIELD=password
182
+ # API_AUTH_TOKEN_PATH=data.jwt # default tries access_token, token, accessToken, data.*
183
+
184
+ # UI login (defaults)
185
+ UI_LOGIN_PATH=/login
186
+ UI_USERNAME_FIELD=Username # label, placeholder or name
187
+ UI_PASSWORD_FIELD=Password
188
+ UI_LOGIN_BUTTON=Login
189
+ # UI_LOGIN_SUCCESS_URL / UI_LOGIN_SUCCESS_TEXT / UI_LOGIN_TIMEOUT=10000 / UI_SESSION_REUSE=true
177
190
  ```
178
191
 
179
192
  ### Behavior
180
193
 
181
- **API Authentication:**
182
- 1. POSTs to `API_AUTH_LOGIN_PATH`
183
- 2. Stores token in `world.headers['Authorization']`
184
- 3. If credentials not set, skips silently with `console.warn`
194
+ **API login (`apiLoginAs(world, role)`):**
195
+ 1. POSTs the role's credentials to `API_AUTH_LOGIN_PATH` (form or JSON)
196
+ 2. Reads the token and sets `world.headers.Authorization = Bearer <token>`; with no token but a `Set-Cookie`, keeps the cookie session
197
+ 3. Fails with status, response body and the settings to check on error. Never starts a browser.
198
+
199
+ **UI login (`uiLoginAs(world, role, { reuseSession })`):**
200
+ 1. Navigates to `UI_LOGIN_PATH`
201
+ 2. Fills username/password fields matched by label, placeholder or `name`
202
+ 3. Clicks `UI_LOGIN_BUTTON` and waits to leave the login page (or for `UI_LOGIN_SUCCESS_URL` / `UI_LOGIN_SUCCESS_TEXT`); fails if it doesn't
203
+ 4. With `reuseSession` (used by `Given I am logged in as`), saves cookies + localStorage per role per worker and restores them later
204
+
205
+ **Missing credentials fail** with `MissingCredentialsError` naming the variables to set (no hardcoded defaults, no silent skip).
206
+
207
+ `apiLoginAsAdmin` / `apiLoginAsUser` / `uiLoginAsAdmin` / `uiLoginAsUser` are the roles `admin` / `user`. `credentialsFor(role)` returns resolved credentials; `clearUiSessions()` forgets saved sessions.
185
208
 
186
- **UI Authentication:**
187
- 1. Navigates to `UI_LOGIN_PATH` (default: `/login`)
188
- 2. Fills fields by placeholder (configurable via `UI_USERNAME_FIELD`, `UI_PASSWORD_FIELD`)
189
- 3. Clicks login button (configurable via `UI_LOGIN_BUTTON`)
190
- 4. If credentials not set, skips silently with `console.warn`
209
+ ### Extending
191
210
 
192
- > **Note:** No hardcoded default credentials are used. All credentials must be set via env vars.
211
+ Subclass and override `protected apiLogin(world, role, creds)` or `protected uiLogin(world, role, creds)` (e.g. SSO or a multi-step form). `this.api`, `this.ui`, `this.roles`, `this.env` are available. Role lookup, steps and session reuse keep working.
193
212
 
194
213
  ## DefaultCleanupAdapter
195
214
 
@@ -118,6 +118,14 @@ interface UiPort {
118
118
 
119
119
  // Utilities
120
120
  zoomTo(scale: number): Promise<void>;
121
+
122
+ // Optional: used by UniversalAuthAdapter for UI login. Custom UiPorts may omit
123
+ // them; login then uses fillPlaceholder and skips the success check and session reuse.
124
+ fillField?(name: string, value: string, options?: { timeoutMs?: number }): Promise<boolean>;
125
+ waitForUrl?(predicate: (url: string) => boolean, timeoutMs: number): Promise<boolean>;
126
+ waitForText?(text: string, timeoutMs: number): Promise<boolean>;
127
+ saveSession?(): Promise<UiSessionState>; // cookies + localStorage
128
+ restoreSession?(state: UiSessionState): Promise<void>;
121
129
  }
122
130
 
123
131
  type UiClickMode = 'click' | 'dispatch click' | 'force click' | 'force dispatch click';
@@ -216,14 +224,20 @@ interface AuthPort {
216
224
 
217
225
  uiLoginAsAdmin(world: World): Promise<void>;
218
226
  uiLoginAsUser(world: World): Promise<void>;
227
+
228
+ // Optional (0.8+): any named role. Steps fall back to the admin/user methods if absent.
229
+ apiLoginAs?(world: World, role: string): Promise<void>;
230
+ uiLoginAs?(world: World, role: string, options?: { reuseSession?: boolean }): Promise<void>;
219
231
  }
220
232
  ```
221
233
 
222
234
  ### Implementation Notes
223
235
 
224
236
  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
237
+ - Credentials: `roles` option, then `AUTH_<ROLE>_USERNAME` / `AUTH_<ROLE>_PASSWORD` (older `DEFAULT_ADMIN_*` / `DEFAULT_USER_*` for admin/user). Missing ones throw, naming the variables.
238
+ - API login: POSTs to `API_AUTH_LOGIN_PATH` (form or JSON per `API_AUTH_BODY`), reads the token (`API_AUTH_TOKEN_PATH`) or keeps the session cookie
239
+ - UI login: fills the form at `UI_LOGIN_PATH` (fields by label, placeholder or `name`), checks the page left the login page, optionally reuses the session
240
+ - Extend by subclassing and overriding `protected apiLogin` / `uiLogin`
227
241
 
228
242
  ## CleanupPort
229
243
 
@@ -68,7 +68,7 @@ Feature: [Resource] API
68
68
 
69
69
  ```gherkin
70
70
  Background:
71
- Given I am authenticated as an admin via API
71
+ Given I am authenticated as "admin" via API
72
72
  ```
73
73
 
74
74
  ### Step 3: Write Scenarios
@@ -164,7 +164,17 @@ Scenario: Submit [form name]
164
164
  Then I should see text "Success"
165
165
  ```
166
166
 
167
- **Pattern: Login Flow**
167
+ **Pattern: Log in as a role** (preferred when the login isn't what you're testing)
168
+ ```gherkin
169
+ Scenario: Project manager sees projects
170
+ Given I am logged in as "pm"
171
+ When I navigate to "/projects"
172
+ Then I should see text "Projects"
173
+ ```
174
+
175
+ `Given I am logged in as "<role>"` fills the app's login form with `AUTH_<ROLE>_USERNAME` / `AUTH_<ROLE>_PASSWORD` once per worker and reuses the session afterwards. Use `When I log in as "<role>" in UI` to always submit the form. For API calls use `Given I am authenticated as "<role>" via API`. Any role name works; tell the user which `AUTH_<ROLE>_*` variables to add to `.env`. Login paths and field names are configured with `API_AUTH_*` / `UI_*` (see docs/guides/authentication.md).
176
+
177
+ **Pattern: Login Flow (testing the form itself)**
168
178
  ```gherkin
169
179
  Scenario: User login
170
180
  Given I navigate to "/login"
@@ -244,7 +254,7 @@ Feature: [Workflow Name]
244
254
  ```gherkin
245
255
  Scenario: [Workflow description]
246
256
  # --- API SETUP PHASE ---
247
- Given I am authenticated as an admin via API
257
+ Given I am authenticated as "admin" via API
248
258
  [API steps to create test data]
249
259
 
250
260
  # --- UI VERIFICATION PHASE ---
@@ -256,7 +266,7 @@ Scenario: [Workflow description]
256
266
  ```gherkin
257
267
  Scenario: Create user via API, verify in admin panel
258
268
  # API: Create user
259
- Given I am authenticated as an admin via API
269
+ Given I am authenticated as "admin" via API
260
270
  Given I generate a UUID and store as "testId"
261
271
  When I POST "/admin/users" with JSON body:
262
272
  """
@@ -306,7 +316,7 @@ Given I register cleanup DELETE "/users/{userId}" # Important!
306
316
 
307
317
  ```gherkin
308
318
  Background:
309
- Given I am authenticated as an admin via API
319
+ Given I am authenticated as "admin" via API
310
320
  Given I generate a UUID and store as "runId"
311
321
 
312
322
  Scenario: Test 1
@@ -8,7 +8,7 @@ Common patterns for API testing with the Katalyst BDD framework.
8
8
 
9
9
  ```gherkin
10
10
  Scenario: List all [resources]
11
- Given I am authenticated as an admin via API
11
+ Given I am authenticated as "admin" via API
12
12
  When I GET "/[endpoint]"
13
13
  Then the response status should be 200
14
14
  And the response should be a JSON array
@@ -18,7 +18,7 @@ Scenario: List all [resources]
18
18
 
19
19
  ```gherkin
20
20
  Scenario: Get [resource] by ID
21
- Given I am authenticated as an admin via API
21
+ Given I am authenticated as "admin" via API
22
22
  When I GET "/[endpoint]/1"
23
23
  Then the response status should be 200
24
24
  And the response should be a JSON object
@@ -29,7 +29,7 @@ Scenario: Get [resource] by ID
29
29
 
30
30
  ```gherkin
31
31
  Scenario: Create [resource]
32
- Given I am authenticated as an admin via API
32
+ Given I am authenticated as "admin" via API
33
33
  Given I generate a UUID and store as "runId"
34
34
  When I POST "/[endpoint]" with JSON body:
35
35
  """
@@ -48,7 +48,7 @@ Scenario: Create [resource]
48
48
 
49
49
  ```gherkin
50
50
  Scenario: Update [resource] with PUT
51
- Given I am authenticated as an admin via API
51
+ Given I am authenticated as "admin" via API
52
52
  # First create the resource
53
53
  When I POST "/[endpoint]" with JSON body:
54
54
  """
@@ -72,7 +72,7 @@ Scenario: Update [resource] with PUT
72
72
 
73
73
  ```gherkin
74
74
  Scenario: Partial update with PATCH
75
- Given I am authenticated as an admin via API
75
+ Given I am authenticated as "admin" via API
76
76
  When I PATCH "/[endpoint]/{resourceId}" with JSON body:
77
77
  """
78
78
  { "status": "active" }
@@ -85,7 +85,7 @@ Scenario: Partial update with PATCH
85
85
 
86
86
  ```gherkin
87
87
  Scenario: Delete [resource]
88
- Given I am authenticated as an admin via API
88
+ Given I am authenticated as "admin" via API
89
89
  # Create resource to delete
90
90
  When I POST "/[endpoint]" with JSON body:
91
91
  """
@@ -105,18 +105,27 @@ Scenario: Delete [resource]
105
105
 
106
106
  ## Authentication Patterns
107
107
 
108
+ ### Any Role
109
+
110
+ ```gherkin
111
+ Background:
112
+ Given I am authenticated as "pm" via API
113
+ ```
114
+
115
+ Reads `AUTH_PM_USERNAME` / `AUTH_PM_PASSWORD` from `.env`. Missing credentials fail the step with a message naming the variables.
116
+
108
117
  ### Admin Authentication
109
118
 
110
119
  ```gherkin
111
120
  Background:
112
- Given I am authenticated as an admin via API
121
+ Given I am authenticated as "admin" via API
113
122
  ```
114
123
 
115
124
  ### User Authentication
116
125
 
117
126
  ```gherkin
118
127
  Background:
119
- Given I am authenticated as a user via API
128
+ Given I am authenticated as "user" via API
120
129
  ```
121
130
 
122
131
  ### Custom Token Authentication
@@ -150,7 +159,7 @@ Scenario: Login and use token
150
159
 
151
160
  ```gherkin
152
161
  Scenario: Resource not found
153
- Given I am authenticated as an admin via API
162
+ Given I am authenticated as "admin" via API
154
163
  When I GET "/[endpoint]/99999"
155
164
  Then the response status should be 404
156
165
  ```
@@ -159,7 +168,7 @@ Scenario: Resource not found
159
168
 
160
169
  ```gherkin
161
170
  Scenario: Invalid data returns 400
162
- Given I am authenticated as an admin via API
171
+ Given I am authenticated as "admin" via API
163
172
  When I POST "/[endpoint]" with JSON body:
164
173
  """
165
174
  { "email": "not-valid-email" }
@@ -181,7 +190,7 @@ Scenario: Unauthorized access
181
190
 
182
191
  ```gherkin
183
192
  Scenario: User cannot access admin endpoint
184
- Given I am authenticated as a user via API
193
+ Given I am authenticated as "user" via API
185
194
  When I GET "/admin/settings"
186
195
  Then the response status should be 403
187
196
  ```
@@ -237,7 +246,7 @@ Then the value at "created_at" should match "^\d{4}-\d{2}-\d{2}"
237
246
 
238
247
  ```gherkin
239
248
  Scenario: Paginated list
240
- Given I am authenticated as an admin via API
249
+ Given I am authenticated as "admin" via API
241
250
  When I GET "/[endpoint]?page=1&limit=10"
242
251
  Then the response status should be 200
243
252
  And the response should be a JSON object
@@ -250,7 +259,7 @@ Scenario: Paginated list
250
259
 
251
260
  ```gherkin
252
261
  Scenario: Filter by status
253
- Given I am authenticated as an admin via API
262
+ Given I am authenticated as "admin" via API
254
263
  When I GET "/[endpoint]?status=active"
255
264
  Then the response status should be 200
256
265
  And the response should be a JSON array
@@ -260,7 +269,7 @@ Scenario: Filter by status
260
269
 
261
270
  ```gherkin
262
271
  Scenario: Bulk create
263
- Given I am authenticated as an admin via API
272
+ Given I am authenticated as "admin" via API
264
273
  When I POST "/[endpoint]/bulk" with JSON body:
265
274
  """
266
275
  {
@@ -279,7 +288,7 @@ Scenario: Bulk create
279
288
 
280
289
  ```gherkin
281
290
  Scenario: Upload file
282
- Given I am authenticated as an admin via API
291
+ Given I am authenticated as "admin" via API
283
292
  Given I set header "Content-Type" to "multipart/form-data"
284
293
  # Note: Actual file upload requires custom step implementation
285
294
  ```
@@ -293,7 +302,7 @@ Feature: User Management API
293
302
  So that I can control system access
294
303
 
295
304
  Background:
296
- Given I am authenticated as an admin via API
305
+ Given I am authenticated as "admin" via API
297
306
  Given I generate a UUID and store as "runId"
298
307
 
299
308
  Scenario: Create new user
@@ -16,7 +16,7 @@ Hybrid tests are scenarios that mix API and UI steps. No tag is needed — every
16
16
  ```gherkin
17
17
  Scenario: Create user via API, verify in admin panel
18
18
  # API: Create test data
19
- Given I am authenticated as an admin via API
19
+ Given I am authenticated as "admin" via API
20
20
  Given I generate a UUID and store as "testId"
21
21
  When I POST "/admin/users" with JSON body:
22
22
  """
@@ -40,7 +40,7 @@ Scenario: Create user via API, verify in admin panel
40
40
  ```gherkin
41
41
  Scenario: Test order workflow with pre-created product
42
42
  # API: Create product
43
- Given I am authenticated as an admin via API
43
+ Given I am authenticated as "admin" via API
44
44
  Given I generate a UUID and store as "productId"
45
45
  When I POST "/admin/products" with JSON body:
46
46
  """
@@ -67,7 +67,7 @@ Scenario: Test order workflow with pre-created product
67
67
  ```gherkin
68
68
  Scenario: API update reflects in UI
69
69
  # API: Create and update
70
- Given I am authenticated as an admin via API
70
+ Given I am authenticated as "admin" via API
71
71
  When I POST "/users" with JSON body:
72
72
  """
73
73
  { "name": "Original Name" }
@@ -95,7 +95,7 @@ Scenario: API update reflects in UI
95
95
  ```gherkin
96
96
  Scenario: Use API-created ID in UI navigation
97
97
  # API: Create resource
98
- Given I am authenticated as an admin via API
98
+ Given I am authenticated as "admin" via API
99
99
  When I POST "/projects" with JSON body:
100
100
  """
101
101
  { "name": "Test Project" }
@@ -120,7 +120,7 @@ Scenario: Verify API data in UI
120
120
  Given I set variable "testName" to "Hybrid User {runId}"
121
121
 
122
122
  # API: Create with variables
123
- Given I am authenticated as an admin via API
123
+ Given I am authenticated as "admin" via API
124
124
  When I POST "/users" with JSON body:
125
125
  """
126
126
  {
@@ -140,12 +140,29 @@ Scenario: Verify API data in UI
140
140
 
141
141
  ## Authentication Patterns
142
142
 
143
+ ### Same Role in API and UI
144
+
145
+ ```gherkin
146
+ Scenario: PM creates via API, sees it in UI
147
+ Given I am authenticated as "pm" via API
148
+ When I POST "/api/projects" with JSON body:
149
+ """
150
+ { "name": "Apollo" }
151
+ """
152
+ Then the response status should be 201
153
+ Given I am logged in as "pm"
154
+ Given I navigate to "/projects"
155
+ Then I should see text "Apollo"
156
+ ```
157
+
158
+ Both steps read `AUTH_PM_USERNAME` / `AUTH_PM_PASSWORD`. Relative API paths go to `API_BASE_URL`, or to `FRONTEND_URL` when it isn't set, so a same-origin app needs only `FRONTEND_URL`.
159
+
143
160
  ### Separate API and UI Auth
144
161
 
145
162
  ```gherkin
146
163
  Scenario: Different auth for API vs UI
147
164
  # API: Admin creates data
148
- Given I am authenticated as an admin via API
165
+ Given I am authenticated as "admin" via API
149
166
  When I POST "/admin/announcements" with JSON body:
150
167
  """
151
168
  { "message": "Test announcement", "audience": "all" }
@@ -185,7 +202,7 @@ Scenario: Get token from API, use in UI
185
202
  ```gherkin
186
203
  Scenario: Complete order processing workflow
187
204
  # API: Setup - Create customer and product
188
- Given I am authenticated as an admin via API
205
+ Given I am authenticated as "admin" via API
189
206
  Given I generate a UUID and store as "runId"
190
207
 
191
208
  When I POST "/customers" with JSON body:
@@ -221,7 +238,7 @@ Scenario: Complete order processing workflow
221
238
 
222
239
  ```gherkin
223
240
  Scenario: Real-time sync between API and UI
224
- Given I am authenticated as an admin via API
241
+ Given I am authenticated as "admin" via API
225
242
  Given I generate a UUID and store as "runId"
226
243
 
227
244
  # API: Create initial data
@@ -258,7 +275,7 @@ Scenario: Real-time sync between API and UI
258
275
  Feature: User Settings
259
276
 
260
277
  Background:
261
- Given I am authenticated as an admin via API
278
+ Given I am authenticated as "admin" via API
262
279
  Given I generate a UUID and store as "testId"
263
280
 
264
281
  # Create fresh user for each test
@@ -292,7 +309,7 @@ Feature: User Settings
292
309
 
293
310
  ```gherkin
294
311
  Scenario: Dashboard with multiple data types
295
- Given I am authenticated as an admin via API
312
+ Given I am authenticated as "admin" via API
296
313
  Given I generate a UUID and store as "runId"
297
314
 
298
315
  # Create multiple related resources
@@ -330,7 +347,7 @@ Scenario: Dashboard with multiple data types
330
347
  ```gherkin
331
348
  Scenario: UI shows error when API resource deleted
332
349
  # API: Create and immediately delete
333
- Given I am authenticated as an admin via API
350
+ Given I am authenticated as "admin" via API
334
351
  When I POST "/items" with JSON body:
335
352
  """
336
353
  { "name": "Temporary Item" }
@@ -360,7 +377,7 @@ Feature: User Onboarding
360
377
  Given I set variable "userName" to "New User {testId}"
361
378
 
362
379
  # Step 1: Admin creates user invitation via API
363
- Given I am authenticated as an admin via API
380
+ Given I am authenticated as "admin" via API
364
381
  When I POST "/admin/invitations" with JSON body:
365
382
  """
366
383
  {
@@ -386,7 +403,7 @@ Feature: User Onboarding
386
403
  And the URL should contain "/dashboard"
387
404
 
388
405
  # Step 3: Verify user created via API
389
- Given I am authenticated as an admin via API
406
+ Given I am authenticated as "admin" via API
390
407
  When I GET "/admin/users?email={userEmail}"
391
408
  Then the response status should be 200
392
409
  And the value at "[0].name" should equal "{userName}"
@@ -35,6 +35,17 @@ Scenario: Go back to previous page
35
35
 
36
36
  ## Login Patterns
37
37
 
38
+ ### Log in as a Role (real login form, session reused)
39
+
40
+ ```gherkin
41
+ Scenario: Project manager dashboard
42
+ Given I am logged in as "pm"
43
+ Given I navigate to "/dashboard"
44
+ Then I should see text "Projects"
45
+ ```
46
+
47
+ Uses `AUTH_PM_USERNAME` / `AUTH_PM_PASSWORD` and the `UI_LOGIN_*` settings. Use `When I log in as "pm" in UI` when testing the login itself (always submits the form).
48
+
38
49
  ### Simple Login Form
39
50
 
40
51
  ```gherkin
@@ -97,21 +97,23 @@ cp .env.example .env
97
97
  Edit `.env` with your settings:
98
98
 
99
99
  ```bash
100
- # API Configuration
101
- API_BASE_URL=http://localhost:3000
102
-
103
- # Authentication (required -- no hardcoded defaults)
104
- DEFAULT_ADMIN_USERNAME=admin@example.com
105
- DEFAULT_ADMIN_PASSWORD=changeme
106
- DEFAULT_USER_USERNAME=user@example.com
107
- DEFAULT_USER_PASSWORD=changeme
108
- API_AUTH_LOGIN_PATH=/auth/login
109
-
110
- # UI Configuration
100
+ # Where to test
111
101
  FRONTEND_URL=http://localhost:3000
112
- BASE_URL=http://localhost:3000
102
+ # API_BASE_URL=http://localhost:4000 # only if the API is on another origin
113
103
  HEADLESS=true
114
104
 
105
+ # Who logs in: AUTH_<ROLE>_USERNAME / AUTH_<ROLE>_PASSWORD, any role name
106
+ AUTH_ADMIN_USERNAME=admin@example.com
107
+ AUTH_ADMIN_PASSWORD=changeme
108
+ # AUTH_PM_USERNAME=pm@example.com # for Given I am logged in as "pm"
109
+ # AUTH_PM_PASSWORD=changeme
110
+
111
+ # Login settings (defaults shown; change only if your app differs)
112
+ # API_AUTH_LOGIN_PATH=/auth/login
113
+ # API_AUTH_BODY=form # or json
114
+ # UI_LOGIN_PATH=/login
115
+ # UI_USERNAME_FIELD=Username # label, placeholder or name
116
+
115
117
  # Cleanup Rules (required -- no built-in rules)
116
118
  # CLEANUP_RULES='[{"varMatch":"user","path":"/api/users/{id}"}]'
117
119
  ```
@@ -122,10 +124,12 @@ Then use relative paths in features, e.g. `Given I navigate to "/login"`, `When
122
124
 
123
125
  | Steps used | Required Variables |
124
126
  |-----------|-------------------|
125
- | API steps with relative paths | `API_BASE_URL` |
126
- | UI steps with relative paths | `FRONTEND_URL` or `BASE_URL` |
127
- | Both in one scenario | Both API and UI variables |
128
- | Auth steps | `DEFAULT_*_USERNAME`, `DEFAULT_*_PASSWORD` |
127
+ | UI steps with relative paths | `FRONTEND_URL` (older alias `BASE_URL`) |
128
+ | API steps with relative paths | `API_BASE_URL`, or nothing extra if the API is on the `FRONTEND_URL` origin |
129
+ | Both in one scenario | `FRONTEND_URL` (+ `API_BASE_URL` if the API is elsewhere) |
130
+ | Login steps (`Given I am authenticated as "pm" via API`, `Given I am logged in as "pm"`) | `AUTH_PM_USERNAME`, `AUTH_PM_PASSWORD` (one pair per role) |
131
+
132
+ Missing credentials fail the login step with a message naming the variables. Each run prints `katalyst-xspec targets: UI … | API …` showing where tests point. Login settings: docs/guides/authentication.md.
129
133
 
130
134
  ## Step 4: Write Your First Test
131
135
 
@@ -161,7 +165,7 @@ Any scenario can mix API and UI steps — no tag or separate project needed. Put
161
165
  Feature: User Workflow
162
166
 
163
167
  Scenario: Create via API, verify in UI
164
- Given I am authenticated as an admin via API
168
+ Given I am authenticated as "admin" via API
165
169
  When I POST "/users" with JSON body:
166
170
  """
167
171
  { "name": "Test User" }
@@ -40,8 +40,9 @@ When I GET "/users/{userId}" # Becomes /users/123
40
40
  | `Then the response should be a JSON object` | Asserts response is an object |
41
41
  | `Then the value at {string} should equal {string}` | `Then the value at "name" should equal "John"` |
42
42
  | `And I store the value at {string} as {string}` | `And I store the value at "id" as "userId"` |
43
- | `Given I am authenticated as an admin via API` | Admin API authentication |
44
- | `Given I am authenticated as a user via API` | User API authentication |
43
+ | `Given I am authenticated as {string} via API` | `Given I am authenticated as "pm" via API` (reads `AUTH_PM_*`) |
44
+ | `Given I am authenticated as an admin via API` | Shorthand for role `"admin"` |
45
+ | `Given I am authenticated as a user via API` | Shorthand for role `"user"` |
45
46
  | `Given I set header {string} to {string}` | `Given I set header "X-Custom" to "value"` |
46
47
 
47
48
  ### UI Steps
@@ -49,6 +50,8 @@ When I GET "/users/{userId}" # Becomes /users/123
49
50
  | Step | Example |
50
51
  |------|---------|
51
52
  | `Given I navigate to {string}` | `Given I navigate to "/login"` |
53
+ | `Given I am logged in as {string}` | `Given I am logged in as "pm"` (UI login, session reused) |
54
+ | `When I log in as {string} in UI` | `When I log in as "pm" in UI` (always submits the form) |
52
55
  | `When I click the button {string}` | `When I click the button "Submit"` |
53
56
  | `When I click the link {string}` | `When I click the link "Sign Up"` |
54
57
  | `When I fill the field {string} with {string}` | `When I fill the field "Email" with "test@example.com"` |
@@ -106,7 +109,7 @@ For complete step definitions with all parameters and examples:
106
109
 
107
110
  ```gherkin
108
111
  Scenario: Create and fetch user
109
- Given I am authenticated as an admin via API
112
+ Given I am authenticated as "admin" via API
110
113
  When I POST "/users" with JSON body:
111
114
  """
112
115
  { "email": "test@example.com", "name": "Test" }
@@ -132,7 +135,7 @@ Scenario: User login
132
135
 
133
136
  ```gherkin
134
137
  Scenario: Create via API, verify in UI
135
- Given I am authenticated as an admin via API
138
+ Given I am authenticated as "admin" via API
136
139
  When I POST "/users" with JSON body:
137
140
  """
138
141
  { "email": "new@example.com" }
@@ -88,21 +88,29 @@ When I DELETE "/users/{userId}"
88
88
 
89
89
  ## Authentication Steps
90
90
 
91
- ### Admin Authentication
91
+ ### Log in as a role
92
92
 
93
93
  ```gherkin
94
- Given I am authenticated as an admin via API
94
+ Given I am authenticated as {string} via API
95
95
  ```
96
96
 
97
- Uses `DEFAULT_ADMIN_USERNAME` and `DEFAULT_ADMIN_PASSWORD` env variables.
97
+ **Example:**
98
+ ```gherkin
99
+ Given I am authenticated as "pm" via API
100
+ ```
98
101
 
99
- ### User Authentication
102
+ Reads `AUTH_<ROLE>_USERNAME` / `AUTH_<ROLE>_PASSWORD` (role upper-cased, spaces/dashes become `_`), POSTs them to `API_AUTH_LOGIN_PATH` (default `/auth/login`), and sends the returned token as `Authorization: Bearer ...` on later API steps. Cookie sessions work too. Login settings: `API_AUTH_BODY` (`form`|`json`), `API_AUTH_USERNAME_FIELD`, `API_AUTH_PASSWORD_FIELD`, `API_AUTH_TOKEN_PATH`.
103
+
104
+ If credentials are missing, the step fails with a message naming the variables to set.
105
+
106
+ ### Admin / user shorthand
100
107
 
101
108
  ```gherkin
109
+ Given I am authenticated as an admin via API
102
110
  Given I am authenticated as a user via API
103
111
  ```
104
112
 
105
- Uses `DEFAULT_USER_USERNAME` and `DEFAULT_USER_PASSWORD` env variables.
113
+ Same as the roles `"admin"` and `"user"` (`AUTH_ADMIN_*`, `AUTH_USER_*`; older `DEFAULT_ADMIN_*`, `DEFAULT_USER_*`, `NON_ADMIN_*` also work).
106
114
 
107
115
  ### Set Bearer Token
108
116
 
@@ -210,7 +218,7 @@ And I store the value at "items[0].id" as "firstItemId"
210
218
  Feature: User Management API
211
219
 
212
220
  Background:
213
- Given I am authenticated as an admin via API
221
+ Given I am authenticated as "admin" via API
214
222
 
215
223
  Scenario: Full CRUD lifecycle
216
224
  # Create
@@ -250,7 +250,7 @@ type World = {
250
250
  Feature: User Management
251
251
 
252
252
  Background:
253
- Given I am authenticated as an admin via API
253
+ Given I am authenticated as "admin" via API
254
254
  Given I generate a UUID and store as "runId"
255
255
 
256
256
  Scenario: Create user with cleanup
@@ -298,7 +298,7 @@ Feature: User Onboarding
298
298
  Given I set variable "userName" to "Test User {testId}"
299
299
 
300
300
  # API: Create user
301
- Given I am authenticated as an admin via API
301
+ Given I am authenticated as "admin" via API
302
302
  When I POST "/admin/users" with JSON body:
303
303
  """
304
304
  {