API Hooks
API hooks modify requests before sending or process responses after receiving.
Export Pattern
// src/hooks/index.ts
import type { ApiHook } from "../types";
export const $$ApiHooks: ApiHook[] = [
authSignatureHook,
responseValidationHook,
];Hook Types
| Type | Function Name | When It Runs |
|---|---|---|
beforeRequest | beforeRequest | Just before API request is sent |
afterResponse | afterResponse | Just after API response is received |
Before Request Hook
Modify the outgoing request:
export const authSignatureHook: ApiHook = {
id: "auth-signature",
name: "Auth Signature",
description: "Adds HMAC signature to requests",
enabled: true,
deprecated: false,
tested: true,
type: "beforeRequest",
function: async function beforeRequest() {
const timestamp = Date.now().toString();
const payload = `${$request.method}${$request.url}${timestamp}`;
const secret = $secrets.apiSecret;
const signature = CryptoJS.HmacSHA256(payload, secret).toString();
$request.headers["X-Signature"] = signature;
$request.headers["X-Timestamp"] = timestamp;
},
};Available in beforeRequest
$request.url // Request URL
$request.method // HTTP method
$request.headers // Request headers (read/write)
$request.query // Query parameters (read/write)
$request.body // Request body (read/write)
$request.timeout // Request timeout (read/write)
$context // Test context (read/write)
$vars // Variable group values (read)
$secrets // Decrypted secrets (read)After Response Hook
Process the response:
export const responseValidationHook: ApiHook = {
id: "response-validation",
name: "Response Validation",
description: "Validates response structure and logs errors",
enabled: true,
deprecated: false,
tested: true,
type: "afterResponse",
function: async function afterResponse() {
// Store response time in context
$context.lastResponseTime = $response.responseTime;
// Validate response structure
if ($response.status >= 400) {
$log("error", `API Error: ${$response.status}`, {
url: $request.url,
body: $response.body,
});
}
// Extract and store common fields
if ($response.body?.data?.id) {
$context.lastResourceId = $response.body.data.id;
}
},
};Available in afterResponse
$response.status // HTTP status code
$response.statusText // Status text
$response.headers // Response headers
$response.body // Parsed response body
$response.responseTime // Time in milliseconds
$request // Original request (read only)
$context // Test context (read/write)
$vars // Variable group values (read)
$secrets // Decrypted secrets (read)Example: Request Encryption
export const encryptRequestBody: ApiHook = {
id: "encrypt-request-body",
name: "Encrypt Request Body",
description: "Encrypts request body for secure endpoints",
enabled: true,
deprecated: false,
tested: true,
type: "beforeRequest",
function: async function beforeRequest() {
if ($request.body && $context.config?.encryptRequests) {
const key = $secrets.encryptionKey;
const encrypted = CryptoJS.AES.encrypt(
JSON.stringify($request.body),
key
).toString();
$request.body = { encrypted };
$request.headers["X-Encrypted"] = "true";
}
},
};Example: Response Decryption
export const decryptResponseBody: ApiHook = {
id: "decrypt-response-body",
name: "Decrypt Response Body",
description: "Decrypts encrypted response bodies",
enabled: true,
deprecated: false,
tested: true,
type: "afterResponse",
function: async function afterResponse() {
if ($response.headers["x-encrypted"] === "true") {
const key = $secrets.encryptionKey;
const decrypted = CryptoJS.AES.decrypt(
$response.body.encrypted,
key
).toString(CryptoJS.enc.Utf8);
$response.body = JSON.parse(decrypted);
}
},
};Example: Retry Logic Setup
export const configureRetry: ApiHook = {
id: "configure-retry",
name: "Configure Retry",
description: "Sets up retry configuration for flaky endpoints",
enabled: true,
deprecated: false,
tested: true,
type: "beforeRequest",
function: async function beforeRequest() {
// Mark request start time
$context.requestStartTime = Date.now();
// Configure timeout based on endpoint
if ($request.url.includes("/reports")) {
$request.timeout = 60000; // Reports are slow
}
},
};Best Practices
Keep Hooks Lightweight
// ✅ Good: Quick operations
function: async function beforeRequest() {
$request.headers["X-Trace-ID"] = faker.string.uuid();
}
// ❌ Bad: Heavy operations
function: async function beforeRequest() {
await heavyDatabaseQuery(); // Slows every request
}Use Hooks for Cross-Cutting Concerns
- Authentication signatures
- Request/response encryption
- Logging and tracing
- Common header injection
- Response normalization
Don’t Duplicate Logic
// ✅ Good: Centralized auth logic
// Hook handles auth for ALL requests
// ❌ Bad: Auth logic in every test
// Each test manually adds auth headersUsage in Tests
Once synced and enabled:
- Test Flow Level → Apply to all APIs in flow
- Folder Level → Apply to APIs in a folder
- Node Level → Apply to specific API node