TestFramework.Core 0.6.0

dotnet add package TestFramework.Core --version 0.6.0
                    
NuGet\Install-Package TestFramework.Core -Version 0.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TestFramework.Core" Version="0.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TestFramework.Core" Version="0.6.0" />
                    
Directory.Packages.props
<PackageReference Include="TestFramework.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TestFramework.Core --version 0.6.0
                    
#r "nuget: TestFramework.Core, 0.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TestFramework.Core@0.6.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TestFramework.Core&version=0.6.0
                    
Install as a Cake Addin
#tool nuget:?package=TestFramework.Core&version=0.6.0
                    
Install as a Cake Tool

TestFramework.Core

TestFramework.Core is the timeline engine of the TestFramework ecosystem.

It provides the public API to:

  • define integration-test workflows
  • execute them with runtime inputs
  • assert outcomes from an immutable run result

Install

dotnet add package TestFramework.Core

Quick Start

using TestFramework.Core.Timelines;
using TestFramework.Core.Timelines.Assertions;
using TestFramework.Core.Variables;
using Xunit;

public class CoreSample
{
	private const string InputValue = "Alex";

	private static readonly Timeline _timeline = Timeline.Create()
		.SetVariable("name", Var.Const(InputValue))
		.Transform("greeting", Var.Ref<string>("name"), name => $"Hello {name}")
		.AssertVariable(Var.Ref<string>("greeting"), greeting => greeting == $"Hello {InputValue}")
		.Build();

    [Fact]
    public async Task RunTimeline()
    {
        TimelineRun run = await _timeline.SetupRun().RunAsync();

        run.EnsureRanToCompletion();

        using (var assertionScope = run.AssertionScope())
        {
            run.Variable<string>("greeting").Should().Exist().And().Be($"Hello {InputValue}");
        }
    }
}

Common Building Blocks

  • Timeline.Create() to start the builder
  • SetVariable, Transform, AssertVariable for variable-driven data flow
  • Trigger(...) and WaitForEvent(...) for actions and external synchronization
  • WithTimeOut(...), WithRetry(...) for reliability on unstable systems

Consumer-First Contract

For most users, the Core contract is intentionally small:

  1. Start with Timeline.Create().
  2. Compose fluent steps and modifiers.
  3. Freeze the plan with Build().
  4. Create a run with SetupRun(...).
  5. Execute with RunAsync() and assert through TimelineRun.

The scope split matters:

  • Build() usually belongs at class scope because it produces the reusable timeline definition.
  • SetupRun(...) usually belongs at method scope because each call creates a per-run builder with run-specific services, variables, artifacts, or output wiring.

The package exposes additional public types for artifacts, environment integration, debugging, and the fluent builder composition model, but those are advanced surfaces. If you are writing tests rather than framework extensions, prefer the timeline builder, Var, TimelineRun, and the assertion handles as your main API.

Fluent API Discovery

The fluent API is one logical surface even though it is composed internally from several public interfaces.

For normal usage, treat these as the only concepts that matter:

  • Timeline.Create() starts composition
  • fluent builder verbs add steps and modifiers
  • Build() freezes the reusable definition
  • SetupRun(...) creates a per-run builder
  • RunAsync() executes the run

The lower-level action interfaces remain public for compatibility and extension reasons, but they are intentionally de-emphasized in IntelliSense. If you discover those types directly, prefer returning to ITimelineBuilder, ITimelineBuilderModifier, and the fluent usage examples rather than learning the API through the interface lattice.

Extension-Facing Surface

You only need the lower-level public abstractions when you are extending the framework itself, for example by adding:

  • custom triggers or events
  • artifact describers and references
  • environment-provider integrations
  • runtime or debugging integrations

Those advanced surfaces are supported by the architecture docs, but they are secondary to the consumer workflow above.

Timeline Debugging

The recommended debugging path depends on what you need to see:

  1. Name important steps so failures and assertions point to stable labels.
  2. Pass ITestOutputHelper into SetupRun(...) when you want the timeline log in the test output stream.
  3. Inspect the completed TimelineRun for stage state, step results, variables, and artifacts.

For most users, that post-run inspection path is the supported debugging workflow. The lower-level debugger integration seam (IRunDebugger and related state types) remains available for custom tooling, but it is an advanced integration surface rather than the primary learning path.

Run Evidence (Widgets)

Post-run inspection tells you what a run did. run.Widgets is where it says what things looked like:

run.Widgets.Publish(new Widget
{
    Kind = WidgetKinds.Screenshot,
    Name = "after-login",
    Form = DebugPreviewForm.Image,
    Bytes = png,
    Summary = "The dashboard, logged in"
});

The file lands in the run's own output and is attributed to the step and the attempt that produced it, so retries keep every attempt rather than only the last. Publish hands back where it wrote the file, or null if it could not, and never throws - failing to gather evidence must not add a second failure. WidgetKinds names the three the family produces - Screenshot, Document, LogStream - and the kind is a plain string, so a package can introduce one without waiting on a Core release.

For a run that is held at a breakpoint, register an IWidgetCaptureSource: the engine asks it for a current picture while the run is stopped, which the last captured one no longer is.

This is the supported place for evidence. A package that writes screenshots into a folder of its own making has an arrangement only it and one tool understand.

Typical Pattern

  1. Build timeline once (usually static in test classes or other reusable class scope).
  2. Create a run with SetupRun(...) inside the test or method that is about to execute it.
  3. Add runtime variables/artifacts if needed.
  4. Run with RunAsync().
  5. Assert with EnsureRanToCompletion() and variable/artifact checks.

Persistent Environments

Most timelines should keep environment creation per run. When a suite repeatedly needs the same expensive environment slice, Core also exposes PersistentEnvironmentContext<TSetup> as the lower-level reuse primitive.

Use it when all of the following are true:

  • the environment shape is stable across many runs
  • some components are expensive enough that recreating them dominates runtime
  • those components can safely opt into EnvComponentReuseMode.PersistentContext

The model is:

  1. TSetup.CreateEnvironment() describes the full environment instance that future runs should receive.
  2. TSetup.GetPersistentComponentIdentifiers() selects the component roots that should be realized once and reused.
  3. PersistentEnvironmentContext<TSetup>.CreateEnvironment() produces fresh run environments with the persistent runtime state seeded back in.

Higher-level packages may wrap this primitive with package-specific helpers. In the container stack, DockerAzureHostedCollectionFixture<TState> is the xUnit-facing example of that pattern.

Target Frameworks

  • .NET 8 (net8.0)
  • .NET 10 (net10.0)
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on TestFramework.Core:

Package Downloads
TestFramework.Container.Azure

Docker-backed environment providers for TestFramework timelines for Azure components.

TestFramework.Container.Web

Docker-backed environment providers for TestFramework.Web timelines: builds and runs the application under test, starts the SQL Server behind it and the stubbed dependencies in front of it, and publishes every address into the running timeline.

TestFramework.UI.Browser

Drive a local browser from inside a TestFramework timeline. Interactions read as imperative steps (Navigate, Click, Fill, Expect) and address elements the way a person does - by role, label, placeholder or visible text - so a renamed or relocated control does not fail a test that only wanted to press it. Every non-exact match is recorded and assertable, ambiguity fails with the candidates and the dial that resolves it, and expected page structure is declared as a typed, reusable field.

TestFramework.Mock

Test the system under test in-process from inside a TestFramework timeline, with its own dependencies replaced by declared test doubles. Mock-Packs declare a double once; MockEnvironment hosts the application's real service composition with the doubles standing in; MockExt.Host calls into it, awaiting async calls; and what the doubles record becomes findable run artifacts, verified from the frozen run.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.6.0 55 9/30/2026

BREAKING - every step requirement must name a resource the run declares (in configuration, a fixture or an environment). It is checked while the run is planned, and a requirement nothing declares is refused before the first step instead of failing inside it. An environment now receives only the requirements that are its to provide: resources it declares itself, and every resource of a kind it maps with MapResourceKind. MapDeclaredResources maps a kind only for what the environment declares, EnvironmentResources.Required hands each component what the run needs, and OnRequirementResolved is obsolete.