# Gds Test Automate

> Generate automated game tests for gameplay systems. Use when the user says "automate tests" or "generate tests"

- Skill: `bmad-code-org/gds-test-automate` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add bmad-code-org/gds-test-automate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bmad-code-org/gds-test-automate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bmad-code-org (https://skillmd.com/u/bmad-code-org)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/bmad-code-org/gds-test-automate

---


# 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: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-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

<workflow>

<step n="1" goal="Analyze Codebase">
  <action>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
  </action>
  <action>Load the appropriate engine-specific knowledge fragment</action>
  <action>Identify testable systems in the codebase:
    - Pure logic classes (calculators, managers)
    - State machines (AI, gameplay)
    - Data structures (inventory, save data)
  </action>
  <action>Locate existing tests:
    - Find test directory structure
    - Identify test patterns already in use
    - Check for test helpers/fixtures
  </action>
</step>

<step n="2" goal="Generate Unit Tests">
  <action>For each identified testable system, generate a test file using the appropriate engine template below</action>

  <!-- Unity (C#) -->
  <check if="engine == 'unity'">
    <action>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);
    }
}
```
    </action>
  </check>

  <!-- Unreal (C++) -->
  <check if="engine == 'unreal'">
    <action>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;
}
```
    </action>
  </check>

  <!-- Godot (GDScript) -->
  <check if="engine == 'godot'">
    <action>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)
```
    </action>
  </check>

  <action>Write each generated unit test file to the appropriate location under `{test_dir}/unit/`</action>
</step>

<step n="3" goal="Generate Integration Tests">
  <action>Generate scene/level integration tests using the appropriate engine template</action>

  <check if="engine == 'unity'">
    <action>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<ErrorHandler>()
        .Where(e => e.HasErrors);
    Assert.IsEmpty(errors, "Scene should load without errors");
}
```
    </action>
  </check>

  <check if="engine == 'unreal'">
    <action>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}");
}
```
    </action>
  </check>

  <check if="engine == 'godot'">
    <action>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()
```
    </action>
  </check>

  <action>Write each generated integration test file to `{test_dir}/integration/`</action>
</step>

<step n="3.5" goal="Generate E2E Infrastructure">
  <action>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
  </action>
  <action>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();
    }
}
```
  </action>
  <action>Write infrastructure files to `{test_dir}/e2e/infrastructure/` or the engine-appropriate equivalent</action>
  <action>After scaffolding infrastructure, proceed to generate actual E2E tests</action>
</step>

<step n="4" goal="Generate Smoke Tests">
  <action>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
  </action>
  <action>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<Button>().onClick.Invoke();
    yield return new WaitForSeconds(5f);
    var player = GameObject.FindWithTag("Player");
    Assert.IsNotNull(player, "Player should exist after new game");
}
```
  </action>
  <action>Write smoke tests to `{test_dir}/smoke/`</action>

  <!-- Anti-patterns to actively avoid -->
  <action>Ensure generated tests do NOT:
    - Test engine functionality (not game logic)
    - Use hard-coded waits as primary sync (use signals/events)
    - Depend on execution order
    - Lack cleanup in teardown
  </action>
</step>

<step n="5" goal="Generate Test Report">
  <action>After all test files have been written, create an automation summary at `{default_output_file}` using this structure:

```markdown
## Automation Summary

**Engine**: {Unity | Unreal | Godot}
**Tests Generated**: {count}
**Date**: {date}

### Test Distribution

| Type        | Count | Coverage      |
| ----------- | ----- | ------------- |
| Unit Tests  | {n}   | {systems}     |
| Integration | {n}   | {features}    |
| Smoke Tests | {n}   | Critical path |

### Files Created

- `tests/unit/{file1}.{ext}`
- `tests/integration/{file2}.{ext}`
- `tests/smoke/{file3}.{ext}`

### Next Steps

1. Review generated tests
2. Fill in test-specific logic where placeholders remain
3. Run tests to verify they pass
4. Add to CI pipeline
```
  </action>
  <action>Load and apply `{validation}` checklist to verify all deliverables are complete</action>
  <action>Present the automation summary to the user</action>
<action>Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow.on_complete` — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action>
</step>

</workflow>

