--- name: gds-e2e-scaffold description: 'Scaffold end-to-end testing infrastructure. Use when the user says "e2e scaffold" or "set up e2e testing"' --- # E2E Test Infrastructure Scaffold Workflow **Goal:** Scaffold complete E2E testing infrastructure for an existing game project — creating the foundation required for reliable, maintainable end-to-end tests: test fixtures, scenario builders, input simulators, and async assertion utilities, all tailored to the project's specific architecture. **Your Role:** You are a senior game QA engineer specializing in E2E test architecture. E2E tests validate complete player journeys. Without proper infrastructure, they become brittle nightmares. Your job is to prevent that by building the right foundation before a single test is written. Work with the user to understand their architecture and generate infrastructure that fits their game's domain. --- ## 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` ### 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 critical architecture analysis upfront - Engine detection and domain discovery drive all generated code - All infrastructure files are written to disk as they are generated - A working example test proves the infrastructure functions correctly ### Triggers - `ES` - `e2e-scaffold` - `scaffold e2e` - `e2e infrastructure` - `setup e2e` ### Preflight Requirements **Critical:** Verify these requirements before proceeding. If any fail, HALT and guide the user. - Test framework already initialized (run `test-framework` workflow first) - Game has identifiable state manager class - Main gameplay scene exists and is functional - No existing E2E infrastructure (check for `Tests/PlayMode/E2E/` or engine equivalent) ### Paths - `installed_path` = `{skill-root}` - `validation` = `{installed_path}/checklist.md` ### Inputs (Collect from User or Auto-Detect) | Input | Description | Required | Default | |-------|-------------|----------|---------| | `game_state_class` | Primary game state manager class name | Yes | — | | `main_scene` | Scene name where core gameplay occurs | Yes | — | | `input_system` | Input system in use | No | `auto-detect` | ### Knowledge Fragments Load `{installed_path}/knowledge/e2e-testing.md` before proceeding. Load the engine-specific fragment after 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` --- ## 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 core systems: 1. Game State Manager — the primary class holding game state. Look for: `GameManager`, `GameStateManager`, `GameController`, `GameMode`. Note: initialization method, ready state property, save/load methods 2. Input Handling — Unity New Input System vs Legacy, Unreal Enhanced Input vs Legacy, Godot built-in Input, or custom abstraction layer 3. Event/Messaging System — event bus, C# events/delegates, UnityEvents, Godot Signals 4. Scene Structure — main gameplay scene name, loading approach (additive/single), bootstrap/initialization flow Identify domain concepts for the ScenarioBuilder: - Primary Entities: units, players, items, enemies, etc. - State Machine States: turn phases, game modes, player states - Spatial System: grid/hex positions, world coordinates, regions - Resources: currency, health, mana, ammunition, etc. Check existing test structure. If `Tests/PlayMode/E2E/` (or engine equivalent) already exists, HALT and ask user how to proceed. Create the E2E directory structure: ``` Tests/PlayMode/E2E/ (Unity) ├── E2E.asmdef ├── Infrastructure/ │ ├── GameE2ETestFixture.cs │ ├── ScenarioBuilder.cs │ ├── InputSimulator.cs │ └── AsyncAssert.cs ├── Scenarios/ │ └── (empty - user will add tests here) ├── TestData/ │ └── (empty - user will add fixtures here) └── README.md ``` Generate Assembly Definition `E2E.asmdef`: ```json { "name": "E2E", "rootNamespace": "{ProjectNamespace}.Tests.E2E", "references": ["{GameAssemblyName}", "Unity.InputSystem", "Unity.InputSystem.TestFramework"], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": ["nunit.framework.dll", "UnityEngine.TestRunner.dll", "UnityEditor.TestRunner.dll"], "autoReferenced": false, "defineConstraints": ["UNITY_INCLUDE_TESTS"], "versionDefines": [], "noEngineReferences": false } ``` Replace `{ProjectNamespace}` with detected project namespace and `{GameAssemblyName}` with main game assembly. Include `Unity.InputSystem` references only if Input System package detected. Generate `GameE2ETestFixture.cs` base class. Customize placeholders: - `{Namespace}` = detected project namespace - `{MainSceneName}` = detected main gameplay scene - `{GameStateClass}` = identified game state manager class - `{IsReadyProperty}` = property indicating game is initialized (e.g., `IsReady`, `IsInitialized`) The fixture must handle: scene loading/unloading, game ready state waiting, access to GameState/Input/Scenario, cleanup guarantees, and failure screenshot capture. Generate `ScenarioBuilder.cs` with fluent API. Analyze the game's domain model from Step 1 and add 3-5 concrete configuration methods based on identified entities. Include `FromSaveFile(string fileName)` as base method. Add domain-specific methods in the `#region State Configuration` block with TODO comments documenting the pattern. Generate `InputSimulator.cs`. If New Input System detected: - `ClickWorldPosition(Vector3)`, `ClickScreenPosition(Vector2)`, `ClickButton(string)`, `DragFromTo(Vector3, Vector3, float)` using `InputState.Change` and `StateEvent.From` - `PressKey(Key)`, `HoldKey(Key, float)` for keyboard - `Reset()` and `RefreshCamera()` utility methods If Legacy Input detected, generate simpler version using UI event triggering. Generate `AsyncAssert.cs` static utility class with: - `WaitUntil(Func, string, float)` — core wait-for-condition - `WaitUntilVerbose(...)` — with periodic debug logging - `WaitForValue(...)` — wait for specific value (exact equality) - `WaitForValueApprox(...)` — float/double with tolerance - `WaitForValueNot(...)` — wait for value to change - `WaitForNotNull(...)` and `WaitForUnityObject(...)` - `AssertNeverTrue(...)` — assert something doesn't happen - `WaitFrames(int)` and `WaitForPhysics(int)` utility methods Generate equivalent infrastructure files under `Source/{ProjectName}/Tests/E2E/`: - `GameE2ETestBase.h/.cpp` — base test class - `ScenarioBuilder.h/.cpp` — fluent scenario configuration - `InputSimulator.h/.cpp` — input abstraction - `AsyncAssert.h` — wait-for-condition utilities - `{ProjectName}E2ETests.Build.cs` — build configuration Generate equivalent infrastructure files under `tests/e2e/infrastructure/`: - `game_e2e_test_fixture.gd` - `scenario_builder.gd` - `input_simulator.gd` - `async_assert.gd` Write all infrastructure files to disk Create a working E2E test that exercises the infrastructure and proves it works Generate `ExampleE2ETest.cs` with three tests: 1. `Infrastructure_GameLoadsAndReachesReadyState` — verifies base fixture, GameState, Input, Scenario are all non-null and game reaches ready state 2. `Infrastructure_InputSimulatorCanClickButtons` — demonstrates input simulation pattern with commented example for the user to customize 3. `Infrastructure_ScenarioBuilderCanConfigureState` — demonstrates ScenarioBuilder usage with commented domain-specific example Apply `[Category("E2E")]` attribute to the class. Generate equivalent example test in the engine-appropriate location and language, covering the same three verification patterns Write example test file to disk Create `README.md` in the E2E root directory covering: - Quick Start (inherit from GameE2ETestFixture, use Scenario/Input/AsyncAssert) - Example test using Given-When-Then structure - Component documentation for GameE2ETestFixture, ScenarioBuilder, InputSimulator, AsyncAssert - Directory structure explanation - Running tests (Editor UI and command line) - Best practices (wait for conditions not time, one journey per test, descriptive assertions) - Extension guide (adding Scenario methods, adding Input methods) - Troubleshooting table for common issues Write README.md to disk Load and apply `{validation}` checklist to verify all deliverables are complete Present a summary to the user: ```markdown ## E2E Infrastructure Scaffold Complete **Engine**: {Unity | Unreal | Godot} **Version**: {detected_version} ### Files Created [Directory tree of all created files] ### Configuration | Setting | Value | |---------|-------| | Game State Class | `{GameStateClass}` | | Main Scene | `{MainSceneName}` | | Input System | `{InputSystemType}` | | Ready Property | `{IsReadyProperty}` | ### Customization Required 1. ScenarioBuilder: Add domain-specific setup methods for your game entities 2. InputSimulator: Add game-specific input methods (e.g., hex clicking, gesture shortcuts) 3. ExampleE2ETest: Modify example tests to use your actual UI elements ### Next Steps 1. Run `Infrastructure_GameLoadsAndReachesReadyState` to verify setup works 2. Extend `ScenarioBuilder` with your domain methods 3. Extend `InputSimulator` with game-specific input helpers 4. Use `test-design` workflow to identify E2E scenarios 5. Use `automate` workflow to generate E2E tests from scenarios ``` Run: `python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.