Scripting GuideGlobal Scripts

Global Scripts

Global Scripts are the foundation of ReAPI’s scripting architecture. They define reusable functions and utilities that can be referenced throughout your entire testing project—from hooks to assertions to value generators.

Choosing Your Development Approach

Before writing global scripts, evaluate your project’s complexity to choose the best development approach.

In-Browser Development (Simple Projects)

Best for:

  • Quick prototypes and simple testing projects
  • Projects with minimal dependencies (using built-in libraries)
  • Small teams without complex build requirements
  • Learning and experimentation

Pros:

  • ✅ Instant feedback - write and test directly in the browser
  • ✅ No setup required - no local environment needed
  • ✅ Built-in libraries readily available (Lodash, Faker, Zod, Ky, Luxon)
  • ✅ Easy collaboration through ReAPI’s web interface

Cons:

  • ❌ Limited to IIFE/UMD format libraries
  • ❌ No advanced IDE features (full autocomplete, refactoring)
  • ❌ Cannot use complex npm dependencies easily
  • ❌ Harder to manage large codebases

Note: Both in-browser and local development are limited to browser-compatible libraries. Node.js built-in modules are not available in either approach.

Local Development + Build (Complex Projects)

Best for:

  • Enterprise-grade testing frameworks
  • Projects requiring many third-party libraries
  • Teams with complex business logic
  • Projects needing TypeScript, linting, and testing

Recommended when:

  • You need libraries not available as IIFE/UMD
  • Your global scripts exceed 500+ lines
  • You require strong TypeScript support
  • You want to leverage local AI coding assistants
  • You need version control and code review workflows

⚠️ Important Limitation: Your bundled library must be browser-compatible. Node.js-only libraries (e.g., fs, path, crypto native modules) cannot be used, as scripts execute in a browser sandbox environment. Only use libraries that work in browsers.

The Local Development Workflow

For complex projects, develop locally and bundle as an external library:

# 1. Create a local npm project
mkdir my-test-framework
cd my-test-framework
npm init -y
 
# 2. Install dependencies (browser-compatible libraries only)
npm install lodash zod date-fns
npm install -D tsup typescript @types/node
 
# ⚠️ Important: Only install browser-compatible libraries
# ❌ Avoid: fs, path, child_process, os, net, http (Node.js built-ins)
# ✅ Use: lodash, zod, date-fns, axios, validator (browser-compatible)
 
# 3. Write your utilities with full IDE support
# src/index.ts
// src/utils.ts - Full TypeScript support
import { z } from "zod";
import _ from "lodash";
import { format, addDays } from "date-fns";
 
export class $$AnalyticsUtils {
  static schemas = {
    metric: z.object({
      value: z.number(),
      unit: z.string(),
      timestamp: z.number(),
    }),
  };
 
  static formatDate(date: Date): string {
    return format(date, "yyyy-MM-dd");
  }
 
  static calculateGrowth(current: number, previous: number): number {
    if (previous === 0) return current > 0 ? 100 : 0;
    return ((current - previous) / previous) * 100;
  }
}
 
// Export as global for IIFE
if (typeof window !== "undefined") {
  (window as any).$$AnalyticsUtils = $$AnalyticsUtils;
}
// package.json - Configure build
{
  "name": "my-test-framework",
  "scripts": {
    "build": "tsup src/index.ts --format iife --global-name MyTestFramework --minify"
  },
  "devDependencies": {
    "tsup": "^8.0.0",
    "typescript": "^5.0.0"
  }
}
# 4. Build to IIFE format
npm run build
# Output: dist/index.global.js
 
# 5. Host the built file
# Option A: Use your own CDN/server
# Option B: ReAPI Hosted Libraries (coming soon)
# Option C: GitHub Pages, Cloudflare, Vercel, etc.
 
# 6. Register as external library in ReAPI
# libs: ["https://your-domain.com/my-test-framework.js"]

