test: replace fixed waits with observable GUI readiness
Advance integration drivers only after their requested controls, game state or file effects are observable. Preserve the behavioral assertions and shared viewport capture pipeline.
This commit is contained in:
1 parent
a82130edb0
commit
18c58fd117
5 files changed
+123
-20
No files matched your search
@@ -34,6 +34,9 @@ namespace Mods.CollectionEditTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CollectionEditTestDriverNode : Node
|
public partial class CollectionEditTestDriverNode : Node
|
||||||
{
|
{
|
||||||
|
private int readinessToken = int.MinValue;
|
||||||
|
private ulong readinessStarted;
|
||||||
|
private ulong readinessFrame;
|
||||||
public const string Flag = "--itest=collection_edit";
|
public const string Flag = "--itest=collection_edit";
|
||||||
|
|
||||||
private const string GameId = "novel_template";
|
private const string GameId = "novel_template";
|
||||||
@@ -54,7 +57,6 @@ namespace Mods.CollectionEditTest
|
|||||||
private int passed;
|
private int passed;
|
||||||
private int step;
|
private int step;
|
||||||
private double elapsed;
|
private double elapsed;
|
||||||
private double stepAt;
|
|
||||||
private bool done;
|
private bool done;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
@@ -69,6 +71,7 @@ namespace Mods.CollectionEditTest
|
|||||||
ModEventBus.Unsubscribe<SceneEnteredEvent>(OnSceneEntered);
|
ModEventBus.Unsubscribe<SceneEnteredEvent>(OnSceneEntered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Advances when observable scene/UI readiness has held for two real frames; the watchdog remains active.</summary>
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if (done)
|
if (done)
|
||||||
@@ -82,9 +85,6 @@ namespace Mods.CollectionEditTest
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elapsed - stepAt < 0.25)
|
|
||||||
return;
|
|
||||||
stepAt = elapsed;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -110,8 +110,26 @@ namespace Mods.CollectionEditTest
|
|||||||
enteredScenes.Add(e.SceneName);
|
enteredScenes.Add(e.SceneName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deferred UI/scene work must finish before the original assertions run.
|
||||||
|
private bool WaitForReadyFrames(int token, bool ready, double timeoutSeconds = 30)
|
||||||
|
{
|
||||||
|
var now = global::Godot.Time.GetTicksMsec();
|
||||||
|
if (readinessToken != token)
|
||||||
|
{
|
||||||
|
readinessToken = token;
|
||||||
|
readinessStarted = now;
|
||||||
|
readinessFrame = 0;
|
||||||
|
}
|
||||||
|
if (now - readinessStarted > timeoutSeconds * 1000)
|
||||||
|
throw new global::System.TimeoutException($"readiness at step {token}: scene/UI did not become ready");
|
||||||
|
if (!ready) { readinessFrame = 0; return false; }
|
||||||
|
if (readinessFrame == 0) readinessFrame = global::Godot.Engine.GetProcessFrames();
|
||||||
|
return global::Godot.Engine.GetProcessFrames() - readinessFrame >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
private void RunStep()
|
private void RunStep()
|
||||||
{
|
{
|
||||||
|
if (!WaitForReadyFrames(step, Boot.IsReady && Menu != null && !Busy && !GodotSceneService.IsTransitioning, 120)) return;
|
||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ namespace Mods.DisplaySettingsTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class DisplaySettingsTestDriverNode : Node
|
public partial class DisplaySettingsTestDriverNode : Node
|
||||||
{
|
{
|
||||||
|
private int readinessToken = int.MinValue;
|
||||||
|
private ulong readinessStarted;
|
||||||
|
private ulong readinessFrame;
|
||||||
private const string Display = "Window/Column/Body/Pages/Display/";
|
private const string Display = "Window/Column/Body/Pages/Display/";
|
||||||
private const float TimeoutSeconds = 60f;
|
private const float TimeoutSeconds = 60f;
|
||||||
private const string ResetButton = "Window/Column/Footer/Row/ResetButton";
|
private const string ResetButton = "Window/Column/Footer/Row/ResetButton";
|
||||||
@@ -38,7 +41,6 @@ namespace Mods.DisplaySettingsTest
|
|||||||
private int step;
|
private int step;
|
||||||
private int shotTab;
|
private int shotTab;
|
||||||
private double elapsed;
|
private double elapsed;
|
||||||
private double stepAt;
|
|
||||||
private bool done;
|
private bool done;
|
||||||
|
|
||||||
public DisplaySettingsTestDriverNode(string shotsDir)
|
public DisplaySettingsTestDriverNode(string shotsDir)
|
||||||
@@ -47,6 +49,7 @@ namespace Mods.DisplaySettingsTest
|
|||||||
backup = File.Exists(settingsPath) ? File.ReadAllText(settingsPath) : null;
|
backup = File.Exists(settingsPath) ? File.ReadAllText(settingsPath) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Advances when observable scene/UI readiness has held for two real frames; the watchdog remains active.</summary>
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if (done)
|
if (done)
|
||||||
@@ -60,9 +63,6 @@ namespace Mods.DisplaySettingsTest
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elapsed - stepAt < 0.5)
|
|
||||||
return;
|
|
||||||
stepAt = elapsed;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -75,8 +75,39 @@ namespace Mods.DisplaySettingsTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deferred UI/scene work must finish before the original assertions run.
|
||||||
|
private bool WaitForReadyFrames(int token, bool ready, double timeoutSeconds = 30)
|
||||||
|
{
|
||||||
|
var now = global::Godot.Time.GetTicksMsec();
|
||||||
|
if (readinessToken != token)
|
||||||
|
{
|
||||||
|
readinessToken = token;
|
||||||
|
readinessStarted = now;
|
||||||
|
readinessFrame = 0;
|
||||||
|
}
|
||||||
|
if (now - readinessStarted > timeoutSeconds * 1000)
|
||||||
|
throw new global::System.TimeoutException($"readiness at step {token}: scene/UI did not become ready");
|
||||||
|
if (!ready) { readinessFrame = 0; return false; }
|
||||||
|
if (readinessFrame == 0) readinessFrame = global::Godot.Engine.GetProcessFrames();
|
||||||
|
return global::Godot.Engine.GetProcessFrames() - readinessFrame >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DisplayReady()
|
||||||
|
{
|
||||||
|
if (step == 0) return true;
|
||||||
|
if (menu == null || !menu.Visible) return false;
|
||||||
|
return step switch
|
||||||
|
{
|
||||||
|
2 => DisplayServer.WindowGetMode() == DisplayServer.WindowMode.Windowed && DisplayServer.WindowGetSize() == WindowSize,
|
||||||
|
3 or 6 => DisplayServer.WindowGetMode() == DisplayServer.WindowMode.Fullscreen,
|
||||||
|
5 => DisplayServer.WindowGetMode() == DisplayServer.WindowMode.Windowed,
|
||||||
|
_ => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void RunStep()
|
private void RunStep()
|
||||||
{
|
{
|
||||||
|
if (!WaitForReadyFrames(step * 10 + shotTab, Boot.IsReady && GetTree().CurrentScene != null && DisplayReady(), 30)) return;
|
||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ namespace Mods.HotReloadTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class HotReloadTestDriverNode : Node
|
public partial class HotReloadTestDriverNode : Node
|
||||||
{
|
{
|
||||||
|
private int readinessToken = int.MinValue;
|
||||||
|
private ulong readinessStarted;
|
||||||
|
private ulong readinessFrame;
|
||||||
public const string Flag = "--itest=hot_reload";
|
public const string Flag = "--itest=hot_reload";
|
||||||
|
|
||||||
private const string LibId = "hot_reload_probe_lib";
|
private const string LibId = "hot_reload_probe_lib";
|
||||||
@@ -48,7 +51,6 @@ namespace Mods.HotReloadTest
|
|||||||
private int passed;
|
private int passed;
|
||||||
private int step;
|
private int step;
|
||||||
private double elapsed;
|
private double elapsed;
|
||||||
private double stepAt;
|
|
||||||
private bool done;
|
private bool done;
|
||||||
private GameLaunchArgs startSelection;
|
private GameLaunchArgs startSelection;
|
||||||
private string modsRoot;
|
private string modsRoot;
|
||||||
@@ -67,6 +69,7 @@ namespace Mods.HotReloadTest
|
|||||||
|
|
||||||
private void OnReloaded(ModSetReloadedEvent e) => reloads.Add(e);
|
private void OnReloaded(ModSetReloadedEvent e) => reloads.Add(e);
|
||||||
|
|
||||||
|
/// <summary>Advances when observable scene/UI readiness has held for two real frames; the watchdog remains active.</summary>
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if (done)
|
if (done)
|
||||||
@@ -80,9 +83,6 @@ namespace Mods.HotReloadTest
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elapsed - stepAt < 0.25)
|
|
||||||
return;
|
|
||||||
stepAt = elapsed;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -97,8 +97,26 @@ namespace Mods.HotReloadTest
|
|||||||
|
|
||||||
private static bool Busy => GameSession.IsSwitching || ModManager.Instance.IsReloading || ModHotReloadService.IsReloadRunning;
|
private static bool Busy => GameSession.IsSwitching || ModManager.Instance.IsReloading || ModHotReloadService.IsReloadRunning;
|
||||||
|
|
||||||
|
// Deferred UI/scene work must finish before the original assertions run.
|
||||||
|
private bool WaitForReadyFrames(int token, bool ready, double timeoutSeconds = 30)
|
||||||
|
{
|
||||||
|
var now = global::Godot.Time.GetTicksMsec();
|
||||||
|
if (readinessToken != token)
|
||||||
|
{
|
||||||
|
readinessToken = token;
|
||||||
|
readinessStarted = now;
|
||||||
|
readinessFrame = 0;
|
||||||
|
}
|
||||||
|
if (now - readinessStarted > timeoutSeconds * 1000)
|
||||||
|
throw new global::System.TimeoutException($"readiness at step {token}: scene/UI did not become ready");
|
||||||
|
if (!ready) { readinessFrame = 0; return false; }
|
||||||
|
if (readinessFrame == 0) readinessFrame = global::Godot.Engine.GetProcessFrames();
|
||||||
|
return global::Godot.Engine.GetProcessFrames() - readinessFrame >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
private void RunStep()
|
private void RunStep()
|
||||||
{
|
{
|
||||||
|
if (!WaitForReadyFrames(step, Boot.IsReady && GetTree().CurrentScene != null && !Busy, 120)) return;
|
||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ namespace Mods.MenuFlowTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MenuFlowTestDriverNode : Node
|
public partial class MenuFlowTestDriverNode : Node
|
||||||
{
|
{
|
||||||
|
private int readinessToken = int.MinValue;
|
||||||
|
private ulong readinessStarted;
|
||||||
|
private ulong readinessFrame;
|
||||||
private const string GameId = "novel_template";
|
private const string GameId = "novel_template";
|
||||||
private const string Slot = "slot_942";
|
private const string Slot = "slot_942";
|
||||||
private const string DeletableId = "menu_flow_deletable";
|
private const string DeletableId = "menu_flow_deletable";
|
||||||
@@ -35,13 +38,13 @@ namespace Mods.MenuFlowTest
|
|||||||
private int passed;
|
private int passed;
|
||||||
private int step;
|
private int step;
|
||||||
private double elapsed;
|
private double elapsed;
|
||||||
private double stepAt;
|
|
||||||
private bool done;
|
private bool done;
|
||||||
private Node hub;
|
private Node hub;
|
||||||
private string initialLocale;
|
private string initialLocale;
|
||||||
private int languageIndex;
|
private int languageIndex;
|
||||||
private bool languageReady;
|
private bool languageReady;
|
||||||
|
|
||||||
|
/// <summary>Advances when observable scene/UI readiness has held for two real frames; the watchdog remains active.</summary>
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if (done)
|
if (done)
|
||||||
@@ -55,9 +58,6 @@ namespace Mods.MenuFlowTest
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elapsed - stepAt < 0.5)
|
|
||||||
return;
|
|
||||||
stepAt = elapsed;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -72,8 +72,26 @@ namespace Mods.MenuFlowTest
|
|||||||
|
|
||||||
private Control Menu => GetTree().CurrentScene as Control;
|
private Control Menu => GetTree().CurrentScene as Control;
|
||||||
|
|
||||||
|
// Deferred UI/scene work must finish before the original assertions run.
|
||||||
|
private bool WaitForReadyFrames(int token, bool ready, double timeoutSeconds = 30)
|
||||||
|
{
|
||||||
|
var now = global::Godot.Time.GetTicksMsec();
|
||||||
|
if (readinessToken != token)
|
||||||
|
{
|
||||||
|
readinessToken = token;
|
||||||
|
readinessStarted = now;
|
||||||
|
readinessFrame = 0;
|
||||||
|
}
|
||||||
|
if (now - readinessStarted > timeoutSeconds * 1000)
|
||||||
|
throw new global::System.TimeoutException($"readiness at step {token}: scene/UI did not become ready");
|
||||||
|
if (!ready) { readinessFrame = 0; return false; }
|
||||||
|
if (readinessFrame == 0) readinessFrame = global::Godot.Engine.GetProcessFrames();
|
||||||
|
return global::Godot.Engine.GetProcessFrames() - readinessFrame >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
private void RunStep()
|
private void RunStep()
|
||||||
{
|
{
|
||||||
|
if (!WaitForReadyFrames(step * 100 + languageIndex * 2 + (languageReady ? 1 : 0), Boot.IsReady && Menu != null && !Cthangover.Core.Scenes.GodotSceneService.IsTransitioning, 30)) return;
|
||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ namespace Mods.NewGameTest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class NewGameTestDriverNode : Node
|
public partial class NewGameTestDriverNode : Node
|
||||||
{
|
{
|
||||||
|
private int readinessToken = int.MinValue;
|
||||||
|
private ulong readinessStarted;
|
||||||
|
private ulong readinessFrame;
|
||||||
public const string Flag = "--itest=new_game";
|
public const string Flag = "--itest=new_game";
|
||||||
|
|
||||||
private const string MainGame = "cthangover";
|
private const string MainGame = "cthangover";
|
||||||
@@ -49,7 +52,6 @@ namespace Mods.NewGameTest
|
|||||||
private int passed;
|
private int passed;
|
||||||
private int step;
|
private int step;
|
||||||
private double elapsed;
|
private double elapsed;
|
||||||
private double stepAt;
|
|
||||||
private bool done;
|
private bool done;
|
||||||
private bool sawMarao;
|
private bool sawMarao;
|
||||||
private int clicks;
|
private int clicks;
|
||||||
@@ -67,6 +69,7 @@ namespace Mods.NewGameTest
|
|||||||
ModEventBus.Unsubscribe<SceneEnteredEvent>(OnSceneEntered);
|
ModEventBus.Unsubscribe<SceneEnteredEvent>(OnSceneEntered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Advances when observable scene/UI readiness has held for two real frames; the watchdog remains active.</summary>
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if (done)
|
if (done)
|
||||||
@@ -80,9 +83,6 @@ namespace Mods.NewGameTest
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elapsed - stepAt < 0.25)
|
|
||||||
return;
|
|
||||||
stepAt = elapsed;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -101,8 +101,26 @@ namespace Mods.NewGameTest
|
|||||||
|
|
||||||
private void OnSceneEntered(SceneEnteredEvent e) => enteredScenes.Add(e.SceneName);
|
private void OnSceneEntered(SceneEnteredEvent e) => enteredScenes.Add(e.SceneName);
|
||||||
|
|
||||||
|
// Deferred UI/scene work must finish before the original assertions run.
|
||||||
|
private bool WaitForReadyFrames(int token, bool ready, double timeoutSeconds = 30)
|
||||||
|
{
|
||||||
|
var now = global::Godot.Time.GetTicksMsec();
|
||||||
|
if (readinessToken != token)
|
||||||
|
{
|
||||||
|
readinessToken = token;
|
||||||
|
readinessStarted = now;
|
||||||
|
readinessFrame = 0;
|
||||||
|
}
|
||||||
|
if (now - readinessStarted > timeoutSeconds * 1000)
|
||||||
|
throw new global::System.TimeoutException($"readiness at step {token}: scene/UI did not become ready");
|
||||||
|
if (!ready) { readinessFrame = 0; return false; }
|
||||||
|
if (readinessFrame == 0) readinessFrame = global::Godot.Engine.GetProcessFrames();
|
||||||
|
return global::Godot.Engine.GetProcessFrames() - readinessFrame >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
private void RunStep()
|
private void RunStep()
|
||||||
{
|
{
|
||||||
|
if (!WaitForReadyFrames(step, Boot.IsReady && Menu != null && !Busy && (step != 0 || Menu is not Cthangover.BaseMenu.MainMenu || !GameSession.IsWarmingUp), TimeoutSeconds)) return;
|
||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
Reference in new issue
Block a user