@esimplicitylabs/katalyst-xspec 0.7.0 → 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.
- package/README.md +15 -1
- package/cli/init.cjs +74 -22
- package/cli/upgrade.cjs +21 -10
- package/dist/{chunk-UTTKW2SR.js → chunk-VHVH476W.js} +39 -6
- package/dist/index.d.ts +160 -8
- package/dist/index.js +323 -71
- package/dist/steps/index.js +1 -1
- package/package.json +2 -2
- package/skills/katalyst-bdd-architecture/SKILL.md +23 -38
- package/skills/katalyst-bdd-architecture/references/adapters.md +44 -25
- package/skills/katalyst-bdd-architecture/references/ports.md +16 -2
- package/skills/katalyst-bdd-create-test/SKILL.md +15 -5
- package/skills/katalyst-bdd-create-test/references/api-patterns.md +25 -16
- package/skills/katalyst-bdd-create-test/references/hybrid-patterns.md +30 -13
- package/skills/katalyst-bdd-create-test/references/ui-patterns.md +11 -0
- package/skills/katalyst-bdd-quickstart/SKILL.md +21 -17
- package/skills/katalyst-bdd-step-reference/SKILL.md +7 -4
- package/skills/katalyst-bdd-step-reference/references/api-steps.md +14 -6
- package/skills/katalyst-bdd-step-reference/references/shared-steps.md +2 -2
- package/skills/katalyst-bdd-step-reference/references/ui-steps.md +22 -1
- package/skills/katalyst-bdd-troubleshooting/SKILL.md +46 -37
|
@@ -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
|
-
|
|
150
|
-
|
|
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
|
-
#
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
182
|
-
1. POSTs to `API_AUTH_LOGIN_PATH`
|
|
183
|
-
2.
|
|
184
|
-
3.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
226
|
-
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
|
126
|
-
|
|
|
127
|
-
| Both in one scenario |
|
|
128
|
-
|
|
|
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
|
|
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
|
|
44
|
-
| `Given I am authenticated as
|
|
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
|
|
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
|
|
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
|
-
###
|
|
91
|
+
### Log in as a role
|
|
92
92
|
|
|
93
93
|
```gherkin
|
|
94
|
-
Given I am authenticated as
|
|
94
|
+
Given I am authenticated as {string} via API
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
**Example:**
|
|
98
|
+
```gherkin
|
|
99
|
+
Given I am authenticated as "pm" via API
|
|
100
|
+
```
|
|
98
101
|
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
301
|
+
Given I am authenticated as "admin" via API
|
|
302
302
|
When I POST "/admin/users" with JSON body:
|
|
303
303
|
"""
|
|
304
304
|
{
|