@probitas-test/runner

Test execution and orchestration engine for Probitas scenarios.

This package provides the ScenarioRunner class that executes scenario definitions built with @probitas-test/builder. It handles the complete test lifecycle including resource initialization, setup/cleanup hooks, step execution with retry logic, and result reporting.

PackageDescription
@probitas-test/builderBuild scenario definitions to execute
@probitas-test/coreCore type definitions
@probitas-test/reporterOutput formatters for results
probitasCLI that uses these reporters

Key Features

  • Concurrent execution: Run multiple scenarios in parallel with configurable concurrency
  • Failure control: Stop after N failures or continue through all tests
  • Resource lifecycle: Automatic initialization and disposal of resources
  • Retry logic: Built-in retry with linear/exponential backoff strategies
  • Reporter integration: Pluggable reporters for custom output formatting
  • Abort support: Cancel running tests via AbortSignal

Core Exports

  • ScenarioRunner - Main class for executing scenarios
  • Skip - Exception class to skip scenarios conditionally
  • Reporter - Interface for observing test execution events
  • RunOptions - Configuration options for test runs
  • RunResult - Aggregated results from a test run
  • ScenarioResult - Result from executing a single scenario
  • StepResult - Result from executing a single step

Error Types

Installation

npm install @probitas-test/runner

Classes

class

#Runner

class Runner

Top-level test runner that orchestrates execution of multiple scenarios.

The Runner manages:

  • Parallel execution with concurrency control (maxConcurrency)
  • Early stopping on failures (maxFailures)
  • Test lifecycle events through the Reporter interface
  • Aggregated results in RunResult
Examples
import { Runner } from "@probitas-test/runner";
import type { Reporter } from "@probitas-test/runner";
import type { ScenarioDefinition } from "@probitas-test/core";

const reporter: Reporter = {};
const scenarios: ScenarioDefinition[] = [];
const runner = new Runner(reporter);

const result = await runner.run(scenarios, {
  maxConcurrency: 4,
  maxFailures: 1,
});
console.log(result);
Constructor
new Runner(reporter: Reporter)

Create a new Runner with the given reporter.

NameDescription
reporterReporter instance for lifecycle events
run()Execute all scenarios and return aggregated results.
Properties
  • readonlyreporterReporter

    Reporter instance for lifecycle events

Methods
run(
  scenarios: readonly ScenarioDefinition[],
  options?: RunOptions,
): Promise<RunResult>

Execute all scenarios and return aggregated results.

Emits events through the Reporter:

  1. onRunStart - Before execution starts
  2. For each scenario (concurrently):
    • Delegates to ScenarioRunner for scenario-level execution
    • ScenarioRunner emits scenario and step level events
  3. onRunEnd - After all scenarios complete with RunResult
Parameters
class

#ScenarioTimeoutError

class ScenarioTimeoutError extends Error
ExtendsError

Error thrown when a scenario execution times out.

Contains structured information about the timeout including the scenario name, timeout duration, total elapsed time, and optionally the step being executed when timeout occurred.

Constructor
new ScenarioTimeoutError(
  scenarioName: string,
  timeoutMs: number,
  elapsedMs: number,
  options?: ErrorOptions & { currentStepIndex?: number; currentStepName?: string },
)
NameDescription
currentStepIndex—
currentStepName—
elapsedMs—
name—
scenarioName—
timeoutMs—
timeoutSignal()Creates an AbortSignal that aborts with ScenarioTimeoutError after the specified timeout.
Properties
  • readonlycurrentStepIndex?number
  • readonlycurrentStepName?string
  • readonlyelapsedMsnumber
  • readonlyname"ScenarioTimeoutError"
  • readonlyscenarioNamestring
  • readonlytimeoutMsnumber
Methods
static timeoutSignal(
  timeout: number,
  params: { scenarioName: string },
): AbortSignal & Disposable

Creates an AbortSignal that aborts with ScenarioTimeoutError after the specified timeout.

Parameters
  • timeoutnumber

    Timeout duration in milliseconds

  • params{ scenarioName: string }

    Scenario information for the error

class

#StepTimeoutError

class StepTimeoutError extends Error
ExtendsError

Error thrown when a step execution times out.

Contains structured information about the timeout including the step name, timeout duration, retry context, and total elapsed time.

Constructor
new StepTimeoutError(
  stepName: string,
  timeoutMs: number,
  attemptNumber: number,
  elapsedMs: number,
  options?: ErrorOptions,
)
NameDescription
attemptNumber—
elapsedMs—
name—
stepName—
timeoutMs—
timeoutSignal()Creates an AbortSignal that aborts with StepTimeoutError after the specified timeout.
Properties
  • readonlyattemptNumbernumber
  • readonlyelapsedMsnumber
  • readonlyname"StepTimeoutError"
  • readonlystepNamestring
  • readonlytimeoutMsnumber
Methods
static timeoutSignal(
  timeout: number,
  params: { attemptNumber: number; stepName: string },
): AbortSignal & Disposable

Creates an AbortSignal that aborts with StepTimeoutError after the specified timeout.

Parameters
  • timeoutnumber

    Timeout duration in milliseconds

  • params{ attemptNumber: number; stepName: string }

    Step information for the error

Interfaces

interface

#Reporter

interface Reporter

Reporter interface for receiving test execution events.

All callbacks receive serializable metadata types (ScenarioMetadata, StepMetadata) instead of definition types, enabling use across process/thread boundaries (e.g., Web Workers).

NameDescription
onRunEnd()Called when test run completes
onRunStart()Called when the test run starts, before any scenarios execute.
onScenarioEnd()Called when scenario completes
onScenarioStart()Called when a scenario begins execution.
onStepEnd()Called when step completes (passed, failed, or skipped).
onStepStart()Called when step starts
Methods
onRunEnd?(
  scenarios: readonly ScenarioMetadata[],
  result: RunResult,
): void | Promise<void>

Called when test run completes

Parameters
onRunStart?(scenarios: readonly ScenarioMetadata[]): void | Promise<void>

Called when the test run starts, before any scenarios execute.

Parameters
onScenarioEnd?(
  scenario: ScenarioMetadata,
  result: ScenarioResult,
): void | Promise<void>

Called when scenario completes

Parameters
onScenarioStart?(scenario: ScenarioMetadata): void | Promise<void>

Called when a scenario begins execution.

Parameters
onStepEnd?(
  scenario: ScenarioMetadata,
  step: StepMetadata,
  result: StepResult,
): void | Promise<void>

Called when step completes (passed, failed, or skipped).

The result contains status-specific information:

  • If status is "passed": contains value from step execution
  • If status is "failed" or "skipped": contains error information
Parameters
onStepStart?(
  scenario: ScenarioMetadata,
  step: StepMetadata,
): void | Promise<void>

Called when step starts

Parameters
interface

#RunFilter

interface RunFilter

Filter configuration for selecting scenarios to run.

Allows filtering scenarios by tags and/or name pattern. Both conditions must match if both are specified (AND logic).

Examples

Filter by tags

const filter: RunFilter = {
  tags: ["api", "integration"]  // Must have BOTH tags
};

Filter by name pattern

const filter: RunFilter = {
  pattern: /login/i  // Name must contain "login"
};
NameDescription
patternPattern to match against scenario name
tagsTags that scenarios must have (all must match)
Properties
  • readonlypattern?string | RegExp

    Pattern to match against scenario name

  • readonlytags?readonly string[]

    Tags that scenarios must have (all must match)

interface

#RunOptions

interface RunOptions

Configuration options for the scenario runner.

Controls execution behavior including concurrency, failure handling, and reporting.

Examples

Sequential execution with fail-fast

import type { RunOptions } from "@probitas-test/runner";

const options: RunOptions = {
  maxConcurrency: 1,  // Run one at a time
  maxFailures: 1      // Stop after first failure
};
console.log(options);

Parallel execution with limit

import type { RunOptions } from "@probitas-test/runner";

const options: RunOptions = {
  maxConcurrency: 4   // Run up to 4 scenarios at once
};
console.log(options);

With stepOptions

import type { RunOptions } from "@probitas-test/runner";

const options: RunOptions = {
  stepOptions: {
    timeout: 60000,
    retry: { maxAttempts: 3, backoff: "exponential" }
  }
};
console.log(options);
NameDescription
maxConcurrencyMaximum number of scenarios to run in parallel.
maxFailuresMaximum number of failures before stopping the run.
signalAbort signal for external cancellation.
stepOptionsDefault step options applied when step uses default values.
timeoutTimeout for scenario execution in milliseconds.
Properties
  • readonlymaxConcurrency?number

    Maximum number of scenarios to run in parallel.

    • undefined or 0: Unlimited (all scenarios run in parallel)
    • 1: Sequential execution (one at a time)
    • n: Run up to n scenarios concurrently
  • readonlymaxFailures?number

    Maximum number of failures before stopping the run.

    • undefined: Continue all scenarios regardless of failures
    • 1: Fail-fast (stop immediately on first failure)
    • n: Stop after n failures
  • readonlysignal?AbortSignal

    Abort signal for external cancellation.

    When aborted, running scenarios complete but no new ones start. If both timeout and signal are provided, either one can trigger cancellation.

  • readonlystepOptions?StepOptions

    Default step options applied when step uses default values.

    These options override builder defaults but are themselves overridden by scenario-level or step-level options.

    Priority: step options > scenario.stepOptions > config.stepOptions > defaults

  • readonlytimeout?number

    Timeout for scenario execution in milliseconds.

    • undefined: No timeout (unlimited execution time)
    • 0: No timeout
    • n: Timeout after n milliseconds

    When timeout occurs, a ScenarioTimeoutError is thrown. This is independent from step-level timeouts.

interface

#RunResult

interface RunResult

Summary of all scenario executions in a test run.

Provides aggregate statistics and detailed results for the entire run. Passed to Reporter.onRunEnd after all scenarios complete.

Examples
import type { RunResult } from "@probitas-test/runner";

const summary: RunResult = {
  total: 10,
  passed: 8,
  failed: 1,
  skipped: 1,
  duration: 5432,
  scenarios: []
};

console.log(`${summary.passed}/${summary.total} passed`);
// → "8/10 passed"
NameDescription
durationTotal execution time in milliseconds
failedNumber of scenarios that failed
passedNumber of scenarios that passed
scenariosDetailed result for each scenario
skippedNumber of scenarios that were skipped
totalTotal number of scenarios in the run
Properties
  • readonlydurationnumber

    Total execution time in milliseconds

  • readonlyfailednumber

    Number of scenarios that failed

  • readonlypassednumber

    Number of scenarios that passed

  • readonlyscenariosreadonly ScenarioResult[]

    Detailed result for each scenario

  • readonlyskippednumber

    Number of scenarios that were skipped

  • readonlytotalnumber

    Total number of scenarios in the run

interface

#ScenarioContext

interface ScenarioContext

Runtime context for scenario execution.

Provides access to scenario metadata, accumulated results, shared storage, and resources during scenario execution. This is the "live" counterpart to the static ScenarioDefinition.

Examples

ScenarioContext structure

import type { ScenarioContext } from "@probitas-test/runner";

// ScenarioContext is created by the runner for each scenario execution
const ctx: ScenarioContext = {
  name: "My Test",
  tags: ["api", "integration"],
  results: [],
  store: new Map(),
  resources: {},
  signal: undefined,
};
console.log(`Running: ${ctx.name}`);
console.log(`Tags: ${ctx.tags.join(", ")}`);
console.log(`Previous results: ${ctx.results.length}`);
NameDescription
nameHuman-readable scenario name
resourcesNamed resources registered with `.resource()`
resultsArray of all exec step results so far
signalAbort signal (fires on timeout or manual cancellation)
storeShared key-value storage for cross-step communication
tags—
Properties
  • readonlynamestring

    Human-readable scenario name

  • readonlyresourcesRecord<string, unknown>

    Named resources registered with .resource()

  • readonlyresultsunknown[]

    Array of all exec step results so far

  • readonlysignal?AbortSignal

    Abort signal (fires on timeout or manual cancellation)

  • readonlystoreMap<string, unknown>

    Shared key-value storage for cross-step communication

  • readonlytagsreadonly string[]

