--- name: gds-test-automate description: 'Generate automated game tests for gameplay systems. Use when the user says "automate tests" or "generate tests"' --- # Game Test Automation Workflow **Goal:** Generate automated test code for game projects based on test design scenarios or by analyzing existing game code. Creates engine-appropriate tests for Unity, Unreal, or Godot with proper patterns, fixtures, and cleanup. **Your Role:** You are a senior game QA engineer and test automation specialist. Work autonomously to analyze the game codebase, detect the engine in use, and generate well-structured unit, integration, and smoke tests. You bring structured testing knowledge and engine-specific patterns, while the user brings domain context about the game's systems. --- ## Conventions - Bare paths (e.g. `template.md`) resolve from the skill root. - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives). - `{project-root}`-prefixed paths resolve from the project working directory. - `{skill-name}` resolves to the skill directory's basename. ## On Activation ### Step 1: Resolve the Workflow Block Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow` **If the script fails**, resolve the `workflow` block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver: 1. `{skill-root}/customize.toml` — defaults 2. `{project-root}/_bmad/custom/{skill-name}.toml` — team overrides 3. `{project-root}/_bmad/custom/{skill-name}.user.toml` — personal overrides Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by `code` or `id` replace matching entries and append new entries, and all other arrays append. ### Step 2: Execute Prepend Steps Execute each entry in `{workflow.activation_steps_prepend}` in order before proceeding. ### Step 3: Load Persistent Facts Treat every entry in `{workflow.persistent_facts}` as foundational context you carry for the rest of the workflow run. Entries prefixed `file:` are paths or globs under `{project-root}` — load the referenced contents as facts. All other entries are facts verbatim. ### Step 4: Load Config Load config from `{project-root}/_bmad/gds/config.yaml` and resolve: - `user_name` - `communication_language` - `output_folder` - `date` as the system-generated current datetime ### Step 5: Greet the User Greet `{user_name}`, speaking in `{communication_language}`. ### Step 6: Execute Append Steps Execute each entry in `{workflow.activation_steps_append}` in order. Activation is complete. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed. ## WORKFLOW ARCHITECTURE This uses an **inline workflow pattern** for autonomous execution: - Steps execute sequentially with full autonomy - Engine detection drives all subsequent decisions - All test files are written to disk as they are generated - A final summary report is produced at completion ### Preflight Requirements Before proceeding, verify: - Test framework already initialized (run `framework` workflow first) - Test scenarios defined (from `test-design` workflow or ad-hoc) - Game code accessible for analysis If any preflight requirement is not met, HALT and guide the user. ### Paths - `installed_path` = `{skill_root}` - `validation` = `{installed_path}/checklist.md` - `test_dir` = `{project-root}/tests` - `source_dir` = `{project-root}/src` ### Variables - `coverage_target` = `critical-paths` (options: `critical-paths`, `comprehensive`, `selective`) - `game_engine` = `auto` (options: `auto`, `unity`, `unreal`, `godot`) - `default_output_file` = `{output_folder}/automation-summary.md` ### Knowledge Fragments Load the engine-specific knowledge fragment after engine detection in Step 1: - Unity: `{installed_path}/knowledge/unity-testing.md` - Unreal: `{installed_path}/knowledge/unreal-testing.md` - Godot: `{installed_path}/knowledge/godot-testing.md` - E2E patterns: `{installed_path}/knowledge/e2e-testing.md` --- ## EXECUTION Detect Game Engine by checking for engine-specific project files: - Unity: `Assets/`, `ProjectSettings/`, `*.unity` scenes - Unreal: `*.uproject`, `Source/`, `Config/DefaultEngine.ini` - Godot: `project.godot`, `*.tscn`, `*.gd` files Load the appropriate engine-specific knowledge fragment Identify testable systems in the codebase: - Pure logic classes (calculators, managers) - State machines (AI, gameplay) - Data structures (inventory, save data) Locate existing tests: - Find test directory structure - Identify test patterns already in use - Check for test helpers/fixtures For each identified testable system, generate a test file using the appropriate engine template below Generate NUnit test fixtures following this pattern: ```csharp using NUnit.Framework; [TestFixture] public class {ClassName}Tests { private {ClassName} _sut; [SetUp] public void Setup() { _sut = new {ClassName}(); } [Test] public void {MethodName}_When{Condition}_Should{Expectation}() { // Arrange {setup_code} // Act var result = _sut.{MethodName}({parameters}); // Assert Assert.AreEqual({expected}, result); } [TestCase({input1}, {expected1})] [TestCase({input2}, {expected2})] public void {MethodName}_Parameterized({inputType} input, {outputType} expected) { var result = _sut.{MethodName}(input); Assert.AreEqual(expected, result); } } ``` Generate Automation Test macros following this pattern: ```cpp #include "Misc/AutomationTest.h" IMPLEMENT_SIMPLE_AUTOMATION_TEST( F{ClassName}{MethodName}Test, "{ProjectName}.{Category}.{TestName}", EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter ) bool F{ClassName}{MethodName}Test::RunTest(const FString& Parameters) { // Arrange {setup_code} // Act auto Result = {ClassName}::{MethodName}({parameters}); // Assert TestEqual("{assertion_message}", Result, {expected}); return true; } ``` Generate GUT test files following this pattern: ```gdscript extends GutTest var _sut: {ClassName} func before_each(): _sut = {ClassName}.new() func after_each(): _sut.free() func test_{method_name}_when_{condition}_should_{expectation}(): # Arrange {setup_code} # Act var result = \_sut.{method_name}({parameters}) # Assert assert_eq(result, {expected}, "{assertion_message}") func test_{method_name}_parameterized(): var test_cases = [ {"input": {input1}, "expected": {expected1}}, {"input": {input2}, "expected": {expected2}} ] for tc in test_cases: var result = \_sut.{method_name}(tc.input) assert_eq(result, tc.expected) ``` Write each generated unit test file to the appropriate location under `{test_dir}/unit/` Generate scene/level integration tests using the appropriate engine template Generate Unity Play Mode integration tests: ```csharp [UnityTest] public IEnumerator {SceneName}_Loads_WithoutErrors() { SceneManager.LoadScene("{scene_name}"); yield return new WaitForSeconds(2f); var errors = GameObject.FindObjectsOfType() .Where(e => e.HasErrors); Assert.IsEmpty(errors, "Scene should load without errors"); } ``` Generate Unreal Functional Test actors: ```cpp void A{TestName}::StartTest() { Super::StartTest(); {setup} if ({condition}) FinishTest(EFunctionalTestResult::Succeeded, "{message}"); else FinishTest(EFunctionalTestResult::Failed, "{failure_message}"); } ``` Generate Godot integration tests: ```gdscript func test_{feature}_integration(): var scene = load("res://scenes/{scene}.tscn").instantiate() add_child(scene) await get_tree().process_frame {test_code} scene.queue_free() ``` Write each generated integration test file to `{test_dir}/integration/` Before generating E2E tests, scaffold the required infrastructure components: 1. Test Fixture Base Class — scene loading/unloading, game ready state waiting, common service access, cleanup guarantees 2. Scenario Builder — fluent API for game state configuration, domain-specific methods, yields for state propagation 3. Input Simulator — click/drag abstractions, button press simulation, keyboard input queuing 4. Async Assertions — WaitUntil with timeout and message, WaitForEvent for event-driven flows, WaitForState for state machine transitions Generate the GameE2ETestFixture base class using this template: ```csharp public abstract class GameE2ETestFixture { protected {GameStateClass} GameState; protected {InputSimulatorClass} Input; protected {ScenarioBuilderClass} Scenario; [UnitySetUp] public IEnumerator BaseSetUp() { yield return LoadScene("{main_scene}"); GameState = Object.FindFirstObjectByType<{GameStateClass}>(); Input = new {InputSimulatorClass}(); Scenario = new {ScenarioBuilderClass}(GameState); yield return WaitForReady(); } } ``` Write infrastructure files to `{test_dir}/e2e/infrastructure/` or the engine-appropriate equivalent After scaffolding infrastructure, proceed to generate actual E2E tests Create critical path tests that run on every build, covering: 1. Game launches without crash 2. Main menu is navigable 3. New game starts successfully 4. Core gameplay loop executes 5. Save/load works Generate engine-appropriate smoke tests, for example (Unity): ```csharp [UnityTest, Timeout(60000)] public IEnumerator Smoke_NewGame_StartsSuccessfully() { SceneManager.LoadScene("MainMenu"); yield return new WaitForSeconds(2f); var newGameButton = GameObject.Find("NewGameButton"); newGameButton.GetComponent