Scripting GuideBefore Request Hooks

Before Request Hooks

Before Request hooks run just before an API request is sent, allowing you to dynamically modify the request—such as adding authentication headers, changing the request body, or adding query parameters.

Multi-Level Hook System

Before Request hooks in ReAPI follow a 3-level hierarchy with a symmetric “onion model” execution pattern:

  1. Test Flow Level - Global hooks for the entire test case
  2. Folder Level - Module/feature-specific hooks (with folder hierarchy)
  3. Node Level - Individual API request hooks

Each level supports two modes:

  • Inline: Write hook code directly at that level
  • Reference: Select a reusable hook function by ID (defined in Global Scripts)

Execution Order: Outer → Inner

Before Request hooks execute from outer to inner (general to specific):

Test Flow Hooks (global concerns)

Folder Hooks: Root → Parent → Current (module logic)

Node Inline Hook (specific to this API)

Node Referenced Hooks (reusable functions)

→ API Request Sent

Why this order?

  • Start with cross-cutting concerns (auth, tracing, debug flags)
  • Add module-specific logic (encryption, module headers)
  • Finish with endpoint-specific parameters (timeout, specific headers)
  • Each layer builds upon what previous layers established

Execution Order Example

// Test Flow Hook: Add authentication for ALL APIs
async function beforeRequest() {
  $request.headers["Authorization"] = `Bearer ${$context.config.authToken}`;
  $request.headers["X-Trace-ID"] = $context.traceId;
}
 
// Folder Hook (Payment Module): Add payment-specific headers
async function beforeRequest() {
  $request.headers["X-Payment-API-Version"] = "v2";
  $request.headers["X-Idempotency-Key"] = faker.string.uuid();
}
 
// Node Inline Hook: Endpoint-specific timeout
async function beforeRequest() {
  $request.timeout = 60000; // This endpoint is slow
  $request.body.refundReason = "test_automation";
}

Result: Request has auth + tracing + payment headers + specific timeout—all layers applied.


Script Pattern

A Before Request hook must define an async function named beforeRequest. This function can access and modify the $request and $context objects.

async function beforeRequest() {
  // Access and modify request properties
  $request.headers["Authorization"] = "Bearer " + $context.token;
  $request.query["version"] = "v2";
  $request.body = {
    ...$request.body,
    timestamp: Date.now(),
    userId: $context.userId,
  };
 
  // Access and modify context
  $context.requestStartTime = Date.now();
}

Key Points

  • Function Name: Must be beforeRequest.
  • Async: Must be an async function.
  • Request Access: Use $request to read and modify the outgoing HTTP request.
  • Context Access: Use $context to read and write to the test context.
  • Auto-Instrumentation: Changes to $request and $context are automatically saved.

Available Request Properties

  • $request.url: Request URL
  • $request.method: HTTP method (e.g., ‘GET’, ‘POST’)
  • $request.headers: Request headers object
  • $request.query: Query parameters object
  • $request.body: Request body
  • $request.timeout: Request timeout

Example: Dynamic Authentication

async function beforeRequest() {
  if ($context.authType === "bearer") {
    $request.headers["Authorization"] = `Bearer ${$context.accessToken}`;
  } else if ($context.authType === "api-key") {
    $request.headers["X-API-Key"] = $context.apiKey;
  }
 
  $request.headers["X-Request-ID"] = `req_${Date.now()}`;
}

When to Use Each Level

LevelBest ForExamples
Test FlowCross-cutting concerns for entire test caseAuthentication, request tracing, debug flags, global config
FolderModule/feature-specific logicEncryption, module headers, feature-specific auth, rate limiting
NodeOne-off endpoint customizationsSpecific timeout, endpoint-specific params, test metadata

Environment-Driven Configuration

$context.config is automatically populated from Environment Variable Groups selected in the UI. QA engineers simply choose an environment from a dropdown—no code changes needed.

Example Hook Using Environment Config:

async function beforeRequest() {
  // Config comes from UI-selected environment
  if ($context.config.debugMode) {
    $request.query.debug = "true";
    $request.query.verbose = "true";
  }
 
  // Switch between test/staging/prod environments
  const baseUrl = $context.config.apiBaseUrl;
  $request.url = `${baseUrl}${$request.path}`;
 
  // Environment-specific auth
  $request.headers["Authorization"] = `Bearer ${$context.config.authToken}`;
}

QA workflow:

  1. Select environment from dropdown: “Staging”, “Production”, “Local”
  2. All hooks automatically use the correct config values
  3. No code changes, no manual setup—just UI selection

Reusable Hooks vs Inline Hooks

Define once in Global Scripts:

// Global Script
async function addGlobalAuth() {
  $request.headers["Authorization"] = `Bearer ${$context.config.authToken}`;
  $request.headers["X-Tenant-ID"] = $context.config.tenantId;
}
 
async function enableDebugMode() {
  if ($context.config.debugMode) {
    $request.query.debug = "true";
    $request.headers["X-Debug-Session"] = $context.traceId;
  }
}

Reference by ID in UI: QA selects “addGlobalAuth” and “enableDebugMode” from dropdown at Test Flow level—applies to all APIs.

Benefits:

  • ✅ Write once, use everywhere
  • ✅ Update once, all tests adapt
  • ✅ QA can apply via UI without coding

Inline Hooks (For Specific Use Cases)

Write directly at Node or Folder level:

// Inline at Node level for a specific slow endpoint
async function beforeRequest() {
  $request.timeout = 120000; // This endpoint takes 2 minutes
  $request.retries = 3;
}

Benefits:

  • ✅ Quick one-off customizations
  • ✅ Self-contained logic
  • ✅ No need to create global function

Best Practices

1. Use Test Flow Hooks for Cross-Cutting Concerns

// ✅ Good: Authentication applies to all APIs
async function beforeRequest() {
  $request.headers["Authorization"] = `Bearer ${$context.config.authToken}`;
  $request.headers["X-Trace-ID"] = $context.traceId;
}

2. Use Folder Hooks for Module-Specific Logic

// ✅ Good: Payment module needs encryption
async function beforeRequest() {
  if ($request.body?.creditCard) {
    $request.body.creditCard = $$PaymentUtils.encrypt($request.body.creditCard);
  }
  $request.headers["X-Payment-API-Version"] = "v2";
}

3. Use Node Hooks for Endpoint-Specific Customizations

// ✅ Good: This specific endpoint needs longer timeout
async function beforeRequest() {
  $request.timeout = 60000;
  $request.body.refundReason = "test_automation";
}

4. Leverage Global Utilities

// ✅ Good: Use global utilities for complex logic
async function beforeRequest() {
  const hash = $$DataUtils.generateRequestHash($request.body);
  $request.headers["X-Request-Hash"] = hash;
 
  if ($$ValidationUtils.requiresEncryption($request.path)) {
    $request.body = $$EncryptionUtils.encryptPayload($request.body);
  }
}

5. Keep Hooks Focused

// ✅ Good: Single responsibility
async function addAuthentication() {
  $request.headers["Authorization"] = `Bearer ${$context.config.authToken}`;
}
 
async function addTracing() {
  $request.headers["X-Trace-ID"] = $context.traceId;
}
 
// ❌ Bad: Doing too much in one hook
async function beforeRequest() {
  // Auth logic
  // Tracing logic
  // Encryption logic
  // Validation logic
  // ... 100 lines of mixed concerns
}

Key Takeaways

  1. 3-Level System - Test Flow → Folder → Node (outer to inner)
  2. Inline or Reference - Write directly or select from global functions
  3. Environment Config - $context.config auto-populated from UI dropdown
  4. Reusable Functions - Define once in Global Scripts, use everywhere
  5. Layer Your Logic - General → Module → Specific
  6. Focus Each Hook - Single responsibility, clear purpose
  7. Leverage Utilities - Call $$Utils for complex operations

For a comprehensive example of the multi-level hook system, see Build a Testing Framework.