Advantages of Local Development:

  • 🚀 Full npm ecosystem - Use any browser-compatible library, any version
  • 💡 AI-powered development - Leverage GitHub Copilot, Cursor, etc.
  • 🔍 Advanced IDE features - IntelliSense, refactoring, go-to-definition
  • 🧪 Local testing - Unit tests with Vitest/Jest before deployment
  • 📦 TypeScript support - Full type safety and compilation checks
  • 🔄 Version control - Git workflow with code review
  • 🛠️ Build optimization - Tree shaking, minification, bundling
  • 👥 Team collaboration - Standard software development practices

Note: Libraries must be browser-compatible. Node.js-specific modules (fs, net, child_process, etc.) are not supported as scripts run in a browser environment.

Alternative Bundlers

You can use any bundler that outputs IIFE format:

tsup (Recommended):

tsup src/index.ts --format iife --global-name MyFramework --minify

esbuild:

esbuild src/index.ts --bundle --format=iife --global-name=MyFramework --minify --outfile=dist/bundle.js

Rollup:

// rollup.config.js
export default {
  input: "src/index.ts",
  output: {
    file: "dist/bundle.js",
    format: "iife",
    name: "MyFramework",
  },
};

Webpack:

// webpack.config.js
module.exports = {
  entry: "./src/index.ts",
  output: {
    filename: "bundle.js",
    library: "MyFramework",
    libraryTarget: "window",
  },
};

Hosting Your Built Library

Your bundled IIFE file needs to be publicly accessible:

Option 1: ReAPI Hosted Libraries (Coming Soon)

// Upload via ReAPI UI - automatic versioning and CDN
libs: ["https://libs.reapi.com/your-org/my-framework@1.0.0"];

Option 2: GitHub Pages (Free)

# Deploy to GitHub Pages
git add dist/
git commit -m "Build v1.0.0"
git push
 
# Access via:
# https://your-username.github.io/your-repo/dist/bundle.js

Option 3: npm + CDN (jsDelivr/unpkg)

# Publish to npm
npm publish
 
# Use via CDN:
# https://cdn.jsdelivr.net/npm/your-package@1.0.0/dist/bundle.js
# https://unpkg.com/your-package@1.0.0/dist/bundle.js

Option 4: Cloud Storage

  • Cloudflare R2
  • AWS S3 + CloudFront
  • Vercel Blob Storage
  • Azure Blob Storage

Decision Guide

Is your project simple?
├─ Yes → Use In-Browser Development
│   ├─ Built-in libraries sufficient? → Perfect fit
│   └─ Need 1-2 predefined libs? → Still manageable

└─ No → Use Local Development + Build
    ├─ Need 3+ npm packages? → Definitely local
    ├─ 500+ lines of code? → Definitely local
    ├─ Need TypeScript? → Definitely local
    └─ Team of 3+ developers? → Definitely local

⚠️ Both approaches require browser-compatible libraries

Browser Compatibility Checklist:

// ✅ Browser-Compatible (Safe to Use)
import _ from "lodash"; // Data utilities
import { z } from "zod"; // Validation
import axios from "axios"; // HTTP client
import dayjs from "dayjs"; // Date manipulation
import validator from "validator"; // String validation
 
// ❌ Node.js Only (Cannot Use)
import fs from "fs"; // File system
import path from "path"; // Path utilities
import crypto from "crypto"; // Native crypto (use crypto-js instead)
import http from "http"; // HTTP server
import child_process from "child_process"; // Process execution

Migration Path

You can start simple and migrate later:

  1. Start: Write in-browser for prototyping
  2. Grow: Add complexity using predefined libraries
  3. Scale: Migrate to local development when hitting limitations
  4. Export: Copy in-browser code to local project
  5. Enhance: Add TypeScript, tests, npm dependencies
  6. Build: Bundle to IIFE and host publicly
  7. Deploy: Register as external library in ReAPI

