Test Framework Consolidation Plan

Audience: test-automation and platform engineers consolidating the Stella Ops test estate. Purpose: turn the current test inventory into a consistent, predictable, manifest-driven framework — one source of truth for which suites run, where, and when.

The companion documents are TEST_MANIFEST.md (the implemented manifest contract), TEST_CI_ROUTING.md (trigger placement), and TEST_CI_EVIDENCE_DASHBOARD.md (the CI evidence standard).

Snapshot date: 2026-05-22 UTC

Root-tree consolidation (2026-05-30): the operator overrode the earlier “keep production/operator harnesses at the repo root” stance. As of 2026-05-30 there are no test code, fixture, or corpus directories at the repo root. Everything cross-cutting now lives under src/__Tests/ (e.g. the E2E analyzer corpus at src/__Tests/e2e/scanner-analyzer-corpus/, operator Playwright at src/__Tests/playwright/, reachability/fixtures/supply-chain under src/__Tests/, and shared seed data under src/__Tests/__Datasets/); module-owned suites live under src/<Module>/__Tests/; and former root samples/ moved to docs/samples/. Tables and prose below that still describe tests/** at the root are historical baseline; the current standard is the src/__Tests/ layout. See sprint SPRINT_20260530_008_Tests_root_data_consolidation.md.

This plan turns the current test inventory into a consistent, predictable, and durable test framework. It is based on:

Current baseline:

Target State

Testing must become manifest-driven, not filename/glob-driven.

The target system has:

  1. A versioned test manifest that is the source of truth for runnable suites, seed/fixture roots, CI placement, ownership, runtime cost, and gating state.
  2. A change-impact planner that maps each commit to the smallest correct test plan and prints why each suite was selected or skipped.
  3. Common project and seed-data structure across modules.
  4. One canonical trait taxonomy enforced by tooling.
  5. CI lanes separated by trigger type: per commit/PR, merge to main, nightly, weekly/release, and manual/live.
  6. Explicit handling for expensive, live, hardware, Docker-heavy, and demo/sample test surfaces so they are visible but do not over-trigger.
  7. Stable result artifacts and evidence metadata for every CI run.

Non-Goals

Operating Decisions

Common Tooling

Create these durable tools and artifacts:

ArtifactPathPurpose
Test manifestdocs/technical/testing/TEST_MANIFEST.ymlVersioned source of truth for suites, fixtures, triggers, owners, and gating.
Manifest schemadocs/technical/testing/TEST_MANIFEST.schema.jsonValidates manifest structure in CI.
Fixture manifest schemadocs/technical/testing/FIXTURE_MANIFEST.schema.jsonValidates fixture/corpus metadata.
Manifest linter.gitea/scripts/test/validate-test-manifest.*Fails when suites, fixtures, or traits drift from manifest rules.
Change planner.gitea/scripts/test/plan-test-run.*Converts changed files into a minimal test plan.
Plan runner.gitea/scripts/test/run-test-plan.*Executes manifest-selected suites across .NET, npm, Playwright, Go, and tooling runners.
Result normalizer.gitea/scripts/test/collect-test-results.*Emits stable JSON summary, TRX/JUnit links, timing, failures, and artifacts.
Inventory refresh.gitea/scripts/test/refresh-test-inventory.*Regenerates inventory appendices and compares them with the manifest.

The scripts can be shell, PowerShell, or Python, but the interface must be stable and documented. CI should invoke the same entry points developers use locally.

Manifest Model

Each suite row should include:

suites:
  - id: scanner.reachability.unit
    owner: Scanner
    path: src/Scanner/__Tests/StellaOps.Scanner.Reachability.Tests/StellaOps.Scanner.Reachability.Tests.csproj
    runner: dotnet
    category: Integration
    layers: [integration, golden, determinism]
    scenarioFamilies: [scanner/reachability/vex]
    affectedBy:
      paths:
        - src/Scanner/**
        - src/__Libraries/StellaOps.Reachability*/**
        - src/__Tests/e2e/scanner-analyzer-corpus/**
      fixtures:
        - scanner-analyzer-corpus
    runtime:
      tier: pr-targeted
      cost: medium
      infra: [postgres]
      timeoutMinutes: 15
    ci:
      pr: targeted-blocking
      main: targeted-blocking
      nightly: full-blocking
      manual: allowed
    evidence:
      resultFormat: trx
      artifacts:
        - TestResults/**

Each fixture/corpus row should include:

fixtures:
  - id: scanner-analyzer-corpus
    owner: Scanner
    path: src/__Tests/e2e/scanner-analyzer-corpus/fixtures
    type: scm-corpus
    offline: true
    refreshCommand: devops/scripts/e2e/provision-corpus.ps1 -Seed 42
    consumers:
      - scanner.e2e.analyzer-coverage
      - scanner.reachability.unit
    updatePolicy: explicit-review
    expectedHashPolicy: manifest-recorded

Common Project Structure

Standardize project placement as follows:

SurfacePreferred locationNotes
Module unit/component testssrc/<Module>/__Tests/<Project>.Tests/Default for module-owned tests.
Module integration testssrc/<Module>/__Tests/<Project>.IntegrationTests/Uses Testcontainers or WebApplicationFactory.
Module contract/security tests`src//__Tests/.<ContractSecurity>.Tests/`
Shared test librariessrc/__Libraries/** and src/__Libraries/__Tests/**Must be manifest-owned; changes are broad but should not always run every suite.
Cross-module integration/E2Esrc/__Tests/Integration/** and src/__Tests/e2e/**Explicit manifest rows; no implicit glob-only discovery.
Production/operator Playwrightsrc/__Tests/playwright/**Live-stack/operator scenarios, not Web unit E2E.
Web Playwright/Vitestsrc/Web/StellaOps.Web/**Split Vitest, source-served Playwright, docker/live Playwright, and air-gap UI specs into separate manifest rows.
Docs sample/template testsdocs/**/samples/** or docs/dev/**/plugin-templates/**Must be sample lane and only triggered by sample/template changes.

Rules:

Common Seed and Fixture Structure

Standardize fixture roots by intent:

DirectoryUse
Fixtures/Small hand-authored inputs used by one suite.
Seeds/Deterministic data loaded into a store or service.
Golden/Expected output files reviewed as contract fixtures.
Snapshots/ or __snapshots__/Snapshot framework output.
Corpus/Larger multi-case fixture collections.
Generated/Generated but committed only when intentionally reviewed.

Every non-trivial fixture root needs a local fixtures.manifest.yml:

id: concelier-nvd-fixtures
owner: Concelier
type: connector-fixture
offline: true
source: frozen-upstream-sample
refreshCommand: src/Concelier/devops/refresh-nvd-fixtures.ps1
updatePolicy: explicit-review
consumers:
  - concelier.connector.nvd.tests
expectedHashPolicy: manifest-recorded

Rules:

Trait and Category Taxonomy

Every .NET test class or method must have exactly one primary Category unless the manifest records a temporary exception.

Canonical primary categories:

CategoryTrigger classMeaning
UnitPR targetedNo network, no database, deterministic, fast.
ContractPR targetedSchema/API/serialization contract.
ArchitecturePR targetedLayering, dependency, naming, API conformance.
IntegrationPR targeted or main/nightlyTestcontainers, WebApplicationFactory, service/storage flows.
SecurityPR targetedAuthN/AuthZ, crypto, injection, validation, negative cases.
GoldenPR targeted or nightlyFrozen expected-output regression.
DeterminismPR targeted or nightlyRepeatability, canonical ordering, replay, clocks.
E2Emain/nightly/manual unless fast smokeFull workflow or multi-service behavior.
AirGapmain/nightly/manualOffline behavior and bundle/federation checks.
Performancenightly/weekly/manualThreshold-based performance smoke.
Benchmarkweekly/manualBenchmarkDotNet or long measurement suites.
Chaosnightly/manualFault injection and recovery.
Interopnightly/release/manualExternal tool or version compatibility.
Livemanual/scheduled onlyReal upstream services or credentials.
PostIncidentPR/main/release by severityRegression from a named incident.

Secondary traits:

TraitRequired when
OwnerAll tests after migration.
IntentSafety, regulatory, operational, determinism, performance, or UX-critical behavior.
InfraAny test needing Postgres, Valkey, Docker, browser, file system, network, or hardware.
FixtureAny test consuming a registered fixture/corpus.
RequirementContract, compliance, release, or post-incident tests.
BlastRadiusCross-cutting tests touching auth, API, persistence, evidence, policy, scanner, or release surfaces.

Skip rules:

CI Placement

Per Commit / Pull Request

Purpose: fast, correct feedback for the changed surface.

Always run:

Conditionally run:

Do not run automatically on every commit:

Merge To Main

Purpose: catch integration misses after merge without making every PR pay the full cost.

Run after push to main:

Main-merge failures should block release promotion and produce an owner-routed failure artifact. They should not silently disappear into report-only logs.

Nightly

Purpose: complete regression and expensive deterministic coverage.

Run:

Nightly should publish full coverage evidence and open/refresh failure tracking, but only suites marked nightly: blocking should block release readiness.

Weekly / Release

Purpose: long, costly, or environment-dependent validation.

Run:

Manual

Purpose: controlled investigation, live target validation, and ad hoc release evidence.

Manual workflows must still consume the manifest so their scope, owners, and artifacts are visible.

Change-Impact Planner

The planner replaces broad implicit triggering with an auditable decision.

Inputs:

Outputs:

Examples:

ChangeExpected plan
docs/** prose onlyDocs checks only; no product test sweep.
docs/dev/**/plugin-templates/**Sample/template tests only.
src/Web/StellaOps.Web/src/app/features/findings/**Web unit/Vitest affected specs; targeted Playwright smoke if route/user workflow changed.
src/Scanner/** analyzer codeScanner unit/integration/golden/reachability suites; Scanner fixture consumers.
src/__Tests/e2e/scanner-analyzer-corpus/**Scanner analyzer coverage and direct corpus consumers only.
src/__Libraries/StellaOps.TestKit/**TestKit tests plus manifest-derived consumers; PR uses representative blocking slice, nightly runs full consumer sweep.
global.json, Directory.*.props, run-test-plan.*Planner self-tests, representative compile/test slices, and nightly full sweep.
.gitea/workflows/**Workflow syntax/plan dry-run; affected runner self-tests.

The planner must avoid the current binary affects_all behavior where every shared change either fans out too much or defers too much to nightly.

CI Enablement Priorities

  1. Register all 23 ci-gap rows in the manifest.
  2. Split Web into separate rows: Vitest, source-served Playwright, docker/live Playwright, live-search Playwright, and Air-gap notification UI specs.
  3. Add production/operator Playwright under src/__Tests/playwright as nightly/manual first, with PR smoke only for changes to src/__Tests/playwright/** or shared UI routes it exercises.
  4. Add explicit rows for src/__Tests/e2e/** and src/__Tests/Integration/**.
  5. Keep former top-level tests/Telemetry rows under owning module test roots; the P0 Telemetry metrics coverage now lives in the module-owned Telemetry core test project, and the Doctor plugin storage project has been moved under src/Doctor/__Tests.
  6. Add BinaryIndex benchmark rows as weekly/manual, not PR.
  7. Decide whether docs sample/template tests are real runnable examples or excluded sample artifacts. Until then, trigger them only on sample/template path changes.
  8. Convert current report-only .NET PR fanout to targeted non-blocking, then to blocking by layer once trait coverage and planner accuracy are proven.

Phased Rollout

Phase 1 - Manifest Foundation

Deliver:

Exit criteria:

Phase 2 - Change Planner

Deliver:

Exit criteria:

Phase 3 - Trait and Structure Migration

Deliver:

Exit criteria:

Phase 4 - CI Enablement

Deliver:

Exit criteria:

Phase 5 - Fixture and Corpus Governance

Deliver:

Exit criteria:

Phase 6 - Gate Hardening

Deliver:

Exit criteria:

Consistency Rules

Open Decisions

  1. Manifest foundation and linter.
  2. Change planner dry-run.
  3. CI-gap registration.
  4. Web runner split.
  5. Trait migration for the largest/highest-value suites.
  6. Fixture manifest rollout for Scanner, Concelier, and cross-cutting __Tests.
  7. Gate hardening from report-only to blocking by proven lane.

Implementation State

The first implementation slice created the manifest foundation:

Current local evidence is recorded in TEST_MANIFEST.md and docs/implplan/SPRINT_20260522_008_DOCS_testing_framework_consolidation.md.