npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xhubio/bitdiver-runner

v3.0.3

Published

Test-Execution-Framework fuer automatisierte End-to-End Tests. Fuehrt Tests als Suites mit Steps aus, vergleicht Ergebnisse und liefert strukturiertes Reporting.

Readme

@xhubio/bitdiver-runner

Test-Execution-Framework fuer automatisierte End-to-End Tests. Fuehrt Tests als Suites mit Steps aus, vergleicht Ergebnisse und liefert strukturiertes Reporting.

Quickstart

pnpm install
pnpm run build     # biome check + tsc
pnpm run test      # build + vitest
pnpm run test:only # nur tests (ohne build)

Architektur

┌─────────────────────────────────────────────────────────┐
│                    Suite-Config (YAML/JSON)              │
│    setup: [Step1, Step2]                                │
│    timed: auto (scannt Testdaten-Dateien)               │
│    timing: { startAfterStep, testcaseDelaySeconds }     │
│    teardown: [CheckStep, ReportStep]                    │
└──────────────────────┬──────────────────────────────────┘
                       │
              createSuiteFromConfig()
                       │
                       ▼
┌─────────────────────────────────────────────────────────┐
│                   suite.json                            │
│  steps: ["Step1", "Step2", "SendData 120", "CheckStep"] │
│  stepDefinitions: { "SendData 120": { timing: {120} } } │
│  timing: { startAfterStep: "Step2", delay: 0.2 }       │
│  testcases: [{ name: "TC1", data: { ... } }]           │
└──────────────────────┬──────────────────────────────────┘
                       │
                    Runner
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
      StepNormal   StepSingle   Timed Steps
      (1x pro TC)  (1x fuer     (Runner wartet
                    alle TCs)    auf Zeitpunkt)

Module

config — Konfigurationsmanagement

Laedt Konfiguration aus JSON-Dateien mit Environment-Variable-Overrides und Zod-Validierung.

import { z } from 'zod'
import { loadConfig } from '@xhubio/bitdiver-runner'

const schema = z.object({
  database: z.object({
    url: z.string(),
    password: z.string(),
    maxConnections: z.number().default(10)
  }),
  debug: z.boolean().default(false)
})

const { config, toString } = await loadConfig({
  schema,
  file: './config.json',
  envPrefix: 'MYAPP',       // MYAPP_DATABASE_URL, MYAPP_DEBUG etc.
  secrets: ['database.password']
})

console.log(toString())     // password als *** maskiert
console.log(config.debug)   // boolean, typisiert

Prioritaet (hoechste gewinnt):

  1. Environment-Variablen (MYAPP_DATABASE_URL)
  2. Inline-Values
  3. JSON-Datei
  4. Schema-Defaults

Env-Variable-Namenskonvention: <prefix>_<PFAD_IN_UPPER_SNAKE_CASE> — z.B. database.maxConnections wird zu MYAPP_DATABASE_MAX_CONNECTIONS.


suite-builder — Deklarative Suite-Erstellung

Erstellt Suites aus einer YAML/JSON-Konfiguration statt aus Code.

Suite-Config Format

timedStepMapping:
  ri-fahrt-v1: SendRiFahrtV1Time
  playwright: SendPlaywrightTestTime
  bi-basis-fahrt-v3: SendBiBasisFahrtTimeV3

suiteTypes:
  TEST_FIX:
    setup:
      - SetupEnvironmentRun
      - ClearDatabase
      - CheckStartTime
    timing:
      startAfterStep: CheckStartTime
      testcaseDelaySeconds: 0.2
    timed: auto
    teardown:
      - ExportResults
      - CheckResults
      - StoreArtifacts

Drei Phasen

| Phase | Beschreibung | |-------|-------------| | setup | Vorbereitende Steps, sequentiell abgearbeitet | | timed | Automatisch generiert aus Testdaten-Dateien. Dateiname-Pattern: <zeit>_<typ>_<rest>.json. Steps sortiert nach Zeit. | | teardown | Nachbereitende Steps (Export, Pruefung, Reporting) |

Runner-gesteuertes Timing

Der Runner uebernimmt die Zeitsteuerung — Steps muessen nicht selbst warten:

Setup-Steps laufen sequentiell
  Setup → ClearDB → CheckStartTime
                          ↓
          Runner merkt: referenceTime = now()
                          ↓
Timed Steps: Runner wartet auf den richtigen Zeitpunkt
  +120s: SendRiFahrtV1Time 120 (TC1, +0.2s TC2, +0.4s TC3, ...)
  +240s: SendRiFahrtV1Time 240 (TC1, +0.2s TC2, +0.4s TC3, ...)
                          ↓
Teardown-Steps laufen sequentiell
  ExportResults → CheckResults → StoreArtifacts

Konfiguration:

  • timing.startAfterStep: Nach diesem Step beginnt die Zeitmessung
  • timing.testcaseDelaySeconds: Versatz zwischen Testcases (z.B. 0.2s)
  • stepDefinition.timing.offsetSeconds: Sekunden nach Referenzzeit

Im testMode werden alle Delays uebersprungen.

Timed Steps

Dateien in den Testdaten-Verzeichnissen werden gescannt:

TC_01/
  1_ri-fahrt-v1_23711.json      → SendRiFahrtV1Time 1
  120_playwright_soll.json       → SendPlaywrightTestTime 120
  240_ri-fahrt-v1_update.json    → SendRiFahrtV1Time 240

Das timedStepMapping ordnet den Datei-Typ (ri-fahrt-v1) dem Step-ID (SendRiFahrtV1Time) zu. Die Step-Daten enthalten { offsetTime: 120, files: [...] }.

Usage

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

const suite = await createSuiteFromConfig({
  config: suiteConfigYaml,  // geparstes YAML/JSON
  suiteType: 'TEST_FIX',
  testDataDir: './testdaten',
  suiteName: 'regression-paket1'
})

definition — Suite-Format

Das kompakte Suite-Format mit Sparse-Data-Maps und optionalem Timing.

{
  "executionMode": "batch",
  "name": "regression-paket1",
  "steps": ["SetupStep", "SendData 120", "CheckStep"],
  "stepDefinitions": {
    "SetupStep": { "id": "SetupStep", "name": "SetupStep", "description": "..." },
    "SendData 120": {
      "id": "SendDataTime", "name": "SendData 120", "description": "",
      "timing": { "offsetSeconds": 120 }
    },
    "CheckStep": { "id": "CheckStep", "name": "CheckStep", "description": "..." }
  },
  "testcases": [
    {
      "name": "TC_01_Entlastung",
      "data": {
        "SendData 120": { "offsetTime": 120, "files": ["TC_01/120_data.json"] }
      }
    }
  ],
  "timing": {
    "startAfterStep": "SetupStep",
    "testcaseDelaySeconds": 0.2
  }
}

Designprinzipien:

  • steps ist ein Array (einmal fuer alle TCs, nicht pro TC wiederholt)
  • data ist eine Map (stepName -> data), keine positionalen Arrays mit Nulls
  • stepDefinitions mit optionalem timing fuer zeitgesteuerte Steps
  • timing auf Suite-Ebene fuer Referenzzeit und TC-Versatz

model — Steps und Environments

StepNormal

Ein Step der einmal pro Testcase ausgefuehrt wird. Auch fuer zeitgesteuerte Steps — der Runner uebernimmt das Timing.

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

class SendData extends StepNormal {
  async run(): Promise<void> {
    const env = this.tc              // typisiert: EnvironmentTestcase
    const payload = this.data        // Step-Daten aus der Suite

    await sendToKafka(payload)
    await this.logInfo(`Sent data for ${env.name}`)
  }
}

StepSingle

Ein Step der einmal fuer alle Testcases ausgefuehrt wird. Hat Zugriff auf alle TC-Environments.

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

class ClearDatabase extends StepSingle {
  async run(): Promise<void> {
    await db.clear()

    for (const { environment, data } of this.testcases) {
      environment.map.set('dbCleared', true)
    }
  }
}

StepSetupConfig

Generischer Step der Konfiguration in die Run-Umgebung laedt.

import { z } from 'zod'
import { StepSetupConfig } from '@xhubio/bitdiver-runner'

const mySchema = z.object({
  kafka: z.object({
    brokers: z.string(),
    password: z.string()
  }),
  targetEnvironment: z.string().default('tu2')
})

class SetupMyEnv extends StepSetupConfig<typeof mySchema.shape> {
  getConfigSchema() { return mySchema }
  getSecrets() { return ['kafka.password'] }
}

Step-Daten: { "configFile": "./config.json", "envPrefix": "CUSTOM" }

Environment-Persistenz

Steps koennen Daten zwischen Steps austauschen ueber environmentTestcase.map. Fuer grosse Daten gibt es Disk-Persistenz:

class ExportStep extends StepNormal {
  async run(): Promise<void> {
    this.tc.map.set('results', largeData)

    // Auf Disk schreiben + aus Memory entfernen
    await this.exportVars(['results'], './results/TC_01')

    // Spaeter wieder laden
    await this.loadVars(['results'], './results/TC_01')
  }
}

class StepWithTempData extends StepNormal {
  async run(): Promise<void> {
    // Automatisch aufgeraeumt nach afterRun()
    await this.loadTempVars(['cached'], './cache')
    const data = this.tc.map.get('cached')
  }
}

Step-Lifecycle

start()      -> Initialisierung (alle Instanzen eines Steps)
beforeRun()  -> Vorbereitung (Config laden, Pfade setzen)
run()        -> Hauptarbeit
afterRun()   -> Aufraeumen (temp Variablen loeschen)
end()        -> Abschluss (alle Instanzen eines Steps)

StepRegistry

Registriert Step-Klassen unter einem Namen. Der Runner nutzt die Registry um Steps zu instanziieren.

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

const registry = new StepRegistry()
registry.registerStep({ stepName: 'SendData', step: SendData })
registry.registerStep({ stepName: 'ClearDB', step: ClearDatabase })

runner-server — Test-Ausfuehrung

Runner

Fuehrt eine Suite aus. Unterstuetzt zwei Modi:

  • batch (Standard): Iteriert Steps, dann Testcases pro Step
  • normal: Iteriert Testcases, dann Steps pro Testcase
import { Runner, ProgressBarConsoleLogBatchJson, LogAdapterFile } from '@xhubio/bitdiver-runner'

const runner = new Runner({
  id: 'run-001',
  dataDirectory: './data',
  suite: suiteJson,
  stepRegistry: registry,
  logAdapter: new LogAdapterFile({ targetDir: './logs' }),
  progressMeterBatch: new ProgressBarConsoleLogBatchJson({ name: 'my-run' }),
  parallelExecution: true,
  maxParallelSteps: 20,
  testMode: false
})

await runner.run()

Timing-Steuerung: Der Runner liest suite.timing und stepDefinition.timing:

  • Setzt referenceTime nach dem konfigurierten startAfterStep
  • Wartet bei timed Steps bis referenceTime + offsetSeconds
  • Fuegt testcaseDelaySeconds Versatz zwischen TCs ein
  • Ueberspringt alle Delays im testMode

ProgressMeter

Hooks fuer Live-Fortschritt:

| Hook | Wann | |------|------| | init({ stepCount, testcaseCount, name }) | Run startet | | incStep(name) | Neuer Step beginnt | | incTestcase(name) | Neuer Testcase in Step | | setFail() | Testcase fehlgeschlagen | | done() | Run beendet |


check — Ergebnis-Vergleich

Vergleicht Expected-Dateien mit Actual-Ergebnissen.

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

registry.registerStep({ stepName: 'CheckResults', step: StepCheck })

Step-Daten:

{
  "resultDir": "/path/to/results/TC_01",
  "dataDir": "/path/to/expected/TC_01",
  "checks": [{
    "name": "kafka-events",
    "actualDir": "events/kafka",
    "expectedDir": "expected/kafka",
    "dataPath": ["data"],
    "ignorePaths": [
      { "path": ["header", "messageId"], "doc": ["System-generiert"] }
    ]
  }]
}

Pipeline: Mapping (Dateiname) -> Vergleich (@aikotools/datacompare) -> Reporting (summary.json + details.json)


logadapter — Logging

| Adapter | Beschreibung | |---------|-------------| | LogAdapterConsole | Ausgabe auf stdout | | LogAdapterConsoleJson | JSON-formatierte Ausgabe | | LogAdapterFile | Schreibt in Dateisystem (Run/TC/Step Struktur) | | LogAdapterMemory | In-memory (fuer Tests) |

Log-Level: debug (0), info (1), warning (2), error (3), fatal (4)

Run-Level Error-Logs enthalten ein source-Feld mit Testcase-Name, Step-Name und SingleStep-Marker.


Projekt-Struktur

src/
  index.ts                     Barrel-Export
  config/                      Zod-basiertes Config-Laden
  suite-builder/               Deklarative Suite-Erstellung
  definition/                  Suite/Step/Testcase Interfaces + Zod-Validierung
  model/                       Steps, Environments, Persistence, Registry
  runner-server/               Runner, Timing, ProgressMeter, LogAdapter-Bridge
  check/                       Expected vs Actual Vergleich
  logadapter/                  Pluggable Logging
tests/
  36 Test-Suites, 223 Tests

Tooling

| Tool | Zweck | |------|-------| | Vitest | Test-Runner | | Biome | Linting + Formatting | | Zod | Schema-Validierung + TypeScript-Typen | | TypeScript | Typisierung |

Dependencies

Runtime: zod, @aikotools/datacompare, luxon, uuid, md5

Ehemalige Dependencies (clone, mkdirp, rimraf, sprintf-js, p-all, ajv) wurden durch native Node.js APIs ersetzt.