What Are Global Scripts?

Global Scripts serve as a central library where developers write code once that QA engineers can use everywhere. They typically contain:

  1. Utility Classes ($$ prefix) - Static classes with helper methods
  2. Reusable Hook Functions - Referenced by ID in test flows, folders, or nodes
  3. Value Generators - Functions that generate test data
  4. Custom Assertions - Functions that validate complex business rules

The $$ Prefix Convention

Utility classes in Global Scripts use the $$ prefix to make them globally accessible and prevent naming conflicts.

// ❌ Wrong: Regular variable (not accessible globally)
const utils = {
  formatDate() {
    /* ... */
  },
};
 
// ✅ Correct: Global utility class with $$ prefix
class $$DateUtils {
  static formatDate(date) {
    return dayjs(date).format("YYYY-MM-DD");
  }
 
  static isRecent(timestamp, hoursAgo = 24) {
    const cutoff = Date.now() - hoursAgo * 3600000;
    return timestamp > cutoff;
  }
}

Why the $$ prefix?

  • Global scope: Accessible in all scripts (hooks, generators, assertions)
  • Naming clarity: Instantly recognizable as a global utility
  • Collision prevention: Avoids conflicts with built-in variables

Built-in Runtime Libraries

Global Scripts have access to powerful runtime libraries:

  • Lodash (_) - Utility functions
  • Faker - Mock data generation
  • Zod (z) - Schema validation (v4.x)
  • Ky - Modern HTTP client
  • Luxon - Date/time manipulation

📚 See complete library documentation →

Includes predefined libraries (CryptoJS, DayJS, Axios, etc.), custom library support, and the ESM Bundler Service.

Script Patterns

1. Utility Classes (Most Common)

Encapsulate related helper methods in a static class:

import { z } from "zod";
 
class $$AnalyticsUtils {
  // Schema definitions for reuse
  static schemas = {
    metric: z.object({
      value: z.number(),
      unit: z.string(),
      timestamp: z.number(),
    }),
 
    dateRange: z.object({
      start: z.string().datetime(),
      end: z.string().datetime(),
    }),
  };
 
  // Date/time utilities
  static formatTimestamp(timestamp) {
    return dayjs(timestamp).format("YYYY-MM-DD HH:mm:ss");
  }
 
  static getRelativeTime(timestamp) {
    return dayjs(timestamp).fromNow();
  }
 
  // Calculation utilities
  static calculateGrowth(current, previous) {
    if (previous === 0) return current > 0 ? 100 : 0;
    return ((current - previous) / previous) * 100;
  }
 
  // Validation utilities
  static isValidMetric(metric) {
    return this.schemas.metric.safeParse(metric).success;
  }
 
  // Data integrity
  static generateDataHash(data) {
    return CryptoJS.SHA256(JSON.stringify(data)).toString();
  }
 
  // Remote data access
  static async fetchTestData(endpoint, options = {}) {
    const baseUrl = "https://test-data-api.example.com";
    const response = await ky
      .get(`${baseUrl}${endpoint}`, {
        timeout: 10000,
        ...options,
      })
      .json();
    return response;
  }
}

Usage in other scripts:

// In a hook
async function afterResponse() {
  const formattedTime = $$AnalyticsUtils.formatTimestamp(
    $response.data.timestamp
  );
  $context.displayTime = formattedTime;
}
 
// In a value generator
async function generateMetric(value) {
  return {
    value,
    unit: "requests",
    timestamp: Date.now(),
    hash: $$AnalyticsUtils.generateDataHash({ value }),
  };
}
 
// In a custom assertion
async function isValidAnalyticsMetric(metric) {
  const isValid = $$AnalyticsUtils.isValidMetric(metric);
  $addAssertionResult({
    passed: isValid,
    message: isValid ? "Valid metric" : "Invalid metric",
    operator: "isValidAnalyticsMetric",
    leftValue: metric,
    rightValue: "valid metric schema",
  });
}

