{"record":{"id":"71d925aecc27027f","repo":"ppy/osu","slug":"cannot-add-custom-test-steps-without-nameof-hascu","errorCode":null,"errorMessage":"Cannot add custom test steps without {nameof(HasCustomSteps)} being set.","messagePattern":"Cannot add custom test steps without (.+?) being set\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Tests/Visual/PlayerTestScene.cs","lineNumber":67,"sourceCode":"        private void load()\r\n        {\r\n            Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage));\r\n            LocalConfig.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;\r\n        }\r\n\r\n        [SetUpSteps]\r\n        public override void SetUpSteps()\r\n        {\r\n            base.SetUpSteps();\r\n\r\n            if (!HasCustomSteps)\r\n                CreateTest();\r\n        }\r\n\r\n        protected void CreateTest([CanBeNull] Action action = null)\r\n        {\r\n            if (action != null && !HasCustomSteps)\r\n                throw new InvalidOperationException($\"Cannot add custom test steps without {nameof(HasCustomSteps)} being set.\");\r\n\r\n            action?.Invoke();\r\n\r\n            AddStep($\"Load player for {CreatePlayerRuleset().Description}\", LoadPlayer);\r\n            AddUntilStep(\"player loaded\", () => Player.IsLoaded && Player.Alpha == 1);\r\n        }\r\n\r\n        protected virtual bool AllowFail => false;\r\n\r\n        protected virtual bool AllowBackwardsSeeks => false;\r\n\r\n        protected virtual bool Autoplay => false;\r\n\r\n        protected void LoadPlayer() => LoadPlayer(Array.Empty<Mod>());\r\n\r\n        protected void LoadPlayer(Mod[] mods)\r\n        {\r\n            // if a player screen is present already, we must exit that before loading another one,\r","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Tests/Visual/PlayerTestScene.cs#L49-L85","documentation":"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.","triggerScenarios":"Calling CreateTest(someAction) (with a non-null action) in a PlayerTestScene subclass that did not override HasCustomSteps => true.","commonSituations":"Adding bespoke setup steps to a player test; subclassing PlayerTestScene for the first time and wanting custom AddStep calls.","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)."],"exampleFix":"// before\npublic class MyTestScene : PlayerTestScene\n{\n    public MyTestScene() => CreateTest(() => AddStep(\"setup\", doSetup)); // throws\n}\n\n// after\npublic class MyTestScene : PlayerTestScene\n{\n    protected override bool HasCustomSteps => true;\n    public MyTestScene() => CreateTest(() => AddStep(\"setup\", doSetup));\n}","handlingStrategy":"validation","validationCode":"// opt in to custom steps before passing any action to CreateTest\nprotected override bool HasCustomSteps => true;\n// now safe:\nCreateTest(() => AddStep(\"setup\", doSetup));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["test-infra","player-testscene","test-steps"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}