Compare commits
1 Commits
7e143b7a47
...
b4df7b7f12
| Author | SHA1 | Date |
|---|---|---|
|
|
b4df7b7f12 |
|
|
@ -472,8 +472,6 @@ public static class AsyncAssert
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for a specific value, with descriptive failure.
|
/// Wait for a specific value, with descriptive failure.
|
||||||
/// Note: For floating-point comparisons, use WaitForValueApprox instead
|
|
||||||
/// to handle precision issues. This method uses exact equality.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerator WaitForValue<T>(
|
public static IEnumerator WaitForValue<T>(
|
||||||
Func<T> getter,
|
Func<T> getter,
|
||||||
|
|
@ -486,39 +484,7 @@ public static class AsyncAssert
|
||||||
$"{description} to equal {expected} (current: {getter()})",
|
$"{description} to equal {expected} (current: {getter()})",
|
||||||
timeout);
|
timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait for a float value within tolerance (handles floating-point precision).
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerator WaitForValueApprox(
|
|
||||||
Func<float> getter,
|
|
||||||
float expected,
|
|
||||||
string description,
|
|
||||||
float tolerance = 0.0001f,
|
|
||||||
float timeout = 5f)
|
|
||||||
{
|
|
||||||
yield return WaitUntil(
|
|
||||||
() => Mathf.Abs(expected - getter()) < tolerance,
|
|
||||||
$"{description} to equal ~{expected} ±{tolerance} (current: {getter()})",
|
|
||||||
timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait for a double value within tolerance (handles floating-point precision).
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerator WaitForValueApprox(
|
|
||||||
Func<double> getter,
|
|
||||||
double expected,
|
|
||||||
string description,
|
|
||||||
double tolerance = 0.0001,
|
|
||||||
float timeout = 5f)
|
|
||||||
{
|
|
||||||
yield return WaitUntil(
|
|
||||||
() => Math.Abs(expected - getter()) < tolerance,
|
|
||||||
$"{description} to equal ~{expected} ±{tolerance} (current: {getter()})",
|
|
||||||
timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for an event to fire.
|
/// Wait for an event to fire.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -676,22 +642,7 @@ public IEnumerator SaveLoad_PreservesGameState()
|
||||||
"Movement points should be preserved");
|
"Movement points should be preserved");
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
var savedFilePath = GameState.GetSavePath(savePath);
|
System.IO.File.Delete(GameState.GetSavePath(savePath));
|
||||||
if (System.IO.File.Exists(savedFilePath))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
System.IO.File.Delete(savedFilePath);
|
|
||||||
}
|
|
||||||
catch (System.IO.IOException ex)
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"[E2E] Failed to delete test save file '{savedFilePath}': {ex.Message}");
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException ex)
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"[E2E] Access denied deleting test save file '{savedFilePath}': {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -793,17 +744,12 @@ Tests/
|
||||||
{
|
{
|
||||||
"name": "E2E",
|
"name": "E2E",
|
||||||
"references": [
|
"references": [
|
||||||
"GameAssembly"
|
"GameAssembly",
|
||||||
|
"UnityEngine.TestRunner",
|
||||||
|
"UnityEditor.TestRunner"
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
"allowUnsafeCode": false,
|
|
||||||
"overrideReferences": true,
|
|
||||||
"precompiledReferences": [
|
|
||||||
"nunit.framework.dll",
|
|
||||||
"UnityEngine.TestRunner.dll",
|
|
||||||
"UnityEditor.TestRunner.dll"
|
|
||||||
],
|
|
||||||
"defineConstraints": [
|
"defineConstraints": [
|
||||||
"UNITY_INCLUDE_TESTS"
|
"UNITY_INCLUDE_TESTS"
|
||||||
],
|
],
|
||||||
|
|
@ -850,18 +796,16 @@ Capture screenshots and logs on failure:
|
||||||
[UnityTearDown]
|
[UnityTearDown]
|
||||||
public IEnumerator CaptureOnFailure()
|
public IEnumerator CaptureOnFailure()
|
||||||
{
|
{
|
||||||
// Yield first to ensure we're on the main thread for screenshot capture
|
|
||||||
yield return null;
|
|
||||||
|
|
||||||
if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed)
|
if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed)
|
||||||
{
|
{
|
||||||
var screenshot = ScreenCapture.CaptureScreenshotAsTexture();
|
var screenshot = ScreenCapture.CaptureScreenshotAsTexture();
|
||||||
var bytes = screenshot.EncodeToPNG();
|
var bytes = screenshot.EncodeToPNG();
|
||||||
var screenshotPath = $"TestResults/Screenshots/{TestContext.CurrentContext.Test.Name}.png";
|
var path = $"TestResults/Screenshots/{TestContext.CurrentContext.Test.Name}.png";
|
||||||
System.IO.File.WriteAllBytes(screenshotPath, bytes);
|
System.IO.File.WriteAllBytes(path, bytes);
|
||||||
|
|
||||||
Debug.Log($"[E2E FAILURE] Screenshot saved: {screenshotPath}");
|
Debug.Log($"[E2E FAILURE] Screenshot saved: {path}");
|
||||||
}
|
}
|
||||||
|
yield return null;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,11 @@
|
||||||
## Assembly Definition
|
## Assembly Definition
|
||||||
|
|
||||||
- [ ] References main game assembly
|
- [ ] References main game assembly
|
||||||
|
- [ ] References UnityEngine.TestRunner
|
||||||
|
- [ ] References UnityEditor.TestRunner
|
||||||
- [ ] References Unity.InputSystem (if applicable)
|
- [ ] References Unity.InputSystem (if applicable)
|
||||||
- [ ] `overrideReferences` set to true
|
|
||||||
- [ ] `precompiledReferences` includes nunit.framework.dll
|
|
||||||
- [ ] `precompiledReferences` includes UnityEngine.TestRunner.dll
|
|
||||||
- [ ] `precompiledReferences` includes UnityEditor.TestRunner.dll
|
|
||||||
- [ ] `UNITY_INCLUDE_TESTS` define constraint set
|
- [ ] `UNITY_INCLUDE_TESTS` define constraint set
|
||||||
|
- [ ] nunit.framework.dll in precompiled references
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,8 @@ Tests/PlayMode/E2E/
|
||||||
"rootNamespace": "{ProjectNamespace}.Tests.E2E",
|
"rootNamespace": "{ProjectNamespace}.Tests.E2E",
|
||||||
"references": [
|
"references": [
|
||||||
"{GameAssemblyName}",
|
"{GameAssemblyName}",
|
||||||
|
"UnityEngine.TestRunner",
|
||||||
|
"UnityEditor.TestRunner",
|
||||||
"Unity.InputSystem",
|
"Unity.InputSystem",
|
||||||
"Unity.InputSystem.TestFramework"
|
"Unity.InputSystem.TestFramework"
|
||||||
],
|
],
|
||||||
|
|
@ -130,9 +132,7 @@ Tests/PlayMode/E2E/
|
||||||
"allowUnsafeCode": false,
|
"allowUnsafeCode": false,
|
||||||
"overrideReferences": true,
|
"overrideReferences": true,
|
||||||
"precompiledReferences": [
|
"precompiledReferences": [
|
||||||
"nunit.framework.dll",
|
"nunit.framework.dll"
|
||||||
"UnityEngine.TestRunner.dll",
|
|
||||||
"UnityEditor.TestRunner.dll"
|
|
||||||
],
|
],
|
||||||
"autoReferenced": false,
|
"autoReferenced": false,
|
||||||
"defineConstraints": [
|
"defineConstraints": [
|
||||||
|
|
@ -474,30 +474,24 @@ namespace {Namespace}.Tests.E2E
|
||||||
{
|
{
|
||||||
var button = GameObject.Find(buttonName)?
|
var button = GameObject.Find(buttonName)?
|
||||||
.GetComponent<UnityEngine.UI.Button>();
|
.GetComponent<UnityEngine.UI.Button>();
|
||||||
|
|
||||||
if (button == null)
|
if (button == null)
|
||||||
{
|
{
|
||||||
// Search in inactive objects within loaded scenes only
|
// Try searching in inactive objects
|
||||||
var buttons = Object.FindObjectsByType<UnityEngine.UI.Button>(
|
var buttons = Resources.FindObjectsOfTypeAll<UnityEngine.UI.Button>();
|
||||||
FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
||||||
foreach (var b in buttons)
|
foreach (var b in buttons)
|
||||||
{
|
{
|
||||||
if (b.name == buttonName && b.gameObject.scene.isLoaded)
|
if (b.name == buttonName)
|
||||||
{
|
{
|
||||||
button = b;
|
button = b;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UnityEngine.Assertions.Assert.IsNotNull(button,
|
UnityEngine.Assertions.Assert.IsNotNull(button,
|
||||||
$"Button '{buttonName}' not found in active scenes");
|
$"Button '{buttonName}' not found");
|
||||||
|
|
||||||
if (!button.interactable)
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"[InputSimulator] Button '{buttonName}' is not interactable");
|
|
||||||
}
|
|
||||||
|
|
||||||
button.onClick.Invoke();
|
button.onClick.Invoke();
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
@ -703,8 +697,6 @@ namespace {Namespace}.Tests.E2E
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for a value to equal expected.
|
/// Wait for a value to equal expected.
|
||||||
/// Note: For floating-point comparisons, use WaitForValueApprox instead
|
|
||||||
/// to handle precision issues. This method uses exact equality.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerator WaitForValue<T>(
|
public static IEnumerator WaitForValue<T>(
|
||||||
Func<T> getter,
|
Func<T> getter,
|
||||||
|
|
@ -717,39 +709,7 @@ namespace {Namespace}.Tests.E2E
|
||||||
$"{description} to equal '{expected}' (current: '{getter()}')",
|
$"{description} to equal '{expected}' (current: '{getter()}')",
|
||||||
timeout);
|
timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait for a float value within tolerance (handles floating-point precision).
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerator WaitForValueApprox(
|
|
||||||
Func<float> getter,
|
|
||||||
float expected,
|
|
||||||
string description,
|
|
||||||
float tolerance = 0.0001f,
|
|
||||||
float timeout = 5f)
|
|
||||||
{
|
|
||||||
yield return WaitUntil(
|
|
||||||
() => Mathf.Abs(expected - getter()) < tolerance,
|
|
||||||
$"{description} to equal ~{expected} ±{tolerance} (current: {getter()})",
|
|
||||||
timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait for a double value within tolerance (handles floating-point precision).
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerator WaitForValueApprox(
|
|
||||||
Func<double> getter,
|
|
||||||
double expected,
|
|
||||||
string description,
|
|
||||||
double tolerance = 0.0001,
|
|
||||||
float timeout = 5f)
|
|
||||||
{
|
|
||||||
yield return WaitUntil(
|
|
||||||
() => Math.Abs(expected - getter()) < tolerance,
|
|
||||||
$"{description} to equal ~{expected} ±{tolerance} (current: {getter()})",
|
|
||||||
timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wait for a value to not equal a specific value.
|
/// Wait for a value to not equal a specific value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -875,8 +835,6 @@ namespace {Namespace}.Tests.E2E
|
||||||
Assert.IsNotNull(Scenario, "ScenarioBuilder should be available");
|
Assert.IsNotNull(Scenario, "ScenarioBuilder should be available");
|
||||||
|
|
||||||
// Verify game is actually ready
|
// Verify game is actually ready
|
||||||
// NOTE: {IsReadyProperty} is a template placeholder. Replace it with your
|
|
||||||
// game's actual ready-state property (e.g., IsReady, IsInitialized, HasLoaded).
|
|
||||||
yield return AsyncAssert.WaitUntil(
|
yield return AsyncAssert.WaitUntil(
|
||||||
() => GameState.{IsReadyProperty},
|
() => GameState.{IsReadyProperty},
|
||||||
"Game should be in ready state");
|
"Game should be in ready state");
|
||||||
|
|
|
||||||
|
|
@ -24,24 +24,23 @@ workflow:
|
||||||
- "Game has identifiable state manager"
|
- "Game has identifiable state manager"
|
||||||
- "Main gameplay scene exists"
|
- "Main gameplay scene exists"
|
||||||
|
|
||||||
# Paths are relative to this workflow file's location
|
|
||||||
knowledge_fragments:
|
knowledge_fragments:
|
||||||
- "../../../gametest/knowledge/e2e-testing.md"
|
- "knowledge/e2e-testing.md"
|
||||||
- "../../../gametest/knowledge/unity-testing.md"
|
- "knowledge/unity-testing.md"
|
||||||
- "../../../gametest/knowledge/unreal-testing.md"
|
- "knowledge/unreal-testing.md"
|
||||||
- "../../../gametest/knowledge/godot-testing.md"
|
- "knowledge/godot-testing.md"
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
game_state_class:
|
game_state_class:
|
||||||
description: "Primary game state manager class name"
|
description: "Primary game state manager class name"
|
||||||
required: true
|
required: true
|
||||||
example: "GameStateManager"
|
example: "GameStateManager"
|
||||||
|
|
||||||
main_scene:
|
main_scene:
|
||||||
description: "Scene name where core gameplay occurs"
|
description: "Scene name where core gameplay occurs"
|
||||||
required: true
|
required: true
|
||||||
example: "GameScene"
|
example: "GameScene"
|
||||||
|
|
||||||
input_system:
|
input_system:
|
||||||
description: "Input system in use"
|
description: "Input system in use"
|
||||||
required: false
|
required: false
|
||||||
|
|
@ -53,90 +52,47 @@ workflow:
|
||||||
- "godot-input"
|
- "godot-input"
|
||||||
- "custom"
|
- "custom"
|
||||||
|
|
||||||
# Output paths vary by engine. Generate files matching detected engine.
|
|
||||||
outputs:
|
outputs:
|
||||||
unity:
|
infrastructure_files:
|
||||||
condition: "engine == 'unity'"
|
description: "Generated E2E infrastructure classes"
|
||||||
infrastructure_files:
|
files:
|
||||||
description: "Generated E2E infrastructure classes"
|
- "Tests/PlayMode/E2E/Infrastructure/GameE2ETestFixture.cs"
|
||||||
files:
|
- "Tests/PlayMode/E2E/Infrastructure/ScenarioBuilder.cs"
|
||||||
- "Tests/PlayMode/E2E/Infrastructure/GameE2ETestFixture.cs"
|
- "Tests/PlayMode/E2E/Infrastructure/InputSimulator.cs"
|
||||||
- "Tests/PlayMode/E2E/Infrastructure/ScenarioBuilder.cs"
|
- "Tests/PlayMode/E2E/Infrastructure/AsyncAssert.cs"
|
||||||
- "Tests/PlayMode/E2E/Infrastructure/InputSimulator.cs"
|
|
||||||
- "Tests/PlayMode/E2E/Infrastructure/AsyncAssert.cs"
|
assembly_definition:
|
||||||
assembly_definition:
|
description: "E2E test assembly configuration"
|
||||||
description: "E2E test assembly configuration"
|
files:
|
||||||
files:
|
- "Tests/PlayMode/E2E/E2E.asmdef"
|
||||||
- "Tests/PlayMode/E2E/E2E.asmdef"
|
|
||||||
example_test:
|
example_test:
|
||||||
description: "Working example E2E test"
|
description: "Working example E2E test"
|
||||||
files:
|
files:
|
||||||
- "Tests/PlayMode/E2E/ExampleE2ETest.cs"
|
- "Tests/PlayMode/E2E/ExampleE2ETest.cs"
|
||||||
documentation:
|
|
||||||
description: "E2E testing README"
|
documentation:
|
||||||
files:
|
description: "E2E testing README"
|
||||||
- "Tests/PlayMode/E2E/README.md"
|
files:
|
||||||
|
- "Tests/PlayMode/E2E/README.md"
|
||||||
unreal:
|
|
||||||
condition: "engine == 'unreal'"
|
|
||||||
infrastructure_files:
|
|
||||||
description: "Generated E2E infrastructure classes"
|
|
||||||
files:
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/GameE2ETestBase.h"
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/GameE2ETestBase.cpp"
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/ScenarioBuilder.h"
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/ScenarioBuilder.cpp"
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/InputSimulator.h"
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/InputSimulator.cpp"
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/AsyncAssert.h"
|
|
||||||
build_configuration:
|
|
||||||
description: "E2E test build configuration"
|
|
||||||
files:
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/{ProjectName}E2ETests.Build.cs"
|
|
||||||
example_test:
|
|
||||||
description: "Working example E2E test"
|
|
||||||
files:
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/ExampleE2ETest.cpp"
|
|
||||||
documentation:
|
|
||||||
description: "E2E testing README"
|
|
||||||
files:
|
|
||||||
- "Source/{ProjectName}/Tests/E2E/README.md"
|
|
||||||
|
|
||||||
godot:
|
|
||||||
condition: "engine == 'godot'"
|
|
||||||
infrastructure_files:
|
|
||||||
description: "Generated E2E infrastructure classes"
|
|
||||||
files:
|
|
||||||
- "tests/e2e/infrastructure/game_e2e_test_fixture.gd"
|
|
||||||
- "tests/e2e/infrastructure/scenario_builder.gd"
|
|
||||||
- "tests/e2e/infrastructure/input_simulator.gd"
|
|
||||||
- "tests/e2e/infrastructure/async_assert.gd"
|
|
||||||
example_test:
|
|
||||||
description: "Working example E2E test"
|
|
||||||
files:
|
|
||||||
- "tests/e2e/scenarios/example_e2e_test.gd"
|
|
||||||
documentation:
|
|
||||||
description: "E2E testing README"
|
|
||||||
files:
|
|
||||||
- "tests/e2e/README.md"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: analyze
|
- id: analyze
|
||||||
name: "Analyze Game Architecture"
|
name: "Analyze Game Architecture"
|
||||||
instruction_file: "instructions.md#step-1-analyze-game-architecture"
|
instruction_file: "instructions.md#step-1-analyze-game-architecture"
|
||||||
|
|
||||||
- id: scaffold
|
- id: scaffold
|
||||||
name: "Generate Infrastructure"
|
name: "Generate Infrastructure"
|
||||||
instruction_file: "instructions.md#step-2-generate-infrastructure"
|
instruction_file: "instructions.md#step-2-generate-infrastructure"
|
||||||
|
|
||||||
- id: example
|
- id: example
|
||||||
name: "Generate Example Test"
|
name: "Generate Example Test"
|
||||||
instruction_file: "instructions.md#step-3-generate-example-test"
|
instruction_file: "instructions.md#step-3-generate-example-test"
|
||||||
|
|
||||||
- id: document
|
- id: document
|
||||||
name: "Generate Documentation"
|
name: "Generate Documentation"
|
||||||
instruction_file: "instructions.md#step-4-generate-documentation"
|
instruction_file: "instructions.md#step-4-generate-documentation"
|
||||||
|
|
||||||
- id: complete
|
- id: complete
|
||||||
name: "Output Summary"
|
name: "Output Summary"
|
||||||
instruction_file: "instructions.md#step-5-output-summary"
|
instruction_file: "instructions.md#step-5-output-summary"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue