Conduit E2E Fixture Architecture
May 1, 2026 ยท View on GitHub
This guide documents how Picea.Abies.Conduit.Testing.E2E is structured and how to add new user-journey coverage without introducing flaky setup.
Goals
- Start infrastructure once, reuse it safely across test classes.
- Keep tests deterministic by seeding through API calls, not UI setup flows.
- Keep coverage organized by render mode (WASM, InteractiveServer, InteractiveAuto, Static, AppHost regression).
Fixture Stack
The fixture model is layered:
SharedInfrasingleton starts Aspire infrastructure once per test run.- Mode-specific fixtures start one frontend host per render mode and provide
CreatePageAsync(). - Test classes share fixtures through TUnit
[ClassDataSource(..., Shared = SharedType.Keyed, Key = ...)].
Shared infrastructure fixture
Fixtures/ConduitInfraFixture.cs starts:
conduit-apikurrentdbpostgres- AppHost-managed frontend endpoints (
conduit-server,conduit-wasm)
SharedInfra.GetAsync() uses Lazy<Task<ConduitInfraFixture>> so startup happens exactly once.
Mode fixtures
| Fixture | Render mode / host | Notes |
|---|---|---|
ConduitAppFixture | InteractiveWasm | Self-hosted Kestrel + reverse proxy + WASM AppBundle |
ConduitServerFixture | InteractiveServer | Self-hosted Kestrel + WebSocket sessions + reverse proxy |
ConduitAutoFixture | InteractiveAuto | Self-hosted Kestrel with server-first handoff |
ConduitStaticFixture | Static | Self-hosted Kestrel static render checks |
ConduitAppHostServerFixture | AppHost conduit-server | Regression coverage against AppHost wiring |
ConduitAppHostWasmFixture | AppHost conduit-wasm | Regression coverage against AppHost wiring |
All fixtures expose:
BaseUrlfor page navigationApiUrlfor deterministic seedingCreatePageAsync()for an isolated Playwright context per test
Seeding Strategy
Seeding is API-first through Helpers/ApiSeeder.cs.
Why API seeding
- Faster than UI setup
- Less flaky than multi-step UI prerequisites
- Keeps each test independent and order-agnostic
Seeder capabilities
ApiSeeder currently supports:
- user registration and login
- article creation and comment creation
- follow/favorite operations
- profile updates
- read-after-write wait helpers (
WaitForProfileAsync,WaitForArticleAsync,WaitForArticleWithTitleAsync,WaitForArticleDeletedAsync)
SendWithRetryAsync handles transient startup failures and retries 5xx/network errors.
Test Class Pattern
Follow this pattern for new classes:
[Category("E2E")]
[ClassDataSource<ConduitAppFixture>(Shared = SharedType.Keyed, Key = "Conduit")]
[NotInParallel("Conduit")]
public sealed class ExampleTests : IAsyncInitializer, IAsyncDisposable
{
private readonly ConduitAppFixture _fixture;
private IPage _page = null!;
private ApiSeeder _seeder = null!;
public ExampleTests(ConduitAppFixture fixture) => _fixture = fixture;
public async Task InitializeAsync()
{
_page = await _fixture.CreatePageAsync();
_seeder = new ApiSeeder(_fixture.ApiUrl);
}
public async ValueTask DisposeAsync() => await _page.Context.DisposeAsync();
}
Render-mode conventions
- WASM tests call
WaitForWasmReady()before interactive assertions. - InteractiveServer tests use
FillAndWaitForPatch(...)where server-patch timing matters. - InteractiveAuto tests validate functionality after handoff conditions are possible.
- Static tests validate rendered output only (no runtime interaction assumptions).
Adding New User-Journey Coverage
Use this checklist when adding a RealWorld journey.
- Identify the user journey and expected behavior from https://docs.realworld.show/.
- Decide which render modes need the journey.
- Place tests in the correct folder:
- root: WASM baseline journeys
Server/: InteractiveServer equivalentsAuto/: InteractiveAuto equivalentsStatic/: static-render behaviorAppHost/: AppHost-specific regressions
- Seed prerequisites through
ApiSeederinstead of UI flows. - Assert user-visible outcomes only (URL, shell state, form state, visible text).
- Keep one journey per test method.
- Add matching integration coverage in non-E2E projects for core transition/interpreter logic.
Practical Example: Porting a Journey Across Modes
When adding a new journey such as "edit article":
- Implement baseline in a WASM-oriented class (for example in the root folder).
- Add InteractiveServer equivalent under
Server/and use patch-aware helpers. - Add InteractiveAuto equivalent under
Auto/if handoff behavior can affect the journey. - Add Static assertions under
Static/only if the journey has meaningful static output expectations.