Step Types in Detail

BitDiver provides two primary step types: StepNormal for per-testcase work and StepSingle for suite-wide operations. Timing is handled by the Runner using built-in steps. All step types share the same lifecycle and configuration options.

Step Lifecycle

All steps follow the same lifecycle:

start() → beforeRun() → run() → afterRun() → end()
  • start() and end() — Called once per step (not per instance). Use these for one-time setup and teardown.
  • beforeRun(), run(), afterRun() — Called per instance. For a StepNormal, this means once per test case. For a StepSingle, once per suite.

StepNormal

The most common step type. A StepNormal runs once per test case in the suite. Each invocation receives its own test case context.

import { StepNormal } from '@xhubio/bitdiver-runner'

class SendRequest extends StepNormal {
  async run() {
    const url = this.tc.map.get('url')
    const response = await fetch(url)
    this.tc.map.set('response', response)
    await this.logInfo({ message: 'Request sent', url })
  }
}

Key Properties

  • this.tcEnvironmentTestcase (typed getter). The current test case context with its own map for storing data.
  • this.data — Step data from the suite definition. Whatever was provided in the testcases[].data for this step.
  • this.environmentRun — Run-wide state shared across all steps and test cases.
  • this.logAdapter — The logging adapter for structured output.

StepSingle

Runs once for the entire suite rather than per test case. Use this for shared setup/teardown operations like database seeding, environment provisioning, or aggregate reporting. A StepSingle has access to all test case environments at once.

import { StepSingle } from '@xhubio/bitdiver-runner'

class ClearDatabase extends StepSingle {
  async run() {
    await db.truncate('results')
    for (const { environment, data } of this.testcases) {
      environment.map.set('dbCleared', true)
    }
    await this.logInfo('Database cleared')
  }
}

Key Properties

  • this.testcases — Array of { environment: EnvironmentTestcase, data: any }. Gives you access to every test case environment and its associated data.
  • Failure in a single step fails the entire run. Use single steps only for operations that are truly global.

Built-in Timing Steps

Timing is managed by the Runner using three built-in steps that are pre-registered in the StepRegistry. You do not need to register them manually — they are available by default under the names Wait, DetermineStartTime, and CheckStartTime.

StepDetermineStartTime

Extends StepSingle. Calculates a reference time for subsequent timed steps. The reference time is rounded up to the next full minute, accounting for an optional offset and per-testcase delay. The result is stored in EnvironmentRun under the REFERENCE_TIME_KEY constant.

// In your suite definition, use with inline parameters:
{ "step": "DetermineStartTime", "offsetSeconds": 60, "delaySeconds": 5 }

StepCheckStartTime

Extends StepSingle. Verifies that the timing budget has not been exceeded. If the reference time has already passed (the setup phase took too long), the step logs a fatal error and aborts the run. Otherwise it waits until the reference time is reached. In testMode, waiting is skipped but overrun detection still applies.

StepWait

Extends StepSingle. A simple configurable wait step. Accepts seconds as data. In testMode, the delay is skipped entirely.

// In your suite definition:
{ "step": "Wait", "seconds": 30 }

Runner Timing for Steps

Steps in the suite definition can carry a timing property with an offsetSeconds value. Before executing each timed step, the Runner waits until referenceTime + offsetSeconds is reached. All delays are bypassed in testMode.

StepTimed (Deprecated)

The legacy StepTimed class still exists for backwards compatibility. It extends StepNormal and requires implementing getReferenceTime(), getOffsetSeconds(), and doRun(). New suites should use Runner-managed timing instead.

import { StepTimed } from '@xhubio/bitdiver-runner'

// DEPRECATED — use Runner-managed timing instead
class SendAtTime extends StepTimed {
  getReferenceTime(): string {
    return this.tc.map.get('START_TIME')
  }

  getOffsetSeconds(): number {
    return this.data.offsetTime
  }

  async doRun(): Promise<void> {
    await sendMessage(this.data.payload)
    await this.logInfo(`Sent at offset ${this.getOffsetSeconds()}s`)
  }
}

Persistence in Steps

Steps can persist and load variables across executions using built-in persistence methods. These are available on both StepNormal and StepSingle:

  • writeVars(vars) — Persists variables to disk so they survive across runs. Use this to store credentials, session tokens, or any state that should be available in later suites.
  • loadVars() — Loads previously persisted variables back into the step. Returns the stored object.
  • exportVars(vars) — Exports variables to the run environment so other steps in the same run can access them.
  • loadTempVars() — Loads temporary variables that were exported by a previous step in the same run. Unlike loadVars(), these do not persist to disk.
// In a StepNormal: persist an auth token
async run() {
  const token = await authenticate(this.data.credentials)
  await this.writeVars({ authToken: token })
}

// In a later step: load the persisted token
async run() {
  const vars = await this.loadVars()
  const token = vars.authToken
  // use token...
}

// Export vars for other steps in the same run
async run() {
  await this.exportVars({ sessionId: '12345' })
}

// Load exported vars from a previous step
async run() {
  const temp = await this.loadTempVars()
  const sessionId = temp.sessionId
}

Step Options

All step types accept constructor options that control their behaviour within the runner:

  • name — A human-readable name for the step, used in logs and reporting.
  • logAdapter — Override the default log adapter for this specific step.
  • needData: false — Step runs even without data in the suite definition. By default, steps are skipped for test cases that provide no data for them.
  • runOnError: true — Step runs even if the test case has already failed in a previous step. Useful for cleanup or reporting steps that must always execute.
  • maxParallelSteps — Override the global parallelism limit for this step. Set to 1 to force serial execution, or increase it for I/O-heavy steps.
class CleanupStep extends StepNormal {
  constructor(opts) {
    super({
      ...opts,
      name: 'cleanup',
      needData: false,
      runOnError: true,
      maxParallelSteps: 1
    })
  }

  async run() {
    await cleanupResources()
  }
}