norn-cli 1.10.6 → 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/README.md CHANGED
@@ -1,1182 +1,125 @@
1
- # Norn - REST Client
1
+ # Norn
2
2
 
3
- A powerful REST client extension for VS Code with sequences, assertions, environments, script execution, and CLI support for CI/CD pipelines.
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
- ![VS Code Version](https://img.shields.io/badge/VS%20Code-1.108.1+-blue)
6
- ![License](https://img.shields.io/badge/license-EULA-orange)
5
+ ### Simple API Requests
7
6
 
8
- ## Features
7
+ ![Send requests and run sequences](https://firebasestorage.googleapis.com/v0/b/norn-1a99c.firebasestorage.app/o/simple.gif?alt=media&token=6625ec07-041c-4900-a23e-f09df0e9bf99)
9
8
 
10
- - **HTTP Requests**: Send GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS requests from `.norn` files
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
- ## Quick Start
11
+ ![Debug sequences in VS Code](https://firebasestorage.googleapis.com/v0/b/norn-1a99c.firebasestorage.app/o/debug.gif?alt=media&token=2b62c4d4-44d4-403b-ad03-2c0a39b41260)
39
12
 
40
- 1. Create a file with `.norn` extension
41
- 2. Write your HTTP request
42
- 3. Click "Send Request" above the request line
13
+ ## Why Norn
43
14
 
44
- ## Usage
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
- ### Basic Request
17
+ That means you can:
47
18
 
48
- ```bash
49
- GET https://api.example.com/users
50
- Authorization: Bearer my-token
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
- sequence MyTests
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
- **IntelliSense:** When you type `{{user.`, you'll see `name` and `email` as completions based on the sequence's return statement.
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
- ### Sequence Tags
33
+ ## Example
239
34
 
240
- Tag sequences for filtering during test execution. Tags support simple names and key-value pairs:
35
+ ```norn
36
+ var baseUrl = https://api.example.com
241
37
 
242
- ```bash
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
- {"username": "admin", "password": "secret"}
249
- assert $1.status == 200
250
- end sequence
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
- ```bash
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
- The response panel shows a retry indicator (🔄 retried 2x) when retries occurred.
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
- Use `print "Title" | "Body content"` for expandable messages in the result view.
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
- This is useful for comparing responses across environments, before/after changes, or expected vs actual results.
57
+ ## In VS Code
793
58
 
794
- #### Clickable JSON Values
59
+ Use Norn to:
795
60
 
796
- Generate assertions instantly by clicking on JSON values in the response:
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
- 1. Expand a response body in the panel
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
- For example, clicking on `"active"` in `{"status": "active"}` generates:
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
- # Run all test sequences in a file
817
- norn api-tests.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
- ### CLI Options
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
- - Breakpoints in root and imported `.norn` files
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
- You can also define explicit launch configs in `.vscode/launch.json`:
79
+ Create a `norn.mcp.json` in the root of your project:
888
80
 
889
81
  ```json
890
82
  {
891
- "version": "0.2.0",
892
- "configurations": [
893
- {
894
- "type": "norn",
895
- "request": "launch",
896
- "name": "Debug LoginFlow",
897
- "file": "${workspaceFolder}/tests/api-tests.norn",
898
- "sequence": "LoginFlow",
899
- "env": "prelive",
900
- "stopOnEntry": true
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
- Note: debugger support is currently VS Code-only by design. CLI behavior remains unchanged.
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
- ```yaml
960
- jobs:
961
- api-tests:
962
- runs-on: ubuntu-latest
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
- ```bash
1007
- # In your .norn test file:
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
- | Syntax | Description |
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
- See the [LICENSE](LICENSE) file for full terms.
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
- **Enjoy using Norn!**
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