2. Reusable Hook Functions

Define hooks once, reference them by ID at test flow, folder, or node level:

// Global hook: Authentication for all APIs
async function addGlobalAuth() {
  $request.headers["Authorization"] = `Bearer ${$context.config.authToken}`;
  $request.headers["X-Tenant-ID"] = $context.config.tenantId;
}
 
// Global hook: Debug mode enhancement
async function enableDebugMode() {
  if ($context.config.debugMode) {
    $request.query.debug = "true";
    $request.query.verbose = "true";
    $request.headers["X-Debug-Session"] = $context.traceId;
  }
}
 
// Global hook: Response tracking
async function trackResponseMetrics() {
  $context.apiCallLog = $context.apiCallLog || [];
  $context.apiCallLog.push({
    url: $request.url,
    status: $response.status,
    time: $response.time,
    timestamp: Date.now(),
  });
}

Referenced in UI: QA can select these hooks by ID from dropdowns at:

  • Test Flow level: Apply to all APIs in the test case
  • Folder level: Apply to all APIs in a folder hierarchy
  • Node level: Apply to a specific API request

3. Complex Value Generators

Generators can call other global utilities:

async function generateTenantMetrics(options) {
  // Validate parameters with Zod
  const config = $$AnalyticsUtils.schemas.generatorOptions.parse(options || {});
 
  const metrics = [];
  for (let i = 0; i < config.count; i++) {
    const value = _.random(config.min, config.max);
    metrics.push({
      id: faker.string.uuid(),
      value,
      unit: config.unit,
      timestamp: Date.now(),
      hash: $$AnalyticsUtils.generateDataHash({ value }),
    });
  }
 
  return metrics;
}
 
async function fetchTestUser(userId) {
  // Use global utility to fetch remote data
  return await $$AnalyticsUtils.fetchTestData(`/users/${userId}`);
}

4. Complex Custom Assertions

Assertions can leverage global utilities and Zod:

async function isValidMetricWithSLA(metric) {
  try {
    // Use global utility schema
    const parseResult = $$AnalyticsUtils.schemas.metric.safeParse(metric);
 
    if (!parseResult.success) {
      $addAssertionResult({
        passed: false,
        message: `Invalid metric schema: ${parseResult.error.message}`,
        operator: "isValidMetricWithSLA",
        leftValue: metric,
        rightValue: "valid metric with SLA",
      });
      return;
    }
 
    // Business rule validation
    const withinSLA = $$AnalyticsUtils.isWithinSLA(metric.value, metric.unit);
 
    $addAssertionResult({
      passed: withinSLA,
      message: withinSLA
        ? "Metric meets SLA requirements"
        : "Metric violates SLA",
      operator: "isValidMetricWithSLA",
      leftValue: metric,
      rightValue: "SLA threshold",
    });
  } catch (error) {
    $addAssertionResult({
      passed: false,
      message: `Error validating metric: ${error.message}`,
      operator: "isValidMetricWithSLA",
      leftValue: metric,
      rightValue: "valid metric with SLA",
    });
  }
}

Best Practices

1. Use Static Classes for Organization

// ✅ Good: Organized by domain
class $$AuthUtils {
  /* ... */
}
class $$DataUtils {
  /* ... */
}
class $$ValidationUtils {
  /* ... */
}
 
// ❌ Bad: Scattered functions
function validateUser() {
  /* ... */
}
function encryptData() {
  /* ... */
}

2. Define Reusable Zod Schemas

class $$Schemas {
  static user = z.object({
    id: z.number().positive(),
    email: z.string().email(),
    role: z.enum(["admin", "user", "guest"]),
  });
 
  static apiResponse = z.object({
    status: z.string(),
    data: z.any(),
    timestamp: z.number(),
  });
}
 
