Sequences
Sequences let you chain multiple API requests together, capturing data from one request to use in the next.
Basic Sequence
Wrap related requests in a sequence block:
sequence CreateAndVerifyUser
POST https://api.example.com/users
Content-Type: application/json
{"name": "Alice", "email": "alice@example.com"}
GET https://api.example.com/users/1
end sequence Response Capture
Use $1, $2, etc. to reference responses by their order:
sequence UserWorkflow
POST https://api.example.com/users
{"name": "Alice"}
# $1 refers to the POST response above
var userId = $1.body.id
GET https://api.example.com/users/{{userId}}
# $2 refers to this GET response
assert $2.status == 200
end sequence Response Properties
Access different parts of each response:
$1.status- HTTP status code$1.body- Response body (parsed as JSON)$1.body.path.to.value- Nested value in body$1.headers.Content-Type- Response header$1.duration- Request duration in milliseconds$1.cookies- Cookies captured from the response
In VS Code, after a successful Send Request or Run Sequence, Norn can suggest cached response-body properties for $1.body. and captured response variables like user.body.. CLI execution stays the same and does not write this editor cache.
Test Sequences
Mark sequences as tests with the test keyword. Test sequences run in the Test Explorer and CLI:
test sequence UserAPITests
GET https://api.example.com/users
assert $1.status == 200
assert $1.body[0].id exists
end sequence Sequence Composition
Sequences can call other sequences with run so you can separate setup, shared flows, and cleanup.
sequence Login
POST {{baseUrl}}/auth/login
Content-Type: application/json
{"username": "admin", "password": "secret"}
var token = $1.body.accessToken
return token
end sequence
test sequence UserFlow
var token = run Login
GET {{baseUrl}}/users/me
Authorization: Bearer {{token}}
assert $1.status == 200
end sequence Sequence Parameters and Returns
Sequences can accept parameters. A single returned value comes back as the raw value; multiple return values come back as an object keyed by name.
sequence GetTodoTitle(id)
GET {{baseUrl}}/todos/{{id}}
var title = $1.body.title
return title
end sequence
sequence AuthenticateAndFetch
POST {{baseUrl}}/login
{"user": "demo"}
var sessionId = $1.body.sessionId
GET {{baseUrl}}/todos/1
var todoTitle = $2.body.title
return sessionId, todoTitle
end sequence
test sequence ReturnsExample
var title = run GetTodoTitle(1)
var auth = run AuthenticateAndFetch
assert title isType string
assert auth.sessionId exists
assert auth.todoTitle isType string
end sequence Named Requests
Named requests let you define a reusable request block once and invoke it with run.
[FetchTodo]
GET {{baseUrl}}/todos/1
[FetchUser]
GET {{baseUrl}}/users/1
test sequence ReuseRequests
run FetchTodo
assert $1.status == 200
var user = run FetchUser
assert user.status == 200
end sequence Sequence Tags
Add tags to organize and filter tests:
@smoke
@team(backend)
test sequence CriticalPathTest
GET https://api.example.com/health
assert $1.status == 200
end sequence Run tagged tests from CLI: norn tests/ --tag smoke
Parameterized Tests
Use @data for data-driven tests:
@data(1, "Alice")
@data(2, "Bob")
test sequence UserTest(id, expectedName)
GET https://api.example.com/users/{{id}}
assert $1.body.name == "{{expectedName}}"
end sequence For external test data, use @theory with a JSON array of objects whose keys match the sequence parameters.
@theory("./testdata/users.json")
test sequence UserTheory(userId, expectedId)
GET {{baseUrl}}/users/{{userId}}
assert $1.status == 200
assert $1.body.id == {{expectedId}}
end sequence [
{ "userId": 1, "expectedId": 1 },
{ "userId": 2, "expectedId": 2 }
]