Functions

function

#toScenarioMetadata

function toScenarioMetadata(scenario: ScenarioDefinition): ScenarioMetadata

Convert ScenarioDefinition to serializable ScenarioMetadata

Removes the fn property from each step to make the result JSON-serializable.

Parameters
function

#toStepMetadata

function toStepMetadata(step: StepDefinition): StepMetadata

Convert StepDefinition to serializable StepMetadata

Removes the fn property to make the result JSON-serializable.

Parameters

Types

type

#ScenarioResult

type ScenarioResult = {
    readonly duration: number;
    readonly metadata: ScenarioMetadata;
    readonly status: "passed";
    readonly steps: readonly StepResult[];
  }
  | {
    readonly duration: number;
    readonly error: unknown;
    readonly metadata: ScenarioMetadata;
    readonly status: "failed" | "skipped";
    readonly steps: readonly StepResult[];
  }

Result from executing a complete scenario.

Contains the overall scenario status, timing, all step results, and any error or skip reason. Passed to reporters and included in the final RunResult.

Examples

Passed scenario

import type { ScenarioResult } from "@probitas-test/runner";

const result: ScenarioResult = {
  metadata: { name: "Login Flow", tags: [], steps: [], origin: { path: "", line: 0 } },
  status: "passed",
  duration: 1250,
  steps: [{ metadata: { kind: "step", name: "step1", timeout: 30000, retry: { maxAttempts: 1, backoff: "linear" } }, status: "passed", value: null, duration: 100 }]
};
console.log(result);

Skipped scenario

import type { ScenarioResult } from "@probitas-test/runner";

const result: ScenarioResult = {
  metadata: { name: "Premium Feature", tags: [], steps: [], origin: { path: "", line: 0 } },
  status: "skipped",
  duration: 5,
  steps: [],
  error: "Feature flag not enabled"
};
console.log(result);
type

#StepResult

type StepResult = {
    readonly duration: number;
    readonly metadata: StepMetadata;
    readonly status: "passed";
    readonly value: unknown;
  }
  | {
    readonly duration: number;
    readonly error: unknown;
    readonly metadata: StepMetadata;
    readonly status: "failed" | "skipped";
  }

Result from executing a single step.

StepResult is a discriminated union type - the fields available depend on the status:

  • "passed": Contains value (return value from step function)
  • "failed": Contains error (exception that was thrown)
  • "skipped": Contains error (skip reason)

This type design ensures type-safe field access. Reporters should check result.status to safely access status-specific fields.

Examples

Passed step result

import type { StepResult } from "@probitas-test/runner";
import type { StepMetadata } from "@probitas-test/core";

const metadata: StepMetadata = { kind: "step", name: "Create user", timeout: 30000, retry: { maxAttempts: 1, backoff: "linear" } };
const result: StepResult = {
  metadata,
  status: "passed",
  duration: 150,
  value: { userId: "123" }
};
console.log(result);

Failed step result

import type { StepResult } from "@probitas-test/runner";
import type { StepMetadata } from "@probitas-test/core";

const metadata: StepMetadata = { kind: "step", name: "Validate input", timeout: 30000, retry: { maxAttempts: 1, backoff: "linear" } };
const result: StepResult = {
  metadata,
  status: "failed",
  duration: 50,
  error: new Error("Invalid email format")
};
console.log(result);

Skipped step result

import type { StepResult } from "@probitas-test/runner";
import type { StepMetadata } from "@probitas-test/core";

const metadata: StepMetadata = { kind: "step", name: "Optional step", timeout: 30000, retry: { maxAttempts: 1, backoff: "linear" } };
const result: StepResult = {
  metadata,
  status: "skipped",
  duration: 0,
  error: "Prerequisite not met"
};
console.log(result);
Search Documentation