norn-cli
v1.5.0
Published
A powerful REST client for making HTTP requests with sequences, variables, scripts, and cookie support
Downloads
4,011
Maintainers
Readme
Norn - REST Client
A powerful REST client extension for VS Code with sequences, assertions, environments, script execution, and CLI support for CI/CD pipelines.
Features
- HTTP Requests: Send GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS requests from
.nornfiles - API Definitions: Define reusable headers and endpoints in
.nornapifiles - Variables: Define and reference variables with
var name = valueand{{name}} - Environments: Manage dev/staging/prod configurations with
.nornenvfiles - Sequences: Chain multiple requests with response capture using
$N.path - Test Sequences: Mark sequences as tests with
test sequencefor CLI execution - Test Explorer: Run tests from VS Code's Testing sidebar with colorful output
- Swagger Coverage: Track API coverage with status bar indicator and detailed panel
- Parameterized Tests: Data-driven testing with
@dataand@theoryannotations - Sequence Tags: Tag sequences with
@smoke,@team(CustomerExp)for filtering in CI/CD - Secret Variables: Mark sensitive environment variables with
secretfor automatic redaction - Assertions: Validate responses with
assertstatements supporting comparison, type checking, and existence - Named Requests: Define reusable requests with
[RequestName]and call them withrun RequestName - Conditionals: Control flow with
if/end ifblocks based on response data - Wait Commands: Add delays between requests with
wait 1sorwait 500ms - Retry and Backoff: Automatically retry failed requests with
retry 3 backoff 200 ms - JSON File Loading: Load test data from JSON files with
run readJson ./file.json - Property Updates: Modify loaded JSON data inline with
config.property = value - Script Execution: Run bash, PowerShell, or JavaScript scripts within sequences
- Print Statements: Debug and log messages during sequence execution
- Cookie Support: Automatic cookie jar with persistence across requests
- Response Comparison: Compare any two API responses side-by-side with VS Code's diff view
- Clickable JSON: Click any JSON value in the response panel to auto-generate assertions
- Syntax Highlighting: Full syntax highlighting for requests, headers, JSON bodies
- IntelliSense: Autocomplete for HTTP methods, headers, variables, keywords, and Swagger-based request body keys/templates for endpoint POST/PUT/PATCH calls
- Diagnostics: Error highlighting for undefined variables
- CLI: Run tests from terminal with JUnit/HTML reports for CI/CD automation
Quick Start
- Create a file with
.nornextension - Write your HTTP request
- Click "Send Request" above the request line
Usage
Basic Request
GET https://api.example.com/users
Authorization: Bearer my-tokenVariables
Variables can hold literal strings or be assigned from expressions:
# Literal string (quotes optional for simple values)
var baseUrl = https://api.example.com
var name = "John Doe"
# Use variables with {{}}
GET {{baseUrl}}/users
Authorization: Bearer {{token}}Variable Assignment in Sequences
Inside sequences, you can assign variables from expressions:
sequence Example
var data = run readJson ./config.json
# Extract a value from JSON variable (evaluated)
var userId = data.users[0].id
# Literal string with interpolation
var message = "User ID is {{userId}}"
# Response capture
GET https://api.example.com/users/{{userId}}
var userName = $1.body.name
print "Result" | "{{message}}, Name: {{userName}}"
end sequence| Syntax | Type | Example |
|--------|------|---------|
| var x = "text" | Literal string | var name = "John" |
| var x = "Hi {{y}}" | String with interpolation | var msg = "Hello {{name}}" |
| var x = someVar.path | Expression (evaluated) | var id = data.users[0].id |
| var x = $1.body.id | Response capture | var token = $1.body.token |
| var x = run ... | Script/JSON result | var data = run readJson ./file.json |
Request with Body
POST https://api.example.com/users
Content-Type: application/json
{
"name": "John Doe",
"email": "[email protected]"
}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.
Sequences (Chained Requests)
Chain multiple requests and capture response data:
sequence AuthFlow
POST https://api.example.com/login
Content-Type: application/json
{"username": "admin", "password": "secret"}
var token = $1.accessToken
GET https://api.example.com/profile
Authorization: Bearer {{token}}
end sequenceClick "▶ Run Sequence" above the sequence line to execute all requests in order.
Sequence Composition
Sequences can call other sequences, enabling modular test design and natural setup/teardown patterns:
# Define reusable setup sequence
sequence Login
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "admin", "password": "secret"}
var token = $1.body.accessToken
print "Logged in" | "Token: {{token}}"
end sequence
# Define reusable teardown sequence
sequence Logout
POST {{baseUrl}}/auth/logout
Authorization: Bearer {{token}}
print "Logged out"
end sequence
# Main test sequence uses setup/teardown
sequence UserTests
# Run setup - token variable becomes available
run Login
# Run actual tests
GET {{baseUrl}}/users/me
Authorization: Bearer {{token}}
assert $1.status == 200
# Run teardown
run Logout
end sequenceKey behaviors:
- Variables set in called sequences are available to the caller (
tokenfromLogin) - Sequences can be nested to any depth
- Circular references are detected and reported as errors
- If a called sequence fails, the parent sequence stops
Sequence Parameters
Sequences can accept parameters with optional default values:
# Required parameter
sequence Greet(name)
print "Hello, {{name}}!"
end sequence
# Optional parameter with default
sequence Login(username, password = "secret123")
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "{{username}}", "password": "{{password}}"}
var token = $1.body.accessToken
return token
end sequence
# Call with positional arguments
run Greet("World")
run Login("admin", "mypassword")
# Call with named arguments (any order)
run Login(password: "pass123", username: "admin")
# Use defaults - only provide required params
run Login("admin") # uses default passwordRules:
- Required parameters (no default) must come before optional parameters
- Positional arguments are bound in order
- Named arguments can be in any order
- Cannot mix positional arguments after named arguments
Sequence Return Values
Sequences can return values that the caller can capture and use:
sequence FetchUser(userId)
GET {{baseUrl}}/users/{{userId}}
var name = $1.body.name
var email = $1.body.email
return name, email
end sequence
sequence MyTests
# Capture return values
var user = run FetchUser("123")
# Access individual fields
print "User name" | "{{user.name}}"
print "User email" | "{{user.email}}"
# Use in requests
POST {{baseUrl}}/messages
Content-Type: application/json
{"to": "{{user.email}}", "subject": "Hello {{user.name}}"}
end sequenceIntelliSense: When you type {{user., you'll see name and email as completions based on the sequence's return statement.
Sequence Tags
Tag sequences for filtering during test execution. Tags support simple names and key-value pairs:
# Simple tags
@smoke @regression
sequence AuthFlow
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "admin", "password": "secret"}
assert $1.status == 200
end sequence
# Key-value tags for categorization
@team(CustomerExp)
@priority(high)
@jira(NORN-123)
sequence CheckoutFlow
GET {{baseUrl}}/checkout
assert $1.status == 200
end sequence
# Mixed tags on multiple lines
@smoke
@team(Platform)
@feature(payments)
sequence PaymentValidation
POST {{baseUrl}}/payments
assert $1.status == 201
end sequenceTag Syntax:
- Simple tags:
@smoke,@regression,@wip,@slow - Key-value tags:
@team(CustomerExp),@priority(high),@jira(NORN-123) - Multiple tags can be on the same line or separate lines
- Tag names follow the pattern:
[a-zA-Z_][a-zA-Z0-9_-]* - Tag matching is case-insensitive
CLI Filtering:
# Run sequences tagged @smoke
npx norn tests/ --tag smoke
# AND logic: must have BOTH tags
npx norn tests/ --tag smoke --tag auth
# OR logic: match ANY tag
npx norn tests/ --tags smoke,regression
# Key-value exact match
npx norn tests/ --tag team(CustomerExp)
# Combine with environment
npx norn tests/ --env staging --tag smokeBehavior:
- When a sequence calls
run OtherSequenceand tag filtering is active, non-matching sequences are silently skipped - Untagged sequences run when no tag filter is specified
- Tags can only be applied to sequences (diagnostics warn on misplaced tags)
Imports
Organize your tests by importing named requests and sequences from other files:
# common.norn - shared utilities
var baseUrl = https://api.example.com
[AuthRequest]
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "admin", "password": "secret"}
sequence SharedSetup
run AuthRequest
var token = $1.body.accessToken
end sequence# main-test.norn - imports shared utilities
import "./common.norn"
sequence MyTests
# Use imported sequence
run SharedSetup
# Use imported request
run AuthRequest
# Token from SharedSetup is available
GET {{baseUrl}}/users/me
Authorization: Bearer {{token}}
assert $1.status == 200
end sequenceKey behaviors:
- Import paths are relative to the current file
- Nested imports are supported (imported files can import other files)
- Circular imports are detected and reported as errors
- Only named requests
[Name]and sequences are imported - Variables are resolved at import time (baked in), not exported
.nornenvresolution is file-aware: Norn walks up from the running file and uses the closest.nornenv
API Definition Files (.nornapi)
Define reusable API configurations with header groups and named endpoints in .nornapi files.
Header Groups
Header groups define reusable sets of HTTP headers:
# api.nornapi
headers Json
Content-Type: application/json
Accept: application/json
end headers
headers Auth
Authorization: Bearer {{token}}
X-API-Key: {{apiKey}}
end headers
headers Form
Content-Type: application/x-www-form-urlencoded
end headers- Header values can include
{{variables}}that are resolved at runtime - Multiple header groups can be applied to a single request
Named Endpoints
Define named endpoints with path parameters using {param} syntax:
endpoints
# Basic CRUD operations
GetUser: GET {{baseUrl}}/users/{id}
GetAllUsers: GET {{baseUrl}}/users
CreateUser: POST {{baseUrl}}/users
UpdateUser: PUT {{baseUrl}}/users/{id}
DeleteUser: DELETE {{baseUrl}}/users/{id}
# Multiple path parameters
GetUserPosts: GET {{baseUrl}}/users/{userId}/posts
GetUserPost: GET {{baseUrl}}/users/{userId}/posts/{postId}
# All HTTP methods supported
CheckHealth: HEAD {{baseUrl}}/health
GetOptions: OPTIONS {{baseUrl}}/api
end endpoints- Endpoint names are case-sensitive (e.g.,
GetUser,CreateUser) - Path parameters use single braces:
{id},{userId} - Environment variables use double braces:
{{baseUrl}}
Swagger/OpenAPI Import
Import endpoints directly from OpenAPI/Swagger specifications:
# api.nornapi
swagger https://petstore.swagger.io/v2/swagger.jsonClick "Import Swagger" CodeLens above the statement to fetch and generate endpoint definitions from the spec.
Using .nornapi in .norn Files
Import your API definitions and use them in requests:
import "./api.nornapi"
# Standalone request with endpoint and header group
GET GetUser(1) Json
# Inside a sequence
sequence UserTests
# Basic endpoint call with parameter
GET GetTodo(1) Json
assert $1.status == 200
# Multiple header groups (space-separated)
GET GetUser(1) Json Auth
# Header groups on separate lines
POST CreateUser
Json
Auth
{"name": "John", "email": "[email protected]"}
# Capture response to variable
var user = GET GetUser(1) Json
print "User" | "{{user.body.name}}"
# Use variables in endpoint parameters
var userId = 5
GET GetUser({{userId}}) Json
# Quoted string parameters
GET GetUser("123") Json
end sequenceEndpoint Syntax Reference
| Syntax | Description |
|--------|-------------|
| GET EndpointName(param) | Call endpoint with positional parameter |
| GET EndpointName(param1, param2) | Multiple positional parameters |
| GET EndpointName(id: "123") | Named parameter |
| GET EndpointName(1) Json | With header group |
| GET EndpointName(1) Json Auth | Multiple header groups |
| var x = GET EndpointName(1) Json | Capture response to variable |
Assertions
Validate response data with powerful assertion operators:
sequence ValidateAPI
GET https://api.example.com/users/1
# Status and comparison assertions
assert $1.status == 200
assert $1.status < 300
# Body value assertions
assert $1.body.id == 1
assert $1.body.name == "John"
assert $1.body.age >= 18
# String assertions
assert $1.body.email contains "@"
assert $1.body.name startsWith "J"
assert $1.body.name endsWith "ohn"
assert $1.body.email matches "[a-z]+@[a-z]+\.[a-z]+"
# Timing assertions
assert $1.duration < 5000
# Existence checks
assert $1.body.id exists
assert $1.body.deletedAt !exists
# Type assertions
assert $1.body.id isType number
assert $1.body.name isType string
assert $1.body.active isType boolean
assert $1.body.tags isType array
assert $1.body.address isType object
# Header assertions
assert $1.headers.Content-Type contains "application/json"
# Custom failure messages
assert $1.status == 200 | "API should return success status"
end sequenceEnvironments
Create a .nornenv file to manage environment-specific variables.
Norn resolves the closest .nornenv by walking up from the file being run (or edited):
# .nornenv file
# Common variables (always available)
var timeout = 30000
var version = v1
[env:dev]
var baseUrl = https://dev-api.example.com
var apiKey = dev-key-123
[env:staging]
var baseUrl = https://staging-api.example.com
var apiKey = staging-key-456
[env:prod]
var baseUrl = https://api.example.com
var apiKey = prod-key-789Select the active environment from the VS Code status bar. Environment variables override common variables.
Named Requests
Define reusable requests and call them from sequences:
# Define a reusable login request
[Login]
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "admin", "password": "secret"}
# Define a profile request
[GetProfile]
GET {{baseUrl}}/users/me
Authorization: Bearer {{token}}
###
sequence AuthFlow
# Run the named request
run Login
var token = $1.body.accessToken
# Run another named request
run GetProfile
print "Profile" | "Welcome, {{$2.body.name}}!"
end sequenceConditionals (if/end if)
Control execution flow based on response data:
sequence ConditionalFlow
GET https://api.example.com/users/1
# Execute block only if condition is true
if $1.status == 200
print "Success" | "User found!"
GET https://api.example.com/users/1/orders
assert $2.status == 200
end if
# Check for errors
if $1.status == 404
print "Error" | "User not found"
end if
# Conditions support all assertion operators
if $1.body.role == "admin"
print "Admin" | "User has admin privileges"
end if
end sequenceWait Commands
Add delays between requests (useful for rate limiting or async operations):
sequence RateLimitedFlow
POST https://api.example.com/jobs
var jobId = $1.body.id
print "Waiting" | "Job submitted, waiting for completion..."
# Wait 2 seconds
wait 2s
GET https://api.example.com/jobs/{{jobId}}
# Wait 500 milliseconds
wait 500ms
GET https://api.example.com/jobs/{{jobId}}/result
end sequenceRetry and Backoff
Automatically retry failed HTTP requests with configurable backoff:
sequence RetryExample
# Retry up to 3 times with 200ms linear backoff (200ms, 400ms, 600ms)
var result = GET "https://api.example.com/flaky-endpoint" retry 3 backoff 200 ms
# Works with named endpoints from .nornapi files
var user = GET UserEndpoint retry 2 backoff 500 ms
# Works with named requests
var data = run FlakyRequest retry 3 backoff 100 ms
# Retry only (uses default 1 second backoff)
var simple = GET "https://api.example.com/status" retry 2
end sequenceRetries trigger on:
- 5xx server errors (500, 502, 503, etc.)
- 429 rate limiting
- Network failures and timeouts
Time units:
msormilliseconds- e.g.,backoff 200 mssorseconds- e.g.,backoff 2 s
The response panel shows a retry indicator (🔄 retried 2x) when retries occurred.
JSON File Loading
Load test data from external JSON files:
sequence DataDrivenTest
# Load JSON configuration
var config = run readJson ./test-config.json
# Access properties
print "Config" | "Using API: {{config.baseUrl}}"
# Use in requests
GET {{config.baseUrl}}/users/{{config.testUser.id}}
# Access nested values and arrays
print "First Role" | "{{config.testUser.roles[0]}}"
# Modify loaded data inline
config.baseUrl = https://api.updated.com
config.testUser.name = Updated Name
print "Updated" | "New URL: {{config.baseUrl}}"
end sequenceExample test-config.json:
{
"baseUrl": "https://api.example.com",
"testUser": {
"id": 1,
"name": "Test User",
"roles": ["admin", "user"]
}
}Script Execution
Run scripts within sequences for setup, data generation, or validation:
sequence TestWithScripts
# Run a setup script
run bash ./scripts/seed-db.sh
# Generate a signature and capture output
var signature = run js ./scripts/sign.js {{payload}}
# Use the signature in a request
POST https://api.example.com/verify
X-Signature: {{signature}}
end sequenceScripts receive variables as environment variables with NORN_ prefix (e.g., NORN_TOKEN).
Capturing Structured Data from Scripts
When scripts output JSON, you can access properties directly:
sequence DatabaseQuery
# PowerShell script that outputs JSON
var dbResult = run powershell ./scripts/query-db.ps1
# Access properties from the JSON output
print "User Found" | "ID: {{dbResult.id}}, Name: {{dbResult.name}}"
# Use in requests
GET https://api.example.com/users/{{dbResult.id}}
end sequenceAutomatic Format Detection: Norn automatically parses multiple output formats:
| Format | Example | Access |
|--------|---------|--------|
| JSON | {"Id": 123, "Name": "John"} | {{result.Id}} |
| PowerShell Table | Id Name-- ----123 John | {{result.Id}} |
| PowerShell List | Id : 123Name : John | {{result.Id}} |
Norn also strips ANSI color codes automatically, so formatted terminal output won't corrupt your data.
Print Statements
Add debug output to your sequences:
sequence DebugFlow
print "Starting authentication..."
POST https://api.example.com/login
Content-Type: application/json
{"user": "admin"}
var token = $1.token
print "Token received" | "Value: {{token}}"
end sequenceUse print "Title" | "Body content" for expandable messages in the result view.
Response Panel Tools
The response panel provides interactive tools that accelerate test development:
Response Comparison
Compare any two API responses side-by-side using VS Code's built-in diff view:
- Click ⇄ Compare on the first response body
- Click ⇄ Compare on the second response
- VS Code opens a diff view showing differences between the two responses
This is useful for comparing responses across environments, before/after changes, or expected vs actual results.
Clickable JSON Values
Generate assertions instantly by clicking on JSON values in the response:
- Expand a response body in the panel
- Hover over any value (string, number, boolean, null) - it highlights with a + badge
- Click to automatically insert an assertion at the end of your sequence
For example, clicking on "active" in {"status": "active"} generates:
assert $response.body.status == "active"Works with nested paths and arrays:
body.user.email→assert $user.body.user.email == "[email protected]"body.items[0].id→assert $order.body.items[0].id == 123
CLI Usage
Run tests from the command line for CI/CD pipelines. Only sequences marked with test are executed.
# Run all test sequences in a file
npx norn api-tests.norn
# Run all test sequences in a directory (recursive)
npx norn tests/
# Run a specific sequence
npx norn api-tests.norn --sequence AuthFlow
# Run with a specific environment
npx norn api-tests.norn --env staging
# Generate JUnit XML report for CI/CD
npx norn tests/ --junit --output-dir ./reports
# Generate HTML report
npx norn tests/ --html --output-dir ./reports
# Verbose output with colors
npx norn api-tests.norn -v
# Show help
npx norn --helpCLI Options
| Option | Description |
|--------|-------------|
| -s, --sequence <name> | Run a specific sequence by name |
| -e, --env <name> | Use a specific environment from .nornenv |
| --tag <name> | Filter by tag (AND logic, can repeat) |
| --tags <list> | Filter by comma-separated tags (OR logic) |
| -j, --json | Output results as JSON |
| --junit | Generate JUnit XML report |
| --html | Generate HTML report |
| --output-dir <path> | Save reports to directory (auto-timestamps filenames) |
| -v, --verbose | Show detailed output with colors |
| --no-fail | Don't exit with error code on failed tests |
| -h, --help | Show help message |
Test Explorer
Run tests directly from VS Code's Testing sidebar:
- Automatic Discovery: Test sequences appear in the Testing view
- Tag Grouping: Tests organized by tags (
@smoke,@regression, etc.) - Colorful Output: ANSI-colored results with icons and status codes
- Persistent Output: Select a test to see its full output anytime
- Failure Details: Expected vs actual diffs, request/response info
Parameterized Tests
Use @data for data-driven testing - each data row becomes a separate test:
# Single parameter - runs 3 times with id = 1, 2, 3
@data(1, 2, 3)
test sequence TodoTest(id)
GET {{baseUrl}}/todos/{{id}}
assert $1.status == 200
assert $1.body.id == {{id}}
end sequence
# Multiple parameters - runs 2 times
@data(1, "delectus aut autem")
@data(2, "quis ut nam facilis")
test sequence TodoTitleTest(id, expectedTitle)
GET {{baseUrl}}/todos/{{id}}
assert $1.status == 200
assert $1.body.title == "{{expectedTitle}}"
end sequence
# Typed values (numbers, booleans, strings)
@data(1, true, "active")
@data(2, false, "inactive")
test sequence UserStatusTest(userId, isActive, status)
GET {{baseUrl}}/users/{{userId}}
assert $1.status == 200
end sequenceUse @theory for external data files:
@theory("./testdata.json")
test sequence DataFileTest(id, name)
GET {{baseUrl}}/items/{{id}}
assert $1.body.name == "{{name}}"
end sequenceWhere testdata.json contains:
[
{"id": 1, "name": "Widget"},
{"id": 2, "name": "Gadget"}
]CI/CD Example (GitHub Actions)
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Run API Tests
run: npx norn ./tests/ --junit --output-dir ./reports
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: ./reports/*.xmlSyntax Reference
Swagger API Coverage
Track how much of your OpenAPI/Swagger spec is covered by tests:
- Status Bar: Shows coverage percentage when a
.nornapifile has aswaggerURL - Coverage Panel: Click the status bar to see detailed per-endpoint coverage
- Per Status Code: Each response code (200, 400, 404) counts separately toward 100%
- Wildcard Support: Assert
2xxto match 200, 201, 204, etc. - Test Sequences Only: Only
test sequenceblocks count toward coverage - CodeLens: Coverage shown on swagger import lines
Coverage is calculated by analyzing your test sequences for:
- API calls by endpoint name (e.g.,
GET GetPetById) - Status assertions (e.g.,
assert $1.status == 200)
# In your .nornapi file:
swagger https://petstore.swagger.io/v2/swagger.json
GetOrderById: GET https://petstore.swagger.io/v2/store/order/{orderId}# In your .norn test file:
test sequence OrderTests
# This covers GET /store/order/{orderId} with 200
var order = GET GetOrderById(1)
assert order.status == 200
# This covers GET /store/order/{orderId} with 404
var notFound = GET GetOrderById(999999)
assert notFound.status == 404
end sequence| Syntax | Description |
|--------|-------------|
| var name = value | Declare a variable (literal) |
| var name = "text" | Declare a string variable |
| var x = other.path | Assign from expression (in sequences) |
| {{name}} | Reference a variable |
| {{name.path}} | Access nested property of JSON variable |
| {{name[0].prop}} | Access array element in JSON variable |
| ### | Optional request separator |
| [RequestName] | Define a named reusable request |
| run RequestName | Execute a named request |
| sequence Name | Start a helper sequence block |
| test sequence Name | Start a test sequence (runs from CLI) |
| test sequence Name(params) | Test sequence with parameters |
| @tagname | Simple tag on a test sequence |
| @key(value) | Key-value tag on a test sequence |
| @data(val1, val2) | Inline test data for parameterized tests |
| @theory("file.json") | External test data file |
| end sequence | End a sequence block |
| var x = $1.path | Capture value from response 1 |
| $N.status | Access status code of response N |
| $N.body.path | Access body property of response N |
| $N.headers.Name | Access header of response N |
| $N.duration | Access request duration in ms |
| $N.cookies | Access cookies from response N |
Assertions
| Syntax | Description |
|--------|-------------|
| assert $1.status == 200 | Equality check |
| assert $1.body.count != 0 | Inequality check |
| assert $1.body.age > 18 | Greater than |
| assert $1.body.age >= 18 | Greater than or equal |
| assert $1.body.age < 100 | Less than |
| assert $1.body.age <= 100 | Less than or equal |
| assert $1.body.email contains "@" | String contains |
| assert $1.body.name startsWith "J" | String starts with |
| assert $1.body.name endsWith "n" | String ends with |
| assert $1.body.email matches "regex" | Regex match |
| assert $1.body.id exists | Property exists |
| assert $1.body.deleted !exists | Property does not exist |
| assert $1.body.id isType number | Type check (number, string, boolean, array, object, null) |
| assert $1.duration < 5000 | Duration/timing check |
| assert ... \| "message" | Custom failure message |
Control Flow
| Syntax | Description |
|--------|-------------|
| if <condition> | Start conditional block (uses assertion operators) |
| end if | End conditional block |
| wait 2s | Wait 2 seconds |
| wait 500ms | Wait 500 milliseconds |
Scripts & Data
| Syntax | Description |
|--------|-------------|
| run bash ./script.sh | Run a bash script |
| run powershell ./script.ps1 | Run a PowerShell script |
| run js ./script.js | Run a Node.js script |
| var x = run js ./script.js | Run script and capture output |
| var data = run readJson ./file.json | Load JSON file into variable |
| data.property = value | Update loaded JSON property |
| print "Message" | Print a message |
| print "Title" \| "Body" | Print with expandable body |
Environments (.nornenv)
| Syntax | Description |
|--------|-------------|
| var name = value | Common variable (all environments) |
| [env:name] | Start environment section |
| # comment | Comment line |
| resolution | Closest ancestor .nornenv from the current .norn/.nornapi file |
API Definitions (.nornapi)
| Syntax | Description |
|--------|-------------|
| headers Name | Start header group definition |
| end headers | End header group definition |
| HeaderName: value | Define a header (inside headers block) |
| endpoints | Start endpoints block |
| end endpoints | End endpoints block |
| Name: METHOD path | Define named endpoint (e.g., GetUser: GET /users/{id}) |
| {param} | Path parameter placeholder |
| swagger https://... | Import from OpenAPI/Swagger URL |
| import "./file.nornapi" | Import .nornapi file |
Keyboard Shortcuts
| Shortcut | Command |
|----------|---------|
| Ctrl+Alt+R / Cmd+Alt+R | Send Request |
Extension Commands
Norn: Send Request- Send the HTTP request at cursorNorn: Run Sequence- Run the sequence at cursorNorn: Select Environment- Choose the active environment from .nornenvNorn: Clear Cookies- Clear all stored cookiesNorn: Show Stored Cookies- Display cookies in output
Requirements
- VS Code 1.108.1 or higher
- Node.js 22+ (for CLI and script execution)
Release Notes
1.0.14
- Variable Expression Assignment:
var id = data.users[0].id- extract values directly - String Literals:
var name = "John"- use quotes for string values - PowerShell Auto-Parsing: Table and list output automatically converted to JSON
- ANSI Code Stripping: Clean PowerShell output without color code corruption
- Improved Syntax Highlighting: Different colors for strings, numbers, variables, booleans
- IntelliSense in Assignments: Shows defined variables after
var x = - Invalid Assignment Diagnostics: Red squiggly for unquoted strings with spaces
1.0.13
- Assertions: Full assertion system with comparison, type checking, existence, and regex operators
- Environments:
.nornenvfile support with dev/staging/prod configurations - Named Requests: Define reusable requests with
[RequestName]syntax - Conditionals:
if/end ifblocks for conditional execution - Wait Commands:
wait 2s/wait 500msfor delays - JSON File Loading:
run readJsonto load test data from JSON files - Property Updates: Modify loaded JSON data inline
- CLI Environments:
--envflag to select environment in CLI
1.0.0
- HTTP request support (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
- Variables with
varand{{}} - Sequences with response capture (
$N.path) - Script execution (
run bash/powershell/js) - Print statements with title and body
- Cookie jar with automatic persistence
- Full syntax highlighting
- IntelliSense for methods, headers, variables, keywords
- Diagnostic errors for undefined variables
- CLI for CI/CD pipelines
- JSON output mode for automation
License
Free for Personal Use - You may use Norn for personal projects, learning, education, and non-commercial open-source projects at no cost.
30-Day Commercial Evaluation - Businesses may evaluate Norn free for 30 days before purchasing a license.
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.
See the LICENSE file for full terms.
Enjoy using Norn!
