QuadrastackQuadrastack
Documentation

Docs

Workflows

Chain multiple requests together with session variable passing

Workflows

Chain multiple requests together and pass data between them.

Overview

Workflows execute requests in sequence, capturing responses for use in subsequent requests. This is essential for testing multi-step processes like authentication flows, CRUD operations, and complex API interactions.

Key features:

  • Sequential request execution
  • Session variable capture with out
  • Access previous responses via {{.session.varName}}
  • Label-based filtering via scenarios

Workflow Structure

Required Fields

FieldTypeDescription
stepsarrayOrdered list of requests to execute (required)

Optional Fields

FieldTypeDescription
descriptionstringHuman-readable description
labelsarrayLabels for filtering workflows via scenarios

WorkflowStep Fields

FieldTypeDescription
requeststringName of the request to execute (required)
outstringVariable name to capture response in session

Basic Example

workflows:
  auth-flow:
    description: "Login and access protected resource"
    labels: [e2e, auth]
    steps:
      - request: login
        out: auth           # Capture response as "auth"
      - request: get-profile
        # Access via {{.session.auth.body.token}}

Session Variables

When you capture a response with out: varName, you can access its data in subsequent requests.

Available Session Fields

FieldTypeDescription
{{.session.varName.status}}numberHTTP status code
{{.session.varName.headers.HeaderName}}stringResponse header value
{{.session.varName.body}}object/arrayParsed JSON response body
{{.session.varName.body.field}}anySpecific field from JSON body
{{.session.varName.body.nested.field}}anyNested field access
{{.session.varName.bodyRaw}}stringRaw response body as string

Accessing JSON Body Fields

# Simple field access
url: "/users/{{.session.auth.body.userId}}"

# Nested field access
url: "/orgs/{{.session.auth.body.user.organization.id}}"

# Header access
headers:
  Authorization: "Bearer {{.session.auth.body.token}}"
  X-Request-ID: "{{.session.prevRequest.headers.X-Request-ID}}"

Accessing Array Elements

Use Go template index function for array access:

# Access first element's field
body:
  itemId: "{{(index .session.items.body 0).id}}"

# Access nested array
url: "/items/{{(index .session.data.body.items 0).id}}"

Complete Examples

Authentication Flow

Folder structure:

my-api-tests/
├── playbook.yaml

playbook.yaml:

vars:
  default:
    baseUrl: "https://api.example.com"
    username: "testuser"
    password: "testpass"

workflows:
  login-and-access:
    description: "Complete authentication flow"
    labels: [auth, e2e, smoke]
    steps:
      - request: login
        out: auth
      - request: get-profile
      - request: list-resources
      - request: logout

requests:
  login:
    method: POST
    url: "{{.vars.baseUrl}}/auth/login"
    body:
      username: "{{.vars.username}}"
      password: "{{.vars.password}}"
    expect:
      status: 200
      body:
        $.token: exists
        $.userId: exists

  get-profile:
    method: GET
    url: "{{.vars.baseUrl}}/users/{{.session.auth.body.userId}}"
    headers:
      Authorization: "Bearer {{.session.auth.body.token}}"
    expect:
      status: 200
      body:
        $.id: "{{.session.auth.body.userId}}"

  list-resources:
    method: GET
    url: "{{.vars.baseUrl}}/resources"
    headers:
      Authorization: "Bearer {{.session.auth.body.token}}"
    expect:
      status: 200
      body:
        $: type array

  logout:
    method: POST
    url: "{{.vars.baseUrl}}/auth/logout"
    headers:
      Authorization: "Bearer {{.session.auth.body.token}}"
    expect:
      status: 200

scenarios:
  auth-tests:
    description: "Run authentication workflow"
    labelSelector: "auth && e2e"

Run with:

quadrastack --scenario auth-tests

CRUD Workflow

workflows:
  user-crud:
    description: "Complete user CRUD operations"
    labels: [crud, users]
    steps:
      - request: create-user
        out: created
      - request: read-user
      - request: update-user
      - request: verify-update
      - request: delete-user
      - request: verify-deletion

requests:
  create-user:
    method: POST
    url: "{{.vars.baseUrl}}/users"
    body:
      name: Test User
      email: test@example.com
    expect:
      status: 201
      body:
        $.id: exists

  read-user:
    method: GET
    url: "{{.vars.baseUrl}}/users/{{.session.created.body.id}}"
    expect:
      status: 200
      body:
        $.name: Test User

  update-user:
    method: PUT
    url: "{{.vars.baseUrl}}/users/{{.session.created.body.id}}"
    body:
      name: Updated User
      email: updated@example.com
    expect:
      status: 200

  verify-update:
    method: GET
    url: "{{.vars.baseUrl}}/users/{{.session.created.body.id}}"
    expect:
      status: 200
      body:
        $.name: Updated User

  delete-user:
    method: DELETE
    url: "{{.vars.baseUrl}}/users/{{.session.created.body.id}}"
    expect:
      status: 204

  verify-deletion:
    method: GET
    url: "{{.vars.baseUrl}}/users/{{.session.created.body.id}}"
    expect:
      status: 404