// Use in assertions
async function isValidUser(value) {
  const result = $$Schemas.user.safeParse(value);
  // ...
}

3. Use Async for Remote Operations

class $$DataFetcher {
  // ✅ Async for HTTP calls
  static async fetchTestData(endpoint) {
    return await ky.get(endpoint).json();
  }
 
  // ✅ Sync for pure calculations
  static calculateHash(data) {
    return CryptoJS.SHA256(JSON.stringify(data)).toString();
  }
}

4. Provide Clear Method Names

// ✅ Good: Clear intent
static formatTimestampForDisplay(timestamp) { /* ... */ }
static validateMetricAgainstSLA(metric) { /* ... */ }
static fetchUserFromTestAPI(userId) { /* ... */ }
 
// ❌ Bad: Ambiguous
static format(t) { /* ... */ }
static check(m) { /* ... */ }
static get(id) { /* ... */ }

5. Document Complex Utilities

class $$EncryptionUtils {
  /**
   * Encrypts sensitive data using AES encryption
   * @param {string} data - The data to encrypt
   * @param {string} key - The encryption key (from $context.config)
   * @returns {string} Base64 encoded encrypted data
   */
  static encryptSensitiveData(data, key) {
    return CryptoJS.AES.encrypt(data, key).toString();
  }
}

Architecture Pattern

Global Scripts form the foundation layer of your testing framework:

┌─────────────────────────────────────────┐
│  Test Flows & Test Cases               │  ← QA creates tests
├─────────────────────────────────────────┤
│  Hooks (reference global functions)    │  ← QA selects from dropdown
├─────────────────────────────────────────┤
│  Custom Assertions & Generators        │  ← QA uses via UI
├─────────────────────────────────────────┤
│  Global Scripts ($$Utils, functions)   │  ← Developer writes once
└─────────────────────────────────────────┘

The empowerment model:

  • Developers: Write global utilities once (10-30 minutes)
  • QA: Reference/call them everywhere (10 seconds each use)
  • Result: Reusable, maintainable, enterprise-grade testing framework

Common Patterns

Pattern 1: Module-Specific Utilities

class $$PaymentUtils {
  static encryptCardNumber(cardNumber) {
    return CryptoJS.AES.encrypt(
      cardNumber,
      $context.config.encryptionKey
    ).toString();
  }
 
  static validatePaymentResponse(response) {
    return response.status === "approved" && response.transactionId;
  }
}

Pattern 2: Cross-Cutting Concerns

class $$TraceUtils {
  static generateTraceId() {
    return `trace-${Date.now()}-${_.random(1000, 9999)}`;
  }
 
  static addTraceHeaders(request) {
    request.headers["X-Trace-ID"] = $context.traceId || this.generateTraceId();
    request.headers["X-Request-Time"] = new Date().toISOString();
  }
}

Pattern 3: Test Data Management

class $$TestDataManager {
  static async fetchTestUser(criteria = {}) {
    const params = new URLSearchParams(criteria);
    return await ky.get(`https://test-data.example.com/users?${params}`).json();
  }
 
  static async createTestUser(userData) {
    return await ky
      .post("https://test-data.example.com/users", {
        json: userData,
      })
      .json();
  }
 
  static async cleanupTestData(userId) {
    await ky.delete(`https://test-data.example.com/users/${userId}`);
  }
}

Key Takeaways

  1. Global Scripts = Reusable Foundation - Write once, use everywhere
  2. $$ Prefix for Utilities - Global classes use $$ClassName convention
  3. Static Classes Recommended - Organize related methods together
  4. Leverage Built-in Libraries - Zod, lodash, faker, ky, CryptoJS, DayJS
  5. Define Reusable Schemas - Zod schemas in static properties for consistency
  6. Developer Writes, QA Uses - 10-30 min investment → infinite QA leverage
  7. Enable Complex Testing - Without requiring QA to become programmers

For practical examples, see Build a Testing Framework guide.