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