@probitas-test/builder

Builder module for creating scenario definitions with a fluent, type-safe API.

This package provides the scenario fn function that returns a builder for constructing scenario definitions. The builder uses method chaining to define resources, setup functions, and test steps with full TypeScript type inference.

PackageDescription
@probitas-test/coreCore type definitions used by this builder
@probitas-test/runnerExecutes scenarios built with this package

Key Features

  • Fluent API: Chain methods naturally to build complex scenarios
  • Type-safe context: Each step receives typed access to previous step results
  • Resource management: Register resources with automatic lifecycle handling
  • Setup/Cleanup: Define setup functions with automatic cleanup support
  • Configurable defaults: Override timeout and retry settings at any level

Core Exports

Installation

npm install @probitas-test/builder

Functions

function

#scenario

function scenario(name: string, options?: ScenarioOptions): ScenarioBuilderInit

Create a new scenario builder with a fluent API.

This is the primary entry point for defining scenarios. It returns a builder that provides a type-safe, chainable API for constructing test scenarios.

The builder pattern enables:

  • Type-safe chaining: Return values flow through steps with full type inference
  • Resource management: Automatic lifecycle handling with Disposable support
  • Flexible configuration: Override defaults at scenario or step level
Parameters
  • namestring

    Human-readable scenario name (shown in test reports)

  • Optional configuration for tags, timeouts, and retry behavior

Returns

ScenarioBuilderInit

New ScenarioBuilderInit instance ready for chaining

Examples

Basic scenario with type-safe step chaining

import { scenario } from "@probitas-test/builder";

// Mock user service for example
const createUser = (_data: { email: string }) =>
  Promise.resolve({ id: "user-123" });
const verifyEmail = (_userId: string) => Promise.resolve();

export default scenario("User Registration")
  .step("Create user", async () => {
    const user = await createUser({ email: "test@example.com" });
    return { userId: user.id };
  })
  .step("Verify email", async (ctx) => {
    // ctx.previous is typed as { userId: string }
    await verifyEmail(ctx.previous.userId);
  })
  .build();

Scenario with tags for filtering

import { scenario } from "@probitas-test/builder";

scenario("Payment Integration", {
  tags: ["integration", "payment", "slow"],
  stepOptions: { timeout: 60000 }  // 1 minute timeout for all steps
})
  .step("Process payment", async () => {
    return { success: true };
  })
  .build();

// Run with: probitas run -s "tag:payment"

Scenario with resources and setup

import { scenario } from "@probitas-test/builder";

// Mock Database for example
const Database = {
  connect: () =>
    Promise.resolve({
      rollback: () => {},
      insert: (_data: { id: number }) => {},
      query: (_sql: string) => [{ id: 1 }],
    }),
};

scenario("Database Test")
  .resource("db", async () => await Database.connect())
  .setup((ctx) => {
    // Run migrations
    return () => ctx.resources.db.rollback();
  })
  .step("Insert data", (ctx) => ctx.resources.db.insert({ id: 1 }))
  .step("Query data", (ctx) => ctx.resources.db.query("SELECT * FROM users"))
  .build();

Types

type

#BuilderStepContext

type BuilderStepContext<P = unknown, A extends readonly unknown[] = readonly unknown[], R extends Record<string, unknown> = Record<string, unknown>> = StepContext
  & { readonly previous: P; readonly resources: R; readonly results: A }

Execution context provided to steps, resources, and setup hooks.

The context provides access to:

  • Previous step results with full type inference
  • All accumulated results as a typed tuple
  • Named resources registered with .resource()
  • Shared storage for cross-step communication
  • Abort signal for timeout and cancellation handling
Examples

Accessing previous result

import { scenario } from "@probitas-test/builder";

scenario("Chained Steps")
  .step("First", () => ({ id: 123 }))
  .step("Second", (ctx) => {
    console.log(ctx.previous.id);  // 123 (typed as number)
  })
  .build();

Using shared store

import { scenario } from "@probitas-test/builder";

scenario("Store Example")
  .setup((ctx) => {
    ctx.store.set("startTime", Date.now());
  })
  .step("Check duration", (ctx) => {
    const start = ctx.store.get("startTime") as number;
    console.log(`Elapsed: ${Date.now() - start}ms`);
  })
  .build();
type

#BuilderStepDefinition

type BuilderStepDefinition<T = unknown, P = unknown, A extends readonly unknown[] = readonly unknown[], R extends Record<string, unknown> = Record<string, unknown>> = StepDefinition & { readonly fn: BuilderStepFunction<T, P, A, R> }

Immutable definition of a scenario step.

Contains all information needed to execute a single step: the step function, its options, and debugging metadata.

Step definitions are created by the builder and consumed by the runner. They are immutable and should not be modified after creation.

type

#BuilderStepFunction

type BuilderStepFunction<T = unknown, P = unknown, A extends readonly unknown[] = readonly unknown[], R extends Record<string, unknown> = Record<string, unknown>> = (ctx: BuilderStepContext<P, A, R>) => T | Promise<T>

Function signature for step execution.

A step function receives the execution context and returns a value (sync or async) that becomes available to subsequent steps.

Examples

Sync step returning data

import type { BuilderStepFunction } from "@probitas-test/builder";

const step: BuilderStepFunction<{ name: string }> = (_ctx) => {
  return { name: "Alice" };
};

Async step with API call

import type { BuilderStepFunction } from "@probitas-test/builder";

type User = { id: string; name: string };

const step: BuilderStepFunction<User> = async (ctx) => {
  const response = await fetch("/api/user", { signal: ctx.signal });
  return response.json();
};
Search Documentation