Cross-boundary by design
One scenario spans REST, Kafka, Postgres and outbound webhooks — exercising the contracts between systems, not within them.
dotnet tool install -g vouchfx --prerelease · v1.0 GA in preparation
vouchfx compiles declarative .e2e.yaml specs into memory-safe C#, orchestrates the full
container topology with .NET Aspire + Testcontainers, and proves one business
transaction works as it crosses a REST call, a Kafka event, a database mutation and an outbound webhook —
the seams where distributed systems actually break.
.e2e.yaml→
validate vs JSON Schema→
compile YAML→AST→CSX→Roslyn (once)→
orchestrate topology via Aspire→
execute against discovered endpoints→
collect verdict→
unload context · reset
It is not a unit-test framework and not a UI/browser tool.
The problem
A modern transaction is not a function call — it is a relay across services, brokers, databases and webhooks. Each component is tested in isolation and works. The handoffs between them are where ordering, timing, schema drift and partial failure hide. vouchfx tests that relay as a single, declarative scenario.
One scenario spans REST, Kafka, Postgres and outbound webhooks — exercising the contracts between systems, not within them.
No hand-rolled Thread.Sleep. Asynchronous assertions are a first-class RETRY mode the engine owns, with bounded exponential backoff.
The same suite runs unchanged on a laptop, in SaaS, or in CI — because the compiled delegate depends only on a typed contract that orchestration produces.
How it works
A spec is parsed to an AST, lowered to Turing-complete C# script (CSX), and compiled exactly once through Roslyn into a collectible assembly. Orchestration stands up the topology; the delegate runs against the discovered endpoints; the context unloads and resets — no leaked assemblies, no drift between runs.
Write .e2e.yaml; validate against a single JSON Schema before any container starts.
YAML → AST → CSX → CSharpScript.Create<T>() → .Emit() into a collectible AssemblyLoadContext.
Headless .NET Aspire AppHost + Testcontainers bring up services and managed dependencies, health-gated.
Invoke the delegate against discovered endpoints; Polly v8 drives RETRY polling under the hood.
Emit a verdict to a JSON Lines event stream, .Unload() the context, and reset to baseline.
Architecture
The contract that makes a suite portable: the compiled delegate touches the environment only
through a typed ScriptGlobalVariables, and orchestration is its sole producer. Nothing else bridges
the boundary.
The .e2e.yaml grammar, JSON Schema, VSCode/LSP tooling.
Parser → AST → CSX → Roslyn delegate, compiled once.
Aspire AppHost, Testcontainers, health-gated startup.
Delegate host, collectible AssemblyLoadContext, Polly v8 resilience.
Local Docker, SaaS, or on-prem Kubernetes.
ScriptGlobalVariables ← orchestration
A test, in shape
steps is mandatory.
State threads forward between steps through capture and {placeholder} substitution.
One generated identifier from step one can appear in the assertions of step five — across heterogeneous systems.
metadata:
name: New customer is provisioned end-to-end
owner: payments-team
tags: [checkout, billing]
environment:
services: # the system under test — any container, any stack
orders-api: { image: ghcr.io/acme/orders-api:latest }
dependencies: # managed Aspire resources
orders-db: { type: postgres, version: "16" }
events: { type: kafka, schemaRegistry: true }
variables:
basePath: /api/v1
steps:
- id: create-user
type: http.rest
target: orders-api
method: POST
path: "{basePath}/users"
body: { email: ada@example.com }
capture:
newUserId: $.id # JSONPath → shared context
- id: expect-billing-event
type: mq-expect.kafka
topic: billing.accounts
verifyMode: RETRY # engine polls with backoff — never Thread.Sleep
timeout: 30s
match:
key: "{newUserId}"
- id: assert-projection
type: db-assert.postgres
target: orders-db
verifyMode: RETRY
query: SELECT 1 FROM accounts WHERE user_id = '{newUserId}'
expect: { rows: 1 }
Secrets are references, never literals — ${secret:vault/orders-db/conn}, resolved at execution time
and redacted in every report. Test doubles (WireMock/Mountebank) are ordinary containers in environment,
not a bolted-on mocking feature.
Real-world samples
The vouchfx-samples site documents four production-grade sample applications (in four stacks) with complete test suites demonstrating patterns across multiple providers and technologies.
Stack: ASP.NET Core, PostgreSQL, Kafka, Webhook.
Demonstrates: REST, database assertions, message publishing/consumption, webhook listening, RETRY polling.
View →Stack: FastAPI, MySQL, RabbitMQ, Redis.
Demonstrates: HTTP, MySQL assertions, RabbitMQ publish/expect, Redis cache assertions, multi-step state threading.
View →Stack: Spring Boot, SQL Server, NATS JetStream, Mailpit.
Demonstrates: REST, SQL Server assertions, NATS publish/expect with JetStream, email capture, connection-string injection.
View →Stack: Node.js (node:http), PostgreSQL, Kafka, Community provider rpc.json-rpc.
Demonstrates: Custom provider consumption, JSON-RPC protocol, PostgreSQL mutations, message publishing, custom runner pattern.
View →The central problem
Naïvely evaluating a script per run leaks an uncollectable assembly every time. vouchfx is built around the opposite discipline — and ships a CI memory-leak regression test over the full transitive closure of every Core provider as a permanent gate.
// leaks an uncollectable assembly per call
await CSharpScript.EvaluateAsync(code);
// compile once …
var script = CSharpScript.Create<T>(code);
script.GetCompilation().Emit(stream);
// … isolate, invoke N times, then collect
var alc = new AssemblyLoadContext(isCollectible: true);
// … run …
alc.Unload();
The two defining risks empirically retired, guarded permanently in CI.
Provider model
<family>.<provider> — intent, then technology.
Providers are compile-time, source-level plugins: add a project, implement the frozen v1.x
contract, mark it [StepProvider], and a reflective registry discovers it at startup. No runtime
loader, no dynamic assembly loading, no sandbox. Models are strongly-typed records, never
Dictionary<string,object>.
Core · Community — plus the maintainer-awarded Vouched badge, all Apache-2.0. Explore the community provider hub.
IStepProvider / IStepBinder<T> / IStepValidator<T> / IStepCompiler<T> / IResourceContributor<T> — stable for the v1.x engine series.
vouchfx is a .NET-native engine, but it talks to the system under test over the wire. Aspire is polyglot — your services can be Java, Go, Python, Node or .NET.
Verdict taxonomy
Taxonomy, reporting and exit codes never conflate them. Only Fail breaks CI by default —
because conflating an environment error with a real defect is how teams stop trusting a tool.
Every assertion held.
A defect in the system under test. The only outcome that breaks CI by default.
Infrastructure: unhealthy container, pull / tunnel / seed failure.
Timeout, partition outlasted grace, or an upstream capture unmet.
One schema-versioned JSON Lines event stream feeds every renderer — terminal, HTML, JUnit XML, dashboard — and the Healer agent. Each step-attempt is recorded individually, so the RETRY timeline renders without re-running.
Status & roadmap
Work is sequenced by risk, not visibility: the memory model and orchestration were de-risked first, and the memory-leak regression test has been a permanent CI gate from the start. The full picture — including what stays free permanently — is on the public roadmap.
Memory-safe dynamic compilation and health-gated Aspire orchestration empirically validated. Buildable .NET 8 solution; frozen provider contract; JSON Lines event envelope.
The YAML→AST→CSX compiler runs real scenarios end-to-end with declarative seeding, secret references, capture, the reflective provider registry and the terminal renderer.
All twenty-five Core providers across eleven families plus engine-owned RETRY; the v1 schema, provider contract and event-wire contract frozen; the Provider SDK published on NuGet.org with worked examples; runner selection and the polling timeline.
VSCode extension, CLI, HTML/JUnit reporting, CI templates for GitHub and GitLab, the documentation set, the accessibility review, and a signed, provenance-attested release pipeline — with the four-technology reference scenario green.
The first public releases have shipped: the v1.0.0-alpha series is live on NuGet.org and GitHub Releases (currently v1.0.0-alpha.9), signed and provenance-attested. Remaining for GA: real-world validation in the open — through the samples, the migration guide and the community provider hub — and stabilisation of the Provider SDK at 1.0.0 final.
vouchfx is a fully-specified system: an architecture blueprint, a DSL specification, and a public roadmap — all in the open.