OAuth Token Exchange

workflows:
  oauth-flow:
    description: "OAuth authorization code flow"
    labels: [oauth, auth]
    steps:
      - request: get-auth-code
        out: authCode
      - request: exchange-token
        out: token
      - request: access-resource

requests:
  get-auth-code:
    method: POST
    url: "{{.vars.baseUrl}}/oauth/authorize"
    body:
      client_id: "{{.vars.clientId}}"
      redirect_uri: "{{.vars.redirectUri}}"
      response_type: code
    expect:
      status: 200
      body:
        $.code: exists

  exchange-token:
    method: POST
    url: "{{.vars.baseUrl}}/oauth/token"
    body:
      code: "{{.session.authCode.body.code}}"
      grant_type: authorization_code
      client_id: "{{.vars.clientId}}"
      client_secret: "{{.vars.clientSecret}}"
    expect:
      status: 200
      body:
        $.access_token: exists
        $.token_type: Bearer

  access-resource:
    method: GET
    url: "{{.vars.baseUrl}}/api/protected"
    headers:
      Authorization: "{{.session.token.body.token_type}} {{.session.token.body.access_token}}"
    expect:
      status: 200

Two-Factor Authentication

workflows:
  2fa-login:
    description: "Login with 2FA verification"
    labels: [auth, 2fa]
    steps:
      - request: submit-credentials
        out: challenge
      - request: submit-2fa-code
        out: auth
      - request: access-protected

requests:
  submit-credentials:
    method: POST
    url: "{{.vars.baseUrl}}/auth/login"
    body:
      username: "{{.vars.username}}"
      password: "{{.vars.password}}"
    expect:
      status: 200
      body:
        $.challengeId: exists
        $.requires2FA: true

  submit-2fa-code:
    method: POST
    url: "{{.vars.baseUrl}}/auth/verify"
    body:
      challengeId: "{{.session.challenge.body.challengeId}}"
      code: "{{.vars.totpCode}}"
    expect:
      status: 200
      body:
        $.token: exists

  access-protected:
    method: GET
    url: "{{.vars.baseUrl}}/api/protected"
    headers:
      Authorization: "Bearer {{.session.auth.body.token}}"
    expect:
      status: 200

Running Workflows

Workflows are executed via scenarios using label selectors.

Define a Scenario for Your Workflow

workflows:
  my-workflow:
    labels: [e2e, critical]
    steps:
      - request: step1
      - request: step2

scenarios:
  run-e2e:
    description: "Run E2E workflows"
    labelSelector: "e2e"

Run the Scenario

# Run the scenario
quadrastack --scenario run-e2e

# Run with a specific profile
quadrastack --scenario run-e2e --profile staging

Best Practices

1. Use Descriptive Output Names

# Bad
steps:
  - request: login
    out: r1
  - request: get-data
    out: r2

# Good
steps:
  - request: login
    out: auth
  - request: get-data
    out: userData

2. Validate Each Step

Ensure critical fields exist before using them in subsequent steps:

requests:
  login:
    method: POST
    url: "{{.vars.baseUrl}}/auth/login"
    expect:
      status: 200
      body:
        $.token: exists      # Validate token exists
        $.userId: exists     # Validate userId exists

3. Keep Workflows Focused

# Bad: One giant workflow
workflows:
  everything:
    steps:
      # 50 steps...

# Good: Multiple focused workflows
workflows:
  auth-flow:
    labels: [auth]
    steps:
      - request: login
      - request: verify-session

  data-flow:
    labels: [data]
    steps:
      - request: fetch-data
      - request: process-data

4. Use Variables for Flexibility

vars:
  default:
    baseUrl: "http://localhost:8080"
  staging:
    baseUrl: "https://staging.example.com"
  production:
    baseUrl: "https://api.example.com"

requests:
  login:
    method: POST
    url: "{{.vars.baseUrl}}/auth/login"

Limitations

LimitationDescription
Workflow-scoped sessionsSession variables are only available within the same workflow execution
No cross-workflow sharingCannot share session data between different workflows
No loops or conditionalsUse multiple workflows for branching logic
No persistenceSession data is not persisted between CLI runs
Free tier: 5 steps maxFree tier limits workflows to 5 steps

See Also

Workflows - Quadrastack Docs