ppy/osu · error · InvalidOperationException
Cannot add custom test steps without {nameof(HasCustomSteps)
Error message
Cannot add custom test steps without {nameof(HasCustomSteps)} being set. What it means
PlayerTestScene auto-invokes CreateTest() from SetUpSteps when HasCustomSteps is false. Passing a custom action to CreateTest is only allowed when the subclass opts in via HasCustomSteps => true; otherwise the throw prevents conflicting/duplicate step registration.
Source
Thrown at osu.Game/Tests/Visual/PlayerTestScene.cs:67
private void load()
{
Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage));
LocalConfig.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
}
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
if (!HasCustomSteps)
CreateTest();
}
protected void CreateTest([CanBeNull] Action action = null)
{
if (action != null && !HasCustomSteps)
throw new InvalidOperationException($"Cannot add custom test steps without {nameof(HasCustomSteps)} being set.");
action?.Invoke();
AddStep($"Load player for {CreatePlayerRuleset().Description}", LoadPlayer);
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
}
protected virtual bool AllowFail => false;
protected virtual bool AllowBackwardsSeeks => false;
protected virtual bool Autoplay => false;
protected void LoadPlayer() => LoadPlayer(Array.Empty<Mod>());
protected void LoadPlayer(Mod[] mods)
{
// if a player screen is present already, we must exit that before loading another one,
View on GitHub (pinned to d9c73e12ad)
Solutions
- Override `protected override bool HasCustomSteps => true;` in your test scene, then call CreateTest(action).
- If you have no custom steps, call CreateTest() with no argument (the default path handles it).
Example fix
// before
public class MyTestScene : PlayerTestScene
{
public MyTestScene() => CreateTest(() => AddStep("setup", doSetup)); // throws
}
// after
public class MyTestScene : PlayerTestScene
{
protected override bool HasCustomSteps => true;
public MyTestScene() => CreateTest(() => AddStep("setup", doSetup));
} Defensive patterns
Strategy: validation
Validate before calling
// opt in to custom steps before passing any action to CreateTest
protected override bool HasCustomSteps => true;
// now safe:
CreateTest(() => AddStep("setup", doSetup)); Prevention
- Override HasCustomSteps => true the moment you pass an action to CreateTest.
- If you have no custom steps, call CreateTest() with no argument.
- Keep custom setup in CreateTest so the auto-invocation in SetUpSteps cooperates.
When it happens
Trigger: Calling CreateTest(someAction) (with a non-null action) in a PlayerTestScene subclass that did not override HasCustomSteps => true.
Common situations: Adding bespoke setup steps to a player test; subclassing PlayerTestScene for the first time and wanting custom AddStep calls.
Related errors
- Accessing the client-side API room via {nameof(TestMultiplay
- Accessing the client-side room via {nameof(TestMultiplayerCl
- Already joined a room
- Invalid password.
- Local user is not the room host.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/71d925aecc27027f.
Report an issue: GitHub.