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
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 }
]