name: gds-test-automate
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.
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.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:
{skill-root}/customize.toml — defaults{project-root}/_bmad/custom/{skill-name}.toml — team overrides{project-root}/_bmad/custom/{skill-name}.user.toml — personal overridesAny 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.
Execute each entry in {workflow.activation_steps_prepend} in order before proceeding.
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.
Load config from {project-root}/_bmad/gds/config.yaml and resolve:
user_namecommunication_languageoutput_folderdate as the system-generated current datetimeGreet {user_name}, speaking in {communication_language}.
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.
This uses an inline workflow pattern for autonomous execution:
Before proceeding, verify:
framework workflow first)test-design workflow or ad-hoc)If any preflight requirement is not met, HALT and guide the user.
installed_path = {skill_root}validation = {installed_path}/checklist.mdtest_dir = {project-root}/testssource_dir = {project-root}/srccoverage_target = critical-paths (options: critical-paths, comprehensive, selective)game_engine = auto (options: auto, unity, unreal, godot)default_output_file = {output_folder}/automation-summary.mdLoad the engine-specific knowledge fragment after engine detection in Step 1:
{installed_path}/knowledge/unity-testing.md{installed_path}/knowledge/unreal-testing.md{installed_path}/knowledge/godot-testing.md{installed_path}/knowledge/e2e-testing.md
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
<action>Generate NUnit test fixtures following this pattern:
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>
<action>Generate Automation Test macros following this pattern:
#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>
<action>Generate GUT test files following this pattern:
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>
Write each generated unit test file to the appropriate location under {test_dir}/unit/
Generate scene/level integration tests using the appropriate engine template
<action>Generate Unity Play Mode integration tests:
[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>
<action>Generate Unreal Functional Test actors:
void A{TestName}::StartTest()
{
Super::StartTest();
{setup}
if ({condition})
FinishTest(EFunctionalTestResult::Succeeded, "{message}");
else
FinishTest(EFunctionalTestResult::Failed, "{failure_message}");
}
</action>
<action>Generate Godot integration tests:
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>
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:
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):
[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");
}
Write smoke tests to {test_dir}/smoke/
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
After all test files have been written, create an automation summary at {default_output_file} using this structure:
## 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
Load and apply {validation} checklist to verify all deliverables are complete
Present the automation summary to the user
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.