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

TypeFunction NameWhen It Runs
beforeRequestbeforeRequestJust before API request is sent
afterResponseafterResponseJust 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 headers

Usage 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