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:
- Test Flow Level - Global hooks for the entire test case
- Folder Level - Module/feature-specific hooks (with folder hierarchy)
- 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 SentWhy 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
asyncfunction. - Request Access: Use
$requestto read and modify the outgoing HTTP request. - Context Access: Use
$contextto read and write to the test context. - Auto-Instrumentation: Changes to
$requestand$contextare 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
| Level | Best For | Examples |
|---|---|---|
| Test Flow | Cross-cutting concerns for entire test case | Authentication, request tracing, debug flags, global config |
| Folder | Module/feature-specific logic | Encryption, module headers, feature-specific auth, rate limiting |
| Node | One-off endpoint customizations | Specific 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:
- Select environment from dropdown: “Staging”, “Production”, “Local”
- All hooks automatically use the correct config values
- No code changes, no manual setup—just UI selection
Reusable Hooks vs Inline Hooks
Reusable Hooks (Recommended for Common Logic)
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
- 3-Level System - Test Flow → Folder → Node (outer to inner)
- Inline or Reference - Write directly or select from global functions
- Environment Config -
$context.configauto-populated from UI dropdown - Reusable Functions - Define once in Global Scripts, use everywhere
- Layer Your Logic - General → Module → Specific
- Focus Each Hook - Single responsibility, clear purpose
- Leverage Utilities - Call
$$Utilsfor complex operations
For a comprehensive example of the multi-level hook system, see Build a Testing Framework.