stride3d/stride · error · InvalidOperationException
Stride.Games.AutoTesting
Error message
Stride.Games.AutoTesting: '{RegisteredTest.GetType().FullName}' is already registered; exactly one [ScreenshotTest] per sample is allowed (tried to register '{test.GetType().FullName}'). What it means
ScreenshotTestRunner.RegisterTest enforces that exactly one IScreenshotTest instance type is registered per process. If a test with a different type is registered while one is already held, it throws InvalidOperationException naming both types.
Solutions
- Remove [ScreenshotTest] from all but one test class in the project
- Delete the redundant/obsolete test class
- If both tests are needed, split them into separate sample projects/executables
Example fix
// before
[ScreenshotTest]
public class ScreenATest : IScreenshotTest { ... }
[ScreenshotTest]
public class ScreenBTest : IScreenshotTest { ... }
// after
[ScreenshotTest]
public class ScreenATest : IScreenshotTest { ... }
public class ScreenBTest : IScreenshotTest { ... } // attribute removed Defensive patterns
Strategy: validation
Validate before calling
if (ScreenshotTestRunner.RegisteredTest is not null && ScreenshotTestRunner.RegisteredTest.GetType() != test.GetType())
throw new InvalidOperationException("Multiple [ScreenshotTest] classes in this project");
ScreenshotTestRunner.RegisterTest(test); Type guard
bool IsFirstOrSame(IScreenshotTest t) => ScreenshotTestRunner.RegisteredTest is null || ScreenshotTestRunner.RegisteredTest.GetType() == t.GetType();
Try / catch
try { ScreenshotTestRunner.RegisterTest(test); } catch (InvalidOperationException ex) { /* log duplicate test type and skip */ } Prevention
- Keep exactly one [ScreenshotTest] class per sample project
- Grep the project for [ScreenshotTest] before adding a new one
When it happens
Trigger: An app assembly containing two or more classes marked [ScreenshotTest], each triggering static registration on startup.
Common situations: Adding a second sample screen with [ScreenshotTest] to an existing sample project; copy-pasting a test class without removing the old one; merging branches that each added a screenshot test.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The upgrader overlaps with another upgrader.
- Service is already registered with this type
- No [UITest] class found in
- No [UITest] class named
- Multiple [UITest] classes in
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/aef8fe4ecb1abfff.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Games.AutoTesting/ScreenshotTestRunner.cs:43
/// before exiting the game.
/// </summary>
public static class AutoTestingBootstrap
{
/// <summary>The test registered for this run, or null if none (non-test build).</summary>
internal static IScreenshotTest? RegisteredTest { get; private set; }
/// <summary>
/// Registers the screenshot-test driver for this sample. Called from a [ModuleInitializer] in
/// the test fixture: invoking a method here force-loads this assembly (so the
/// <see cref="ScreenshotTestRunner"/>'s [ModuleInitializer] runs and hooks
/// <see cref="Game.GameStarted"/>), and hands over the concrete instance directly — no reflection,
/// keeping the discovery path trim/AOT-clean.
/// </summary>
public static void RegisterTest(IScreenshotTest test)
{
ArgumentNullException.ThrowIfNull(test);
if (RegisteredTest is not null && RegisteredTest.GetType() != test.GetType())
throw new InvalidOperationException(
$"Stride.Games.AutoTesting: '{RegisteredTest.GetType().FullName}' is already registered; " +
$"exactly one [ScreenshotTest] per sample is allowed (tried to register '{test.GetType().FullName}').");
RegisteredTest = test;
}
}
/// <summary>
/// Installs native-crash diagnostics for the sample run from a [ModuleInitializer].
/// See <see cref="NativeCrashHandler"/> (shared with Stride.Graphics.Regression).
/// </summary>
internal static class CrashDiagnostics
{
[ModuleInitializer]
internal static void Initialize()
{
NativeCrashHandler.Install();
// Crash-report popups must never interrupt a test run: unless the caller chose a mode, toolsView on GitHub (pinned to 96fad776d2)