Dependency-free end-to-end spine
The pieces every test lane needs, from running a scenario to surviving a backend restart, live in one package with no third-party dependencies, so packages that must stay isolated can still share them.
- What it is
- Tool — something you run.
- What it does
- One scenario engine drives every end-to-end lane: a step triggers a real capability, observes the outcome on one or more channels, optionally extracts a value later steps read, and asserts what the application's own store says.
- Where it stops
- It cannot contain anything that needs a dependency, and that boundary is enforced by the fact that it has none.
@wildo-ai/testing-core holds the parts every test lane needs and none of the parts any one lane is
about: the scenario engine, the surface and adapter contracts, the isolation assertion, backend-flap
resilience, the log observer and the managed child process. It declares no runtime dependency at
all, and it is exported as TypeScript source rather than as a build, which is what lets a package
with its own lockfile consume it without inheriting anything.
The problem it solves
Test doubles have to be independent to be worth anything. A double that impersonates a customer’s identity provider is only evidence if it speaks the protocol on its own terms rather than borrowing the types of the thing it is testing, and a double that shares a lockfile with the engine cannot demonstrate that it resolved a signature the way an outsider would.
Independence usually costs duplication. Each isolated package ends up with its own copy of the scenario loop, its own retry, its own idea of what an assertion is, and the copies drift until the same failure reads differently in two lanes. This package is the way out: the parts that are genuinely common carry no dependencies, so importing them costs an isolated package none of its isolation.
What it does
One scenario engine drives every end-to-end lane: a step triggers a real capability, observes the outcome on one or more channels, optionally extracts a value later steps read, and asserts what the application’s own store says.
Cross-scope isolation is not a separate mechanism. It is a flag on an assertion, asserting that the active scope recorded the expected traffic AND every other scope stayed silent. That detects misrouting from record counts alone, with no payload parsing — which is why it works identically over an identity provider, a webhook receiver and a security-event sink.
The whole engine is generic, so a surface brings its own kinds rather than the engine knowing about any of them. That is what makes one loop drive an HTTP API, a browser, a message queue, a backend log and an external double.
One seam injects the application. An application supplies provisioning, capability configuration, a trigger, a store read and its addresses; the subprocess form turns that into a cross-process contract — so the adapter runs in the application’s own workspace with the application’s own module resolution, while the scenario stays application-agnostic.
The resilience module exists because a development stack restarts under recompile churn. Rather than widening timeouts, every application-facing call gates on the health endpoint and retries within a readiness ceiling — and a transport failure is described AS one, so a caller can tell an absent backend from a wrong answer.
Limits
It cannot contain anything that needs a dependency, and that boundary is enforced by the fact that it has none. The browser-backed surface therefore lives in its own package, and every concrete surface implementation lives with its lane.
It defines the surface contract and the isolation assertion; it does not know what a Wildo resource, role or organisation is. Everything domain-shaped is on the far side of the adapter.
The log observer parses one record per line, so a stack running its logs in human-readable mode yields nothing parseable to it — which is why the fatal gate in the harness reads raw lines instead of reusing this parser.