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

  1. Override `protected override bool HasCustomSteps => true;` in your test scene, then call CreateTest(action).
  2. 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

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


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/71d925aecc27027f. Report an issue: GitHub.