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:

.norn
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:

.norn
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:

.norn
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.

.norn
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.

.norn
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.

.norn
[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:

.norn
@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:

.norn
@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.

.norn
@theory("./testdata/users.json")
test sequence UserTheory(userId, expectedId)
    GET {{baseUrl}}/users/{{userId}}
    assert $1.status == 200
    assert $1.body.id == {{expectedId}}
end sequence
testdata/users.json
[
  { "userId": 1, "expectedId": 1 },
  { "userId": 2, "expectedId": 2 }
]