norn-cli 1.10.5 → 1.11.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/.kanbn/index.md +30 -0
- package/.kanbn/tasks/book-first-mentor-session.md +13 -0
- package/.kanbn/tasks/decide-what-success-in-a-pilot-looks-like.md +9 -0
- package/.kanbn/tasks/do-5-customer-conversations.md +9 -0
- package/.kanbn/tasks/finalise-the-one-line-pitch.md +11 -0
- package/.kanbn/tasks/interview-script.md +49 -0
- package/.kanbn/tasks/make-a-list-of-10-people-to-speak-to.md +11 -0
- package/.kanbn/tasks/prepare-your-customer-interview-questions.md +11 -0
- package/.kanbn/tasks/recruit-2/342/200/2233-pilot-users.md +9 -0
- package/.kanbn/tasks/refine-your-pitch.md +9 -0
- package/.kanbn/tasks/write-down-repeated-wording.md +9 -0
- package/.kanbn/tasks/write-the-one-pager.md +27 -0
- package/CHANGELOG.md +34 -0
- package/README.md +75 -1132
- package/dist/cli.js +20855 -3573
- package/package.json +36 -1
- package/schemas/norn.mcp.schema.json +125 -0
- package/scripts/generate-coding-bed.mjs +243 -0
package/README.md
CHANGED
|
@@ -1,1182 +1,125 @@
|
|
|
1
|
-
# Norn
|
|
1
|
+
# Norn
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Norn is a REST client for VS Code that keeps ad hoc requests, reusable API flows, and test automation in the same files. Write a request once, turn it into a sequence, debug it in the editor, and run the same `.norn` files from the CLI in CI.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
5
|
+
### Simple API Requests
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+

|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
- **API Definitions**: Define reusable headers and endpoints in `.nornapi` files
|
|
12
|
-
- **Variables**: Define and reference variables with `var name = value` and `{{name}}`
|
|
13
|
-
- **Environments**: Manage dev/staging/prod configurations with `.nornenv` files
|
|
14
|
-
- **Sequences**: Chain multiple requests with response capture using `$N.path`
|
|
15
|
-
- **Test Sequences**: Mark sequences as tests with `test sequence` for CLI execution
|
|
16
|
-
- **Test Explorer**: Run tests from VS Code's Testing sidebar with colorful output
|
|
17
|
-
- **Swagger Coverage**: Track API coverage with status bar indicator and detailed panel
|
|
18
|
-
- **Parameterized Tests**: Data-driven testing with `@data` and `@theory` annotations
|
|
19
|
-
- **Sequence Tags**: Tag sequences with `@smoke`, `@team(CustomerExp)` for filtering in CI/CD
|
|
20
|
-
- **Secret Variables**: Mark sensitive environment variables with `secret` for automatic redaction
|
|
21
|
-
- **Assertions**: Validate responses with `assert` statements supporting comparison, type checking, and existence
|
|
22
|
-
- **Named Requests**: Define reusable requests with `[RequestName]` and call them with `run RequestName`
|
|
23
|
-
- **Conditionals**: Control flow with `if/end if` blocks based on response data
|
|
24
|
-
- **Wait Commands**: Add delays between requests with `wait 1s` or `wait 500ms`
|
|
25
|
-
- **Retry and Backoff**: Automatically retry failed requests with `retry 3 backoff 200 ms`
|
|
26
|
-
- **JSON File Loading**: Load test data from JSON files with `run readJson ./file.json`
|
|
27
|
-
- **Property Updates**: Modify loaded JSON data inline with `config.property = value`
|
|
28
|
-
- **Script Execution**: Run bash, PowerShell, or JavaScript scripts within sequences
|
|
29
|
-
- **Print Statements**: Debug and log messages during sequence execution
|
|
30
|
-
- **Cookie Support**: Automatic cookie jar with persistence across requests
|
|
31
|
-
- **Response Comparison**: Compare any two API responses side-by-side with VS Code's diff view
|
|
32
|
-
- **Clickable JSON**: Click any JSON value in the response panel to auto-generate assertions
|
|
33
|
-
- **Syntax Highlighting**: Full syntax highlighting for requests, headers, JSON bodies
|
|
34
|
-
- **IntelliSense**: Autocomplete for HTTP methods, headers, variables, keywords, and Swagger-based request body keys/templates for endpoint POST/PUT/PATCH calls
|
|
35
|
-
- **Diagnostics**: Error highlighting for undefined variables
|
|
36
|
-
- **CLI**: Run tests from terminal with JUnit/HTML reports for CI/CD automation
|
|
9
|
+
### Chain and Debug API Requests
|
|
37
10
|
|
|
38
|
-
|
|
11
|
+

|
|
39
12
|
|
|
40
|
-
|
|
41
|
-
2. Write your HTTP request
|
|
42
|
-
3. Click "Send Request" above the request line
|
|
13
|
+
## Why Norn
|
|
43
14
|
|
|
44
|
-
|
|
15
|
+
Most API tools split the work across too many places: one app for sending requests, another for test logic, shell scripts for CI, and a pile of copied values between them. Norn keeps that work in plain text files inside VS Code.
|
|
45
16
|
|
|
46
|
-
|
|
17
|
+
That means you can:
|
|
47
18
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
### Variables
|
|
54
|
-
|
|
55
|
-
Variables can hold literal strings or be assigned from expressions:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
# Literal string (quotes optional for simple values)
|
|
59
|
-
var baseUrl = https://api.example.com
|
|
60
|
-
var name = "John Doe"
|
|
61
|
-
|
|
62
|
-
# Use variables with {{}}
|
|
63
|
-
GET {{baseUrl}}/users
|
|
64
|
-
Authorization: Bearer {{token}}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
#### Variable Assignment in Sequences
|
|
68
|
-
|
|
69
|
-
Inside sequences, you can assign variables from expressions:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
sequence Example
|
|
73
|
-
var data = run readJson ./config.json
|
|
74
|
-
|
|
75
|
-
# Extract a value from JSON variable (evaluated)
|
|
76
|
-
var userId = data.users[0].id
|
|
77
|
-
|
|
78
|
-
# Literal string with interpolation
|
|
79
|
-
var message = "User ID is {{userId}}"
|
|
80
|
-
|
|
81
|
-
# Response capture
|
|
82
|
-
GET https://api.example.com/users/{{userId}}
|
|
83
|
-
var userName = $1.body.name
|
|
84
|
-
|
|
85
|
-
print "Result" | "{{message}}, Name: {{userName}}"
|
|
86
|
-
end sequence
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
| Syntax | Type | Example |
|
|
90
|
-
|--------|------|---------|
|
|
91
|
-
| `var x = "text"` | Literal string | `var name = "John"` |
|
|
92
|
-
| `var x = "Hi {{y}}"` | String with interpolation | `var msg = "Hello {{name}}"` |
|
|
93
|
-
| `var x = someVar.path` | Expression (evaluated) | `var id = data.users[0].id` |
|
|
94
|
-
| `var x = $1.body.id` | Response capture | `var token = $1.body.token` |
|
|
95
|
-
| `var x = run ...` | Script/JSON result | `var data = run readJson ./file.json` |
|
|
96
|
-
|
|
97
|
-
### Request with Body
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
POST https://api.example.com/users
|
|
101
|
-
Content-Type: application/json
|
|
102
|
-
{
|
|
103
|
-
"name": "John Doe",
|
|
104
|
-
"email": "john@example.com"
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
If the request uses an imported endpoint (`POST/PUT/PATCH EndpointName`) backed by a cached Swagger spec, typing `{` on the body line offers **Insert request body template** (required fields only). As you type JSON keys, IntelliSense suggests schema properties for that object.
|
|
109
|
-
|
|
110
|
-
### Sequences (Chained Requests)
|
|
111
|
-
|
|
112
|
-
Chain multiple requests and capture response data:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
sequence AuthFlow
|
|
116
|
-
POST https://api.example.com/login
|
|
117
|
-
Content-Type: application/json
|
|
118
|
-
|
|
119
|
-
{"username": "admin", "password": "secret"}
|
|
120
|
-
|
|
121
|
-
var token = $1.accessToken
|
|
122
|
-
|
|
123
|
-
GET https://api.example.com/profile
|
|
124
|
-
Authorization: Bearer {{token}}
|
|
125
|
-
end sequence
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Click "▶ Run Sequence" above the `sequence` line to execute all requests in order.
|
|
129
|
-
|
|
130
|
-
### Sequence Composition
|
|
131
|
-
|
|
132
|
-
Sequences can call other sequences, enabling modular test design and natural setup/teardown patterns:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Define reusable setup sequence
|
|
136
|
-
sequence Login
|
|
137
|
-
POST {{baseUrl}}/auth/login
|
|
138
|
-
Content-Type: application/json
|
|
139
|
-
{"username": "admin", "password": "secret"}
|
|
140
|
-
var token = $1.body.accessToken
|
|
141
|
-
print "Logged in" | "Token: {{token}}"
|
|
142
|
-
end sequence
|
|
143
|
-
|
|
144
|
-
# Define reusable teardown sequence
|
|
145
|
-
sequence Logout
|
|
146
|
-
POST {{baseUrl}}/auth/logout
|
|
147
|
-
Authorization: Bearer {{token}}
|
|
148
|
-
print "Logged out"
|
|
149
|
-
end sequence
|
|
150
|
-
|
|
151
|
-
# Main test sequence uses setup/teardown
|
|
152
|
-
sequence UserTests
|
|
153
|
-
# Run setup - token variable becomes available
|
|
154
|
-
run Login
|
|
155
|
-
|
|
156
|
-
# Run actual tests
|
|
157
|
-
GET {{baseUrl}}/users/me
|
|
158
|
-
Authorization: Bearer {{token}}
|
|
159
|
-
assert $1.status == 200
|
|
160
|
-
|
|
161
|
-
# Run teardown
|
|
162
|
-
run Logout
|
|
163
|
-
end sequence
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Key behaviors:**
|
|
167
|
-
- Variables set in called sequences are available to the caller (`token` from `Login`)
|
|
168
|
-
- Sequences can be nested to any depth
|
|
169
|
-
- Circular references are detected and reported as errors
|
|
170
|
-
- If a called sequence fails, the parent sequence stops
|
|
171
|
-
|
|
172
|
-
### Sequence Parameters
|
|
173
|
-
|
|
174
|
-
Sequences can accept parameters with optional default values:
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
# Required parameter
|
|
178
|
-
sequence Greet(name)
|
|
179
|
-
print "Hello, {{name}}!"
|
|
180
|
-
end sequence
|
|
181
|
-
|
|
182
|
-
# Optional parameter with default
|
|
183
|
-
sequence Login(username, password = "secret123")
|
|
184
|
-
POST {{baseUrl}}/auth/login
|
|
185
|
-
Content-Type: application/json
|
|
186
|
-
{"username": "{{username}}", "password": "{{password}}"}
|
|
187
|
-
|
|
188
|
-
var token = $1.body.accessToken
|
|
189
|
-
return token
|
|
190
|
-
end sequence
|
|
191
|
-
|
|
192
|
-
# Call with positional arguments
|
|
193
|
-
run Greet("World")
|
|
194
|
-
run Login("admin", "mypassword")
|
|
195
|
-
|
|
196
|
-
# Call with named arguments (any order)
|
|
197
|
-
run Login(password: "pass123", username: "admin")
|
|
198
|
-
|
|
199
|
-
# Use defaults - only provide required params
|
|
200
|
-
run Login("admin") # uses default password
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Rules:**
|
|
204
|
-
- Required parameters (no default) must come before optional parameters
|
|
205
|
-
- Positional arguments are bound in order
|
|
206
|
-
- Named arguments can be in any order
|
|
207
|
-
- Cannot mix positional arguments after named arguments
|
|
208
|
-
|
|
209
|
-
### Sequence Return Values
|
|
210
|
-
|
|
211
|
-
Sequences can return values that the caller can capture and use:
|
|
212
|
-
|
|
213
|
-
```bash
|
|
214
|
-
sequence FetchUser(userId)
|
|
215
|
-
GET {{baseUrl}}/users/{{userId}}
|
|
216
|
-
var name = $1.body.name
|
|
217
|
-
var email = $1.body.email
|
|
218
|
-
return name, email
|
|
219
|
-
end sequence
|
|
19
|
+
- send single HTTP requests without leaving the editor
|
|
20
|
+
- build reusable sequences with variables, captured values, assertions, waits, retries, and branching
|
|
21
|
+
- debug those sequences with breakpoints and step-through execution
|
|
22
|
+
- run the exact same files from the CLI for smoke tests, regression suites, and pipelines
|
|
220
23
|
|
|
221
|
-
|
|
222
|
-
# Capture return values
|
|
223
|
-
var user = run FetchUser("123")
|
|
224
|
-
|
|
225
|
-
# Access individual fields
|
|
226
|
-
print "User name" | "{{user.name}}"
|
|
227
|
-
print "User email" | "{{user.email}}"
|
|
228
|
-
|
|
229
|
-
# Use in requests
|
|
230
|
-
POST {{baseUrl}}/messages
|
|
231
|
-
Content-Type: application/json
|
|
232
|
-
{"to": "{{user.email}}", "subject": "Hello {{user.name}}"}
|
|
233
|
-
end sequence
|
|
234
|
-
```
|
|
24
|
+
## What You Get
|
|
235
25
|
|
|
236
|
-
|
|
26
|
+
- `.norn` files for requests, sequences, and tests
|
|
27
|
+
- `.nornenv` files for environments and secrets
|
|
28
|
+
- `.nornapi` files for reusable endpoint definitions
|
|
29
|
+
- syntax highlighting, IntelliSense, and diagnostics
|
|
30
|
+
- response inspection, JSON diffing, and click-to-generate assertions
|
|
31
|
+
- tagged and parameterized test execution in VS Code and the CLI
|
|
237
32
|
|
|
238
|
-
|
|
33
|
+
## Example
|
|
239
34
|
|
|
240
|
-
|
|
35
|
+
```norn
|
|
36
|
+
var baseUrl = https://api.example.com
|
|
241
37
|
|
|
242
|
-
|
|
243
|
-
# Simple tags
|
|
244
|
-
@smoke @regression
|
|
245
|
-
sequence AuthFlow
|
|
38
|
+
sequence Checkout
|
|
246
39
|
POST {{baseUrl}}/auth/login
|
|
247
40
|
Content-Type: application/json
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
# Key-value tags for categorization
|
|
253
|
-
@team(CustomerExp)
|
|
254
|
-
@priority(high)
|
|
255
|
-
@jira(NORN-123)
|
|
256
|
-
sequence CheckoutFlow
|
|
257
|
-
GET {{baseUrl}}/checkout
|
|
258
|
-
assert $1.status == 200
|
|
259
|
-
end sequence
|
|
260
|
-
|
|
261
|
-
# Mixed tags on multiple lines
|
|
262
|
-
@smoke
|
|
263
|
-
@team(Platform)
|
|
264
|
-
@feature(payments)
|
|
265
|
-
sequence PaymentValidation
|
|
266
|
-
POST {{baseUrl}}/payments
|
|
267
|
-
assert $1.status == 201
|
|
268
|
-
end sequence
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
**Tag Syntax:**
|
|
272
|
-
- Simple tags: `@smoke`, `@regression`, `@wip`, `@slow`
|
|
273
|
-
- Key-value tags: `@team(CustomerExp)`, `@priority(high)`, `@jira(NORN-123)`
|
|
274
|
-
- Multiple tags can be on the same line or separate lines
|
|
275
|
-
- Tag names follow the pattern: `[a-zA-Z_][a-zA-Z0-9_-]*`
|
|
276
|
-
- Tag matching is case-insensitive
|
|
277
|
-
|
|
278
|
-
**CLI Filtering:**
|
|
279
|
-
|
|
280
|
-
```bash
|
|
281
|
-
# Run sequences tagged @smoke
|
|
282
|
-
norn tests/ --tag smoke
|
|
283
|
-
|
|
284
|
-
# AND logic: must have BOTH tags
|
|
285
|
-
norn tests/ --tag smoke --tag auth
|
|
286
|
-
|
|
287
|
-
# OR logic: match ANY tag
|
|
288
|
-
norn tests/ --tags smoke,regression
|
|
289
|
-
|
|
290
|
-
# Key-value exact match
|
|
291
|
-
norn tests/ --tag team(CustomerExp)
|
|
292
|
-
|
|
293
|
-
# Combine with environment
|
|
294
|
-
norn tests/ --env staging --tag smoke
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**Behavior:**
|
|
298
|
-
- When a sequence calls `run OtherSequence` and tag filtering is active, non-matching sequences are silently skipped
|
|
299
|
-
- Untagged sequences run when no tag filter is specified
|
|
300
|
-
- Tags can only be applied to sequences (diagnostics warn on misplaced tags)
|
|
301
|
-
|
|
302
|
-
### Imports
|
|
303
|
-
|
|
304
|
-
Organize your tests by importing named requests and sequences from other files:
|
|
305
|
-
|
|
306
|
-
```bash
|
|
307
|
-
# common.norn - shared utilities
|
|
308
|
-
var baseUrl = https://api.example.com
|
|
309
|
-
|
|
310
|
-
[AuthRequest]
|
|
311
|
-
POST {{baseUrl}}/auth/login
|
|
312
|
-
Content-Type: application/json
|
|
313
|
-
{"username": "admin", "password": "secret"}
|
|
41
|
+
{
|
|
42
|
+
"username": "demo",
|
|
43
|
+
"password": "secret"
|
|
44
|
+
}
|
|
314
45
|
|
|
315
|
-
sequence SharedSetup
|
|
316
|
-
run AuthRequest
|
|
317
46
|
var token = $1.body.accessToken
|
|
318
|
-
end sequence
|
|
319
|
-
```
|
|
320
47
|
|
|
321
|
-
|
|
322
|
-
# main-test.norn - imports shared utilities
|
|
323
|
-
import "./common.norn"
|
|
324
|
-
|
|
325
|
-
sequence MyTests
|
|
326
|
-
# Use imported sequence
|
|
327
|
-
run SharedSetup
|
|
328
|
-
|
|
329
|
-
# Use imported request
|
|
330
|
-
run AuthRequest
|
|
331
|
-
|
|
332
|
-
# Token from SharedSetup is available
|
|
333
|
-
GET {{baseUrl}}/users/me
|
|
48
|
+
GET {{baseUrl}}/orders
|
|
334
49
|
Authorization: Bearer {{token}}
|
|
335
|
-
assert $1.status == 200
|
|
336
|
-
end sequence
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
**Key behaviors:**
|
|
340
|
-
- Import paths are relative to the current file
|
|
341
|
-
- Nested imports are supported (imported files can import other files)
|
|
342
|
-
- Circular imports are detected and reported as errors
|
|
343
|
-
- Only named requests `[Name]` and sequences are imported
|
|
344
|
-
- Variables are resolved at import time (baked in), not exported
|
|
345
|
-
- `.nornenv` resolution is file-aware: Norn walks up from the running file and uses the closest `.nornenv`
|
|
346
|
-
|
|
347
|
-
### API Definition Files (.nornapi)
|
|
348
|
-
|
|
349
|
-
Define reusable API configurations with header groups and named endpoints in `.nornapi` files.
|
|
350
|
-
|
|
351
|
-
#### Header Groups
|
|
352
|
-
|
|
353
|
-
Header groups define reusable sets of HTTP headers:
|
|
354
|
-
|
|
355
|
-
```bash
|
|
356
|
-
# api.nornapi
|
|
357
|
-
|
|
358
|
-
headers Json
|
|
359
|
-
Content-Type: application/json
|
|
360
|
-
Accept: application/json
|
|
361
|
-
end headers
|
|
362
|
-
|
|
363
|
-
headers Auth
|
|
364
|
-
Authorization: Bearer {{token}}
|
|
365
|
-
X-API-Key: {{apiKey}}
|
|
366
|
-
end headers
|
|
367
|
-
|
|
368
|
-
headers Form
|
|
369
|
-
Content-Type: application/x-www-form-urlencoded
|
|
370
|
-
end headers
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
- Header values can include `{{variables}}` that are resolved at runtime
|
|
374
|
-
- Multiple header groups can be applied to a single request
|
|
375
|
-
|
|
376
|
-
#### Named Endpoints
|
|
377
|
-
|
|
378
|
-
Define named endpoints with path parameters using `{param}` syntax:
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
endpoints
|
|
382
|
-
# Basic CRUD operations
|
|
383
|
-
GetUser: GET {{baseUrl}}/users/{id}
|
|
384
|
-
GetAllUsers: GET {{baseUrl}}/users
|
|
385
|
-
CreateUser: POST {{baseUrl}}/users
|
|
386
|
-
UpdateUser: PUT {{baseUrl}}/users/{id}
|
|
387
|
-
DeleteUser: DELETE {{baseUrl}}/users/{id}
|
|
388
|
-
|
|
389
|
-
# Multiple path parameters
|
|
390
|
-
GetUserPosts: GET {{baseUrl}}/users/{userId}/posts
|
|
391
|
-
GetUserPost: GET {{baseUrl}}/users/{userId}/posts/{postId}
|
|
392
|
-
|
|
393
|
-
# All HTTP methods supported
|
|
394
|
-
CheckHealth: HEAD {{baseUrl}}/health
|
|
395
|
-
GetOptions: OPTIONS {{baseUrl}}/api
|
|
396
|
-
end endpoints
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
- Endpoint names are case-sensitive (e.g., `GetUser`, `CreateUser`)
|
|
400
|
-
- Path parameters use single braces: `{id}`, `{userId}`
|
|
401
|
-
- Environment variables use double braces: `{{baseUrl}}`
|
|
402
|
-
|
|
403
|
-
#### Swagger/OpenAPI Import
|
|
404
|
-
|
|
405
|
-
Import endpoints directly from OpenAPI/Swagger specifications:
|
|
406
|
-
|
|
407
|
-
```bash
|
|
408
|
-
# api.nornapi
|
|
409
|
-
|
|
410
|
-
swagger https://petstore.swagger.io/v2/swagger.json
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
Click "Import Swagger" CodeLens above the statement to fetch and generate endpoint definitions from the spec.
|
|
414
|
-
|
|
415
|
-
#### Using .nornapi in .norn Files
|
|
416
|
-
|
|
417
|
-
Import your API definitions and use them in requests:
|
|
418
|
-
|
|
419
|
-
```bash
|
|
420
|
-
import "./api.nornapi"
|
|
421
|
-
|
|
422
|
-
# Standalone request with endpoint and header group
|
|
423
|
-
GET GetUser(1) Json
|
|
424
|
-
|
|
425
|
-
# Inside a sequence
|
|
426
|
-
sequence UserTests
|
|
427
|
-
# Basic endpoint call with parameter
|
|
428
|
-
GET GetTodo(1) Json
|
|
429
|
-
assert $1.status == 200
|
|
430
|
-
|
|
431
|
-
# Multiple header groups (space-separated)
|
|
432
|
-
GET GetUser(1) Json Auth
|
|
433
|
-
|
|
434
|
-
# Header groups on separate lines
|
|
435
|
-
POST CreateUser
|
|
436
|
-
Json
|
|
437
|
-
Auth
|
|
438
|
-
{"name": "John", "email": "john@example.com"}
|
|
439
|
-
|
|
440
|
-
# Capture response to variable
|
|
441
|
-
var user = GET GetUser(1) Json
|
|
442
|
-
print "User" | "{{user.body.name}}"
|
|
443
|
-
|
|
444
|
-
# Use variables in endpoint parameters
|
|
445
|
-
var userId = 5
|
|
446
|
-
GET GetUser({{userId}}) Json
|
|
447
|
-
|
|
448
|
-
# Quoted string parameters
|
|
449
|
-
GET GetUser("123") Json
|
|
450
|
-
end sequence
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
#### Endpoint Syntax Reference
|
|
454
|
-
|
|
455
|
-
| Syntax | Description |
|
|
456
|
-
|--------|-------------|
|
|
457
|
-
| `GET EndpointName(param)` | Call endpoint with positional parameter |
|
|
458
|
-
| `GET EndpointName(param1, param2)` | Multiple positional parameters |
|
|
459
|
-
| `GET EndpointName(id: "123")` | Named parameter |
|
|
460
|
-
| `GET EndpointName(1) Json` | With header group |
|
|
461
|
-
| `GET EndpointName(1) Json Auth` | Multiple header groups |
|
|
462
|
-
| `var x = GET EndpointName(1) Json` | Capture response to variable |
|
|
463
|
-
|
|
464
|
-
### Assertions
|
|
465
|
-
|
|
466
|
-
Validate response data with powerful assertion operators:
|
|
467
|
-
|
|
468
|
-
```bash
|
|
469
|
-
sequence ValidateAPI
|
|
470
|
-
GET https://api.example.com/users/1
|
|
471
|
-
|
|
472
|
-
# Status and comparison assertions
|
|
473
|
-
assert $1.status == 200
|
|
474
|
-
assert $1.status < 300
|
|
475
|
-
|
|
476
|
-
# Body value assertions
|
|
477
|
-
assert $1.body.id == 1
|
|
478
|
-
assert $1.body.name == "John"
|
|
479
|
-
assert $1.body.age >= 18
|
|
480
|
-
|
|
481
|
-
# String assertions
|
|
482
|
-
assert $1.body.email contains "@"
|
|
483
|
-
assert $1.body.name startsWith "J"
|
|
484
|
-
assert $1.body.name endsWith "ohn"
|
|
485
|
-
assert $1.body.email matches "[a-z]+@[a-z]+\.[a-z]+"
|
|
486
|
-
|
|
487
|
-
# Timing assertions
|
|
488
|
-
assert $1.duration < 5000
|
|
489
|
-
|
|
490
|
-
# Existence checks
|
|
491
|
-
assert $1.body.id exists
|
|
492
|
-
assert $1.body.deletedAt !exists
|
|
493
|
-
|
|
494
|
-
# Type assertions
|
|
495
|
-
assert $1.body.id isType number
|
|
496
|
-
assert $1.body.name isType string
|
|
497
|
-
assert $1.body.active isType boolean
|
|
498
|
-
assert $1.body.tags isType array
|
|
499
|
-
assert $1.body.address isType object
|
|
500
|
-
|
|
501
|
-
# Header assertions
|
|
502
|
-
assert $1.headers.Content-Type contains "application/json"
|
|
503
|
-
|
|
504
|
-
# Custom failure messages
|
|
505
|
-
assert $1.status == 200 | "API should return success status"
|
|
506
|
-
end sequence
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
### Environments
|
|
510
|
-
|
|
511
|
-
Create a `.nornenv` file to manage environment-specific variables.
|
|
512
|
-
Norn resolves the closest `.nornenv` by walking up from the file being run (or edited):
|
|
513
|
-
|
|
514
|
-
```bash
|
|
515
|
-
# .nornenv file
|
|
516
|
-
# Common variables (always available)
|
|
517
|
-
var timeout = 30000
|
|
518
|
-
var version = v1
|
|
519
|
-
|
|
520
|
-
[env:dev]
|
|
521
|
-
var baseUrl = https://dev-api.example.com
|
|
522
|
-
var apiKey = dev-key-123
|
|
523
|
-
|
|
524
|
-
[env:staging]
|
|
525
|
-
var baseUrl = https://staging-api.example.com
|
|
526
|
-
var apiKey = staging-key-456
|
|
527
|
-
|
|
528
|
-
[env:prod]
|
|
529
|
-
var baseUrl = https://api.example.com
|
|
530
|
-
var apiKey = prod-key-789
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
Select the active environment from the VS Code status bar. Environment variables override common variables.
|
|
534
|
-
|
|
535
|
-
#### Encrypted Secrets in `.nornenv`
|
|
536
|
-
|
|
537
|
-
Use `secret` declarations with encrypted values so `.nornenv` can be committed safely:
|
|
538
|
-
|
|
539
|
-
```nornenv
|
|
540
|
-
[env:prelive]
|
|
541
|
-
secret apiKey = ENC[NORN_AGE_V1:kid=team-main:...]
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
Key flow:
|
|
545
|
-
|
|
546
|
-
```bash
|
|
547
|
-
# Generate and cache a shared key once
|
|
548
|
-
norn secrets keygen --name team-main
|
|
549
|
-
|
|
550
|
-
# Teammates import the shared key from your vault
|
|
551
|
-
norn secrets import-key --kid team-main
|
|
552
|
-
|
|
553
|
-
# Encrypt plaintext secrets already in .nornenv
|
|
554
|
-
norn secrets encrypt --file .nornenv --env prelive --var apiKey --kid team-main
|
|
555
|
-
|
|
556
|
-
# Rotate an existing encrypted value
|
|
557
|
-
norn secrets rotate --file .nornenv --env prelive --var apiKey
|
|
558
|
-
|
|
559
|
-
# CI guardrail: fail on plaintext secrets
|
|
560
|
-
norn secrets audit .
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
When Norn detects a locked secret with a missing key id (`kid`), it prompts once, then stores the key in `.norn-cache/secret-keys.json` (gitignored).
|
|
564
|
-
|
|
565
|
-
### Named Requests
|
|
566
|
-
|
|
567
|
-
Define reusable requests and call them from sequences:
|
|
568
|
-
|
|
569
|
-
```bash
|
|
570
|
-
# Define a reusable login request
|
|
571
|
-
[Login]
|
|
572
|
-
POST {{baseUrl}}/auth/login
|
|
573
|
-
Content-Type: application/json
|
|
574
|
-
{"username": "admin", "password": "secret"}
|
|
575
|
-
|
|
576
|
-
# Define a profile request
|
|
577
|
-
[GetProfile]
|
|
578
|
-
GET {{baseUrl}}/users/me
|
|
579
|
-
Authorization: Bearer {{token}}
|
|
580
|
-
|
|
581
|
-
###
|
|
582
|
-
|
|
583
|
-
sequence AuthFlow
|
|
584
|
-
# Run the named request
|
|
585
|
-
run Login
|
|
586
|
-
var token = $1.body.accessToken
|
|
587
|
-
|
|
588
|
-
# Run another named request
|
|
589
|
-
run GetProfile
|
|
590
|
-
print "Profile" | "Welcome, {{$2.body.name}}!"
|
|
591
|
-
end sequence
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
### Conditionals (if/end if)
|
|
595
|
-
|
|
596
|
-
Control execution flow based on response data:
|
|
597
|
-
|
|
598
|
-
```bash
|
|
599
|
-
sequence ConditionalFlow
|
|
600
|
-
GET https://api.example.com/users/1
|
|
601
|
-
|
|
602
|
-
# Execute block only if condition is true
|
|
603
|
-
if $1.status == 200
|
|
604
|
-
print "Success" | "User found!"
|
|
605
|
-
|
|
606
|
-
GET https://api.example.com/users/1/orders
|
|
607
|
-
assert $2.status == 200
|
|
608
|
-
end if
|
|
609
|
-
|
|
610
|
-
# Check for errors
|
|
611
|
-
if $1.status == 404
|
|
612
|
-
print "Error" | "User not found"
|
|
613
|
-
end if
|
|
614
|
-
|
|
615
|
-
# Conditions support all assertion operators
|
|
616
|
-
if $1.body.role == "admin"
|
|
617
|
-
print "Admin" | "User has admin privileges"
|
|
618
|
-
end if
|
|
619
|
-
end sequence
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
### Wait Commands
|
|
623
|
-
|
|
624
|
-
Add delays between requests (useful for rate limiting or async operations):
|
|
625
|
-
|
|
626
|
-
```bash
|
|
627
|
-
sequence RateLimitedFlow
|
|
628
|
-
POST https://api.example.com/jobs
|
|
629
|
-
var jobId = $1.body.id
|
|
630
|
-
|
|
631
|
-
print "Waiting" | "Job submitted, waiting for completion..."
|
|
632
|
-
|
|
633
|
-
# Wait 2 seconds
|
|
634
|
-
wait 2s
|
|
635
|
-
|
|
636
|
-
GET https://api.example.com/jobs/{{jobId}}
|
|
637
|
-
|
|
638
|
-
# Wait 500 milliseconds
|
|
639
|
-
wait 500ms
|
|
640
|
-
|
|
641
|
-
GET https://api.example.com/jobs/{{jobId}}/result
|
|
642
|
-
end sequence
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
### Retry and Backoff
|
|
646
|
-
|
|
647
|
-
Automatically retry failed HTTP requests with configurable backoff:
|
|
648
|
-
|
|
649
|
-
```bash
|
|
650
|
-
sequence RetryExample
|
|
651
|
-
# Retry up to 3 times with 200ms linear backoff (200ms, 400ms, 600ms)
|
|
652
|
-
var result = GET "https://api.example.com/flaky-endpoint" retry 3 backoff 200 ms
|
|
653
|
-
|
|
654
|
-
# Works with named endpoints from .nornapi files
|
|
655
|
-
var user = GET UserEndpoint retry 2 backoff 500 ms
|
|
656
|
-
|
|
657
|
-
# Works with named requests
|
|
658
|
-
var data = run FlakyRequest retry 3 backoff 100 ms
|
|
659
|
-
|
|
660
|
-
# Retry only (uses default 1 second backoff)
|
|
661
|
-
var simple = GET "https://api.example.com/status" retry 2
|
|
662
|
-
end sequence
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
**Retries trigger on:**
|
|
666
|
-
- 5xx server errors (500, 502, 503, etc.)
|
|
667
|
-
- 429 rate limiting
|
|
668
|
-
- Network failures and timeouts
|
|
669
|
-
|
|
670
|
-
**Time units:**
|
|
671
|
-
- `ms` or `milliseconds` - e.g., `backoff 200 ms`
|
|
672
|
-
- `s` or `seconds` - e.g., `backoff 2 s`
|
|
673
50
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
### JSON File Loading
|
|
677
|
-
|
|
678
|
-
Load test data from external JSON files:
|
|
679
|
-
|
|
680
|
-
```bash
|
|
681
|
-
sequence DataDrivenTest
|
|
682
|
-
# Load JSON configuration
|
|
683
|
-
var config = run readJson ./test-config.json
|
|
684
|
-
|
|
685
|
-
# Access properties
|
|
686
|
-
print "Config" | "Using API: {{config.baseUrl}}"
|
|
687
|
-
|
|
688
|
-
# Use in requests
|
|
689
|
-
GET {{config.baseUrl}}/users/{{config.testUser.id}}
|
|
690
|
-
|
|
691
|
-
# Access nested values and arrays
|
|
692
|
-
print "First Role" | "{{config.testUser.roles[0]}}"
|
|
693
|
-
|
|
694
|
-
# Modify loaded data inline
|
|
695
|
-
config.baseUrl = https://api.updated.com
|
|
696
|
-
config.testUser.name = Updated Name
|
|
697
|
-
|
|
698
|
-
print "Updated" | "New URL: {{config.baseUrl}}"
|
|
699
|
-
end sequence
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
Example `test-config.json`:
|
|
703
|
-
```json
|
|
704
|
-
{
|
|
705
|
-
"baseUrl": "https://api.example.com",
|
|
706
|
-
"testUser": {
|
|
707
|
-
"id": 1,
|
|
708
|
-
"name": "Test User",
|
|
709
|
-
"roles": ["admin", "user"]
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
### Script Execution
|
|
715
|
-
|
|
716
|
-
Run scripts within sequences for setup, data generation, or validation:
|
|
717
|
-
|
|
718
|
-
```bash
|
|
719
|
-
sequence TestWithScripts
|
|
720
|
-
# Run a setup script
|
|
721
|
-
run bash ./scripts/seed-db.sh
|
|
722
|
-
|
|
723
|
-
# Generate a signature and capture output
|
|
724
|
-
var signature = run js ./scripts/sign.js {{payload}}
|
|
725
|
-
|
|
726
|
-
# Use the signature in a request
|
|
727
|
-
POST https://api.example.com/verify
|
|
728
|
-
X-Signature: {{signature}}
|
|
729
|
-
end sequence
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
Scripts receive variables as environment variables with `NORN_` prefix (e.g., `NORN_TOKEN`).
|
|
733
|
-
|
|
734
|
-
#### Capturing Structured Data from Scripts
|
|
735
|
-
|
|
736
|
-
When scripts output JSON, you can access properties directly:
|
|
737
|
-
|
|
738
|
-
```bash
|
|
739
|
-
sequence DatabaseQuery
|
|
740
|
-
# PowerShell script that outputs JSON
|
|
741
|
-
var dbResult = run powershell ./scripts/query-db.ps1
|
|
742
|
-
|
|
743
|
-
# Access properties from the JSON output
|
|
744
|
-
print "User Found" | "ID: {{dbResult.id}}, Name: {{dbResult.name}}"
|
|
745
|
-
|
|
746
|
-
# Use in requests
|
|
747
|
-
GET https://api.example.com/users/{{dbResult.id}}
|
|
748
|
-
end sequence
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
**Automatic Format Detection**: Norn automatically parses multiple output formats:
|
|
752
|
-
|
|
753
|
-
| Format | Example | Access |
|
|
754
|
-
|--------|---------|--------|
|
|
755
|
-
| JSON | `{"Id": 123, "Name": "John"}` | `{{result.Id}}` |
|
|
756
|
-
| PowerShell Table | `Id Name`<br>`-- ----`<br>`123 John` | `{{result.Id}}` |
|
|
757
|
-
| PowerShell List | `Id : 123`<br>`Name : John` | `{{result.Id}}` |
|
|
758
|
-
|
|
759
|
-
Norn also strips ANSI color codes automatically, so formatted terminal output won't corrupt your data.
|
|
760
|
-
|
|
761
|
-
### Print Statements
|
|
762
|
-
|
|
763
|
-
Add debug output to your sequences:
|
|
764
|
-
|
|
765
|
-
```bash
|
|
766
|
-
sequence DebugFlow
|
|
767
|
-
print "Starting authentication..."
|
|
768
|
-
|
|
769
|
-
POST https://api.example.com/login
|
|
770
|
-
Content-Type: application/json
|
|
771
|
-
{"user": "admin"}
|
|
772
|
-
|
|
773
|
-
var token = $1.token
|
|
774
|
-
print "Token received" | "Value: {{token}}"
|
|
51
|
+
assert $2.status == 200
|
|
775
52
|
end sequence
|
|
776
53
|
```
|
|
777
54
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
### Response Panel Tools
|
|
781
|
-
|
|
782
|
-
The response panel provides interactive tools that accelerate test development:
|
|
783
|
-
|
|
784
|
-
#### Response Comparison
|
|
785
|
-
|
|
786
|
-
Compare any two API responses side-by-side using VS Code's built-in diff view:
|
|
787
|
-
|
|
788
|
-
1. Click **⇄ Compare** on the first response body
|
|
789
|
-
2. Click **⇄ Compare** on the second response
|
|
790
|
-
3. VS Code opens a diff view showing differences between the two responses
|
|
55
|
+
This is the core idea: one file can hold the request, the flow, the captured data, and the assertion. When the flow grows, you still stay in text, version control, and normal code review.
|
|
791
56
|
|
|
792
|
-
|
|
57
|
+
## In VS Code
|
|
793
58
|
|
|
794
|
-
|
|
59
|
+
Use Norn to:
|
|
795
60
|
|
|
796
|
-
|
|
61
|
+
- send a request directly from a `.norn` file
|
|
62
|
+
- run a whole sequence from the editor
|
|
63
|
+
- debug a sequence with breakpoints
|
|
64
|
+
- run test sequences from the Testing view
|
|
797
65
|
|
|
798
|
-
|
|
799
|
-
2. Hover over any value (string, number, boolean, null) - it highlights with a **+** badge
|
|
800
|
-
3. Click to automatically insert an assertion at the end of your sequence
|
|
66
|
+
## In The CLI
|
|
801
67
|
|
|
802
|
-
|
|
803
|
-
```bash
|
|
804
|
-
assert $response.body.status == "active"
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
Works with nested paths and arrays:
|
|
808
|
-
- `body.user.email` → `assert $user.body.user.email == "john@example.com"`
|
|
809
|
-
- `body.items[0].id` → `assert $order.body.items[0].id == 123`
|
|
810
|
-
|
|
811
|
-
## CLI Usage
|
|
812
|
-
|
|
813
|
-
Run tests from the command line for CI/CD pipelines. Only sequences marked with `test` are executed.
|
|
68
|
+
The CLI uses the same execution model as the extension, so local runs and CI runs stay aligned.
|
|
814
69
|
|
|
815
70
|
```bash
|
|
816
|
-
|
|
817
|
-
norn
|
|
818
|
-
|
|
819
|
-
# Run all test sequences in a directory (recursive)
|
|
820
|
-
norn tests/
|
|
821
|
-
|
|
822
|
-
# Run a specific sequence
|
|
823
|
-
norn api-tests.norn --sequence AuthFlow
|
|
824
|
-
|
|
825
|
-
# Run with a specific environment
|
|
826
|
-
norn api-tests.norn --env staging
|
|
827
|
-
|
|
828
|
-
# Run against local/self-signed TLS endpoints (dev only)
|
|
829
|
-
norn api-tests.norn --insecure
|
|
830
|
-
|
|
831
|
-
# Generate JUnit XML report for CI/CD
|
|
832
|
-
norn tests/ --junit --output-dir ./reports
|
|
833
|
-
|
|
834
|
-
# Generate HTML report
|
|
835
|
-
norn tests/ --html --output-dir ./reports
|
|
836
|
-
|
|
837
|
-
# Verbose output with colors
|
|
838
|
-
norn api-tests.norn -v
|
|
839
|
-
|
|
840
|
-
# Show help
|
|
841
|
-
norn --help
|
|
71
|
+
npm install -g norn-cli
|
|
72
|
+
norn ./tests/smoke.norn -e dev
|
|
842
73
|
```
|
|
843
74
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
| Option | Description |
|
|
847
|
-
|--------|-------------|
|
|
848
|
-
| `-s, --sequence <name>` | Run a specific sequence by name |
|
|
849
|
-
| `-e, --env <name>` | Use a specific environment from .nornenv |
|
|
850
|
-
| `--insecure` | Disable TLS certificate verification (dev/self-signed endpoints only) |
|
|
851
|
-
| `--tag <name>` | Filter by tag (AND logic, can repeat) |
|
|
852
|
-
| `--tags <list>` | Filter by comma-separated tags (OR logic) |
|
|
853
|
-
| `-j, --json` | Output results as JSON |
|
|
854
|
-
| `--junit` | Generate JUnit XML report |
|
|
855
|
-
| `--html` | Generate HTML report |
|
|
856
|
-
| `--output-dir <path>` | Save reports to directory (auto-timestamps filenames) |
|
|
857
|
-
| `-v, --verbose` | Show detailed output with colors |
|
|
858
|
-
| `--no-fail` | Don't exit with error code on failed tests |
|
|
859
|
-
| `-h, --help` | Show help message |
|
|
860
|
-
|
|
861
|
-
Security note: `--insecure` should only be used for local development or trusted internal test environments. Keep TLS verification enabled for staging/production endpoints.
|
|
862
|
-
|
|
863
|
-
## Test Explorer
|
|
864
|
-
|
|
865
|
-
Run tests directly from VS Code's Testing sidebar:
|
|
866
|
-
|
|
867
|
-
- **Automatic Discovery**: Test sequences appear in the Testing view
|
|
868
|
-
- **Tag Grouping**: Tests organized by tags (`@smoke`, `@regression`, etc.)
|
|
869
|
-
- **Colorful Output**: ANSI-colored results with icons and status codes
|
|
870
|
-
- **Persistent Output**: Select a test to see its full output anytime
|
|
871
|
-
- **Failure Details**: Expected vs actual diffs, request/response info
|
|
872
|
-
|
|
873
|
-
### Debugging `.norn` Sequences (VS Code)
|
|
874
|
-
|
|
875
|
-
Norn supports native VS Code debugging for `.norn` sequences:
|
|
75
|
+
## Deterministic MCP Tools
|
|
876
76
|
|
|
877
|
-
|
|
878
|
-
- Continue, pause, step over, step into sequence calls, and step out
|
|
879
|
-
- Default `stopOnEntry: true`
|
|
880
|
-
- Variables pane scopes:
|
|
881
|
-
- Runtime Variables
|
|
882
|
-
- Responses (`$1`, `$2`, ...)
|
|
883
|
-
- Launch entry points:
|
|
884
|
-
- `🐞 Debug Sequence` CodeLens
|
|
885
|
-
- Testing sidebar `Debug Tests` profile (single target per debug session)
|
|
77
|
+
Norn can call MCP tools from sequences without leaving the `.norn` runtime. MCP sessions are deterministic and shared across the full sequence run, so nested sequences reuse the same connection for the same resolved server alias.
|
|
886
78
|
|
|
887
|
-
|
|
79
|
+
Create a `norn.mcp.json` in the root of your project:
|
|
888
80
|
|
|
889
81
|
```json
|
|
890
82
|
{
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
83
|
+
"version": 1,
|
|
84
|
+
"servers": {
|
|
85
|
+
"localTools": {
|
|
86
|
+
"transport": "stdio",
|
|
87
|
+
"command": ["node", "./tools/mcp-server.js"]
|
|
88
|
+
},
|
|
89
|
+
"remoteTools": {
|
|
90
|
+
"transport": "http",
|
|
91
|
+
"url": "https://mcp.example.com/mcp",
|
|
92
|
+
"headers": {
|
|
93
|
+
"Authorization": "Bearer {{$env.mcpToken}}"
|
|
94
|
+
},
|
|
95
|
+
"timeoutMs": 5000
|
|
96
|
+
}
|
|
901
97
|
}
|
|
902
|
-
]
|
|
903
98
|
}
|
|
904
99
|
```
|
|
905
100
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
### Parameterized Tests
|
|
909
|
-
|
|
910
|
-
Use `@data` for data-driven testing - each data row becomes a separate test:
|
|
911
|
-
|
|
912
|
-
```bash
|
|
913
|
-
# Single parameter - runs 3 times with id = 1, 2, 3
|
|
914
|
-
@data(1, 2, 3)
|
|
915
|
-
test sequence TodoTest(id)
|
|
916
|
-
GET {{baseUrl}}/todos/{{id}}
|
|
917
|
-
assert $1.status == 200
|
|
918
|
-
assert $1.body.id == {{id}}
|
|
919
|
-
end sequence
|
|
920
|
-
|
|
921
|
-
# Multiple parameters - runs 2 times
|
|
922
|
-
@data(1, "delectus aut autem")
|
|
923
|
-
@data(2, "quis ut nam facilis")
|
|
924
|
-
test sequence TodoTitleTest(id, expectedTitle)
|
|
925
|
-
GET {{baseUrl}}/todos/{{id}}
|
|
926
|
-
assert $1.status == 200
|
|
927
|
-
assert $1.body.title == "{{expectedTitle}}"
|
|
928
|
-
end sequence
|
|
929
|
-
|
|
930
|
-
# Typed values (numbers, booleans, strings)
|
|
931
|
-
@data(1, true, "active")
|
|
932
|
-
@data(2, false, "inactive")
|
|
933
|
-
test sequence UserStatusTest(userId, isActive, status)
|
|
934
|
-
GET {{baseUrl}}/users/{{userId}}
|
|
935
|
-
assert $1.status == 200
|
|
936
|
-
end sequence
|
|
937
|
-
```
|
|
938
|
-
|
|
939
|
-
Use `@theory` for external data files:
|
|
940
|
-
|
|
941
|
-
```bash
|
|
942
|
-
@theory("./testdata.json")
|
|
943
|
-
test sequence DataFileTest(id, name)
|
|
944
|
-
GET {{baseUrl}}/items/{{id}}
|
|
945
|
-
assert $1.body.name == "{{name}}"
|
|
946
|
-
end sequence
|
|
947
|
-
```
|
|
948
|
-
|
|
949
|
-
Where `testdata.json` contains:
|
|
950
|
-
```json
|
|
951
|
-
[
|
|
952
|
-
{"id": 1, "name": "Widget"},
|
|
953
|
-
{"id": 2, "name": "Gadget"}
|
|
954
|
-
]
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
### CI/CD Example (GitHub Actions)
|
|
101
|
+
Use MCP tools directly inside sequences:
|
|
958
102
|
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
steps:
|
|
964
|
-
- uses: actions/checkout@v4
|
|
965
|
-
|
|
966
|
-
- name: Setup Node.js
|
|
967
|
-
uses: actions/setup-node@v4
|
|
968
|
-
with:
|
|
969
|
-
node-version: '20'
|
|
970
|
-
|
|
971
|
-
- name: Run API Tests
|
|
972
|
-
run: norn ./tests/ --junit --output-dir ./reports
|
|
973
|
-
|
|
974
|
-
- name: Upload Test Results
|
|
975
|
-
uses: actions/upload-artifact@v4
|
|
976
|
-
if: always()
|
|
977
|
-
with:
|
|
978
|
-
name: test-results
|
|
979
|
-
path: ./reports/*.xml
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
## Syntax Reference
|
|
983
|
-
|
|
984
|
-
### Swagger API Coverage
|
|
985
|
-
|
|
986
|
-
Track how much of your OpenAPI/Swagger spec is covered by tests:
|
|
987
|
-
|
|
988
|
-
- **Status Bar**: Shows coverage percentage when a `.nornapi` file has a `swagger` URL
|
|
989
|
-
- **Coverage Panel**: Click the status bar to see detailed per-endpoint coverage
|
|
990
|
-
- **Per Status Code**: Each response code (200, 400, 404) counts separately toward 100%
|
|
991
|
-
- **Wildcard Support**: Assert `2xx` to match 200, 201, 204, etc.
|
|
992
|
-
- **Test Sequences Only**: Only `test sequence` blocks count toward coverage
|
|
993
|
-
- **CodeLens**: Coverage shown on swagger import lines
|
|
994
|
-
|
|
995
|
-
Coverage is calculated by analyzing your test sequences for:
|
|
996
|
-
1. API calls by endpoint name (e.g., `GET GetPetById`)
|
|
997
|
-
2. Status assertions (e.g., `assert $1.status == 200`)
|
|
998
|
-
|
|
999
|
-
```bash
|
|
1000
|
-
# In your .nornapi file:
|
|
1001
|
-
swagger https://petstore.swagger.io/v2/swagger.json
|
|
1002
|
-
|
|
1003
|
-
GetOrderById: GET https://petstore.swagger.io/v2/store/order/{orderId}
|
|
1004
|
-
```
|
|
103
|
+
```norn
|
|
104
|
+
sequence ToolFlow
|
|
105
|
+
var tools = run mcp list localTools
|
|
106
|
+
var result = run mcp call localTools summarize_text(text: "hello world", format: "short")
|
|
1005
107
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
test sequence OrderTests
|
|
1009
|
-
# This covers GET /store/order/{orderId} with 200
|
|
1010
|
-
var order = GET GetOrderById(1)
|
|
1011
|
-
assert order.status == 200
|
|
1012
|
-
|
|
1013
|
-
# This covers GET /store/order/{orderId} with 404
|
|
1014
|
-
var notFound = GET GetOrderById(999999)
|
|
1015
|
-
assert notFound.status == 404
|
|
108
|
+
assert tools[0].name exists
|
|
109
|
+
assert result.structuredContent.summary exists
|
|
1016
110
|
end sequence
|
|
1017
111
|
```
|
|
1018
112
|
|
|
1019
|
-
|
|
1020
|
-
|--------|-------------|
|
|
1021
|
-
| `var name = value` | Declare a variable (literal) |
|
|
1022
|
-
| `var name = "text"` | Declare a string variable |
|
|
1023
|
-
| `var x = other.path` | Assign from expression (in sequences) |
|
|
1024
|
-
| `{{name}}` | Reference a variable |
|
|
1025
|
-
| `{{name.path}}` | Access nested property of JSON variable |
|
|
1026
|
-
| `{{name[0].prop}}` | Access array element in JSON variable |
|
|
1027
|
-
| `###` | Optional request separator |
|
|
1028
|
-
| `[RequestName]` | Define a named reusable request |
|
|
1029
|
-
| `run RequestName` | Execute a named request |
|
|
1030
|
-
| `sequence Name` | Start a helper sequence block |
|
|
1031
|
-
| `test sequence Name` | Start a test sequence (runs from CLI) |
|
|
1032
|
-
| `test sequence Name(params)` | Test sequence with parameters |
|
|
1033
|
-
| `@tagname` | Simple tag on a test sequence |
|
|
1034
|
-
| `@key(value)` | Key-value tag on a test sequence |
|
|
1035
|
-
| `@data(val1, val2)` | Inline test data for parameterized tests |
|
|
1036
|
-
| `@theory("file.json")` | External test data file |
|
|
1037
|
-
| `end sequence` | End a sequence block |
|
|
1038
|
-
| `var x = $1.path` | Capture value from response 1 |
|
|
1039
|
-
| `$N.status` | Access status code of response N |
|
|
1040
|
-
| `$N.body.path` | Access body property of response N |
|
|
1041
|
-
| `$N.headers.Name` | Access header of response N |
|
|
1042
|
-
| `$N.duration` | Access request duration in ms |
|
|
1043
|
-
| `$N.cookies` | Access cookies from response N |
|
|
1044
|
-
|
|
1045
|
-
### Assertions
|
|
1046
|
-
|
|
1047
|
-
| Syntax | Description |
|
|
1048
|
-
|--------|-------------|
|
|
1049
|
-
| `assert $1.status == 200` | Equality check |
|
|
1050
|
-
| `assert $1.body.count != 0` | Inequality check |
|
|
1051
|
-
| `assert $1.body.age > 18` | Greater than |
|
|
1052
|
-
| `assert $1.body.age >= 18` | Greater than or equal |
|
|
1053
|
-
| `assert $1.body.age < 100` | Less than |
|
|
1054
|
-
| `assert $1.body.age <= 100` | Less than or equal |
|
|
1055
|
-
| `assert $1.body.email contains "@"` | String contains |
|
|
1056
|
-
| `assert $1.body.name startsWith "J"` | String starts with |
|
|
1057
|
-
| `assert $1.body.name endsWith "n"` | String ends with |
|
|
1058
|
-
| `assert $1.body.email matches "regex"` | Regex match |
|
|
1059
|
-
| `assert $1.body.id exists` | Property exists |
|
|
1060
|
-
| `assert $1.body.deleted !exists` | Property does not exist |
|
|
1061
|
-
| `assert $1.body.id isType number` | Type check (number, string, boolean, array, object, null) |
|
|
1062
|
-
| `assert $1.duration < 5000` | Duration/timing check |
|
|
1063
|
-
| `assert ... \| "message"` | Custom failure message |
|
|
1064
|
-
|
|
1065
|
-
### Control Flow
|
|
1066
|
-
|
|
1067
|
-
| Syntax | Description |
|
|
1068
|
-
|--------|-------------|
|
|
1069
|
-
| `if <condition>` | Start conditional block (uses assertion operators) |
|
|
1070
|
-
| `end if` | End conditional block |
|
|
1071
|
-
| `wait 2s` | Wait 2 seconds |
|
|
1072
|
-
| `wait 500ms` | Wait 500 milliseconds |
|
|
1073
|
-
|
|
1074
|
-
### Scripts & Data
|
|
1075
|
-
|
|
1076
|
-
| Syntax | Description |
|
|
1077
|
-
|--------|-------------|
|
|
1078
|
-
| `run bash ./script.sh` | Run a bash script |
|
|
1079
|
-
| `run powershell ./script.ps1` | Run a PowerShell script |
|
|
1080
|
-
| `run js ./script.js` | Run a Node.js script |
|
|
1081
|
-
| `var x = run js ./script.js` | Run script and capture output |
|
|
1082
|
-
| `var data = run readJson ./file.json` | Load JSON file into variable |
|
|
1083
|
-
| `data.property = value` | Update loaded JSON property |
|
|
1084
|
-
| `print "Message"` | Print a message |
|
|
1085
|
-
| `print "Title" \| "Body"` | Print with expandable body |
|
|
1086
|
-
|
|
1087
|
-
### Environments (.nornenv)
|
|
1088
|
-
|
|
1089
|
-
| Syntax | Description |
|
|
1090
|
-
|--------|-------------|
|
|
1091
|
-
| `var name = value` | Common variable (all environments) |
|
|
1092
|
-
| `[env:name]` | Start environment section |
|
|
1093
|
-
| `# comment` | Comment line |
|
|
1094
|
-
| resolution | Closest ancestor `.nornenv` from the current `.norn`/`.nornapi` file |
|
|
1095
|
-
|
|
1096
|
-
### API Definitions (.nornapi)
|
|
1097
|
-
|
|
1098
|
-
| Syntax | Description |
|
|
1099
|
-
|--------|-------------|
|
|
1100
|
-
| `headers Name` | Start header group definition |
|
|
1101
|
-
| `end headers` | End header group definition |
|
|
1102
|
-
| `HeaderName: value` | Define a header (inside headers block) |
|
|
1103
|
-
| `endpoints` | Start endpoints block |
|
|
1104
|
-
| `end endpoints` | End endpoints block |
|
|
1105
|
-
| `Name: METHOD path` | Define named endpoint (e.g., `GetUser: GET /users/{id}`) |
|
|
1106
|
-
| `{param}` | Path parameter placeholder |
|
|
1107
|
-
| `swagger https://...` | Import from OpenAPI/Swagger URL |
|
|
1108
|
-
| `import "./file.nornapi"` | Import .nornapi file |
|
|
1109
|
-
|
|
1110
|
-
## Keyboard Shortcuts
|
|
1111
|
-
|
|
1112
|
-
| Shortcut | Command |
|
|
1113
|
-
|----------|---------|
|
|
1114
|
-
| `Ctrl+Alt+R` / `Cmd+Alt+R` | Send Request |
|
|
1115
|
-
|
|
1116
|
-
## Extension Commands
|
|
1117
|
-
|
|
1118
|
-
- `Norn: Send Request` - Send the HTTP request at cursor
|
|
1119
|
-
- `Norn: Run Sequence` - Run the sequence at cursor
|
|
1120
|
-
- `Norn: Select Environment` - Choose the active environment from .nornenv
|
|
1121
|
-
- `Norn: Clear Cookies` - Clear all stored cookies
|
|
1122
|
-
- `Norn: Show Stored Cookies` - Display cookies in output
|
|
1123
|
-
|
|
1124
|
-
## Extension Settings
|
|
1125
|
-
|
|
1126
|
-
- `norn.security.verifyTlsCertificates` (default: `true`) - Verify TLS certificates for HTTPS requests and Swagger/OpenAPI fetches. Disable only when testing local endpoints with self-signed certificates.
|
|
1127
|
-
|
|
1128
|
-
## Requirements
|
|
1129
|
-
|
|
1130
|
-
- VS Code 1.108.1 or higher
|
|
1131
|
-
- Node.js 22+ (for CLI and script execution)
|
|
1132
|
-
|
|
1133
|
-
## Release Notes
|
|
1134
|
-
|
|
1135
|
-
### 1.0.14
|
|
1136
|
-
|
|
1137
|
-
- **Variable Expression Assignment**: `var id = data.users[0].id` - extract values directly
|
|
1138
|
-
- **String Literals**: `var name = "John"` - use quotes for string values
|
|
1139
|
-
- **PowerShell Auto-Parsing**: Table and list output automatically converted to JSON
|
|
1140
|
-
- **ANSI Code Stripping**: Clean PowerShell output without color code corruption
|
|
1141
|
-
- **Improved Syntax Highlighting**: Different colors for strings, numbers, variables, booleans
|
|
1142
|
-
- **IntelliSense in Assignments**: Shows defined variables after `var x = `
|
|
1143
|
-
- **Invalid Assignment Diagnostics**: Red squiggly for unquoted strings with spaces
|
|
1144
|
-
|
|
1145
|
-
### 1.0.13
|
|
1146
|
-
|
|
1147
|
-
- **Assertions**: Full assertion system with comparison, type checking, existence, and regex operators
|
|
1148
|
-
- **Environments**: `.nornenv` file support with dev/staging/prod configurations
|
|
1149
|
-
- **Named Requests**: Define reusable requests with `[RequestName]` syntax
|
|
1150
|
-
- **Conditionals**: `if/end if` blocks for conditional execution
|
|
1151
|
-
- **Wait Commands**: `wait 2s` / `wait 500ms` for delays
|
|
1152
|
-
- **JSON File Loading**: `run readJson` to load test data from JSON files
|
|
1153
|
-
- **Property Updates**: Modify loaded JSON data inline
|
|
1154
|
-
- **CLI Environments**: `--env` flag to select environment in CLI
|
|
1155
|
-
|
|
1156
|
-
### 1.0.0
|
|
1157
|
-
|
|
1158
|
-
- HTTP request support (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
|
|
1159
|
-
- Variables with `var` and `{{}}`
|
|
1160
|
-
- Sequences with response capture (`$N.path`)
|
|
1161
|
-
- Script execution (`run bash/powershell/js`)
|
|
1162
|
-
- Print statements with title and body
|
|
1163
|
-
- Cookie jar with automatic persistence
|
|
1164
|
-
- Full syntax highlighting
|
|
1165
|
-
- IntelliSense for methods, headers, variables, keywords
|
|
1166
|
-
- Diagnostic errors for undefined variables
|
|
1167
|
-
- CLI for CI/CD pipelines
|
|
1168
|
-
- JSON output mode for automation
|
|
1169
|
-
|
|
1170
|
-
## License
|
|
1171
|
-
|
|
1172
|
-
**Free for Personal Use** - You may use Norn for personal projects, learning, education, and non-commercial open-source projects at no cost.
|
|
1173
|
-
|
|
1174
|
-
**30-Day Commercial Evaluation** - Businesses may evaluate Norn free for 30 days before purchasing a license.
|
|
1175
|
-
|
|
1176
|
-
**Commercial Use Requires a License** - After the evaluation period, use within a business, by employees during work, or in CI/CD pipelines for commercial projects requires a license. Contact us for commercial licensing options.
|
|
113
|
+
Behavior:
|
|
1177
114
|
|
|
1178
|
-
|
|
115
|
+
- `run mcp list <alias>` returns the full tool list and drains paginated `nextCursor` responses automatically.
|
|
116
|
+
- `run mcp call <alias> <tool>(name: value)` returns a deterministic result envelope with `content`, `structuredContent`, `isError`, `text`, `server`, and `tool`.
|
|
117
|
+
- Tool `structuredContent` is validated against the MCP tool's advertised `outputSchema` when present.
|
|
118
|
+
- Sessions are closed automatically when the outermost sequence finishes or fails.
|
|
1179
119
|
|
|
1180
|
-
|
|
120
|
+
## Good Fit For
|
|
1181
121
|
|
|
1182
|
-
|
|
122
|
+
- backend teams validating APIs during development
|
|
123
|
+
- QA and automation work that needs readable test flows
|
|
124
|
+
- regression and smoke suites that should run the same way locally and in CI
|
|
125
|
+
- projects that want API requests and API tests to live next to the code
|