Editor/Drivers/ScenarioRunner.cs

Editor utility that runs automated test scenarios in play mode. It parses a JSON array of steps and executes UI clicks, simulated player input, and waits; it can record video via VideoRecordingDriver and returns a ToolOutcome with results.

NetworkingFile Access
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Sandbox;

namespace Editor.Mcp
{
	internal static class ScenarioRunner
	{
		public static async Task<ToolOutcome<object>> ExecuteScenarioAsync( string scenarioName, string stepsJson, bool recordVideo = false )
		{
			if ( !Game.IsPlaying )
			{
				return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "scenario.playmode_inactive", "Play mode must be active to run test scenarios." );
			}

			if ( recordVideo )
			{
				VideoRecordingDriver.Control( "start", $"{scenarioName}.mp4" );
			}

			var failures = new List<object>();
			int executed = 0;

			try
			{
				using var doc = JsonDocument.Parse( stepsJson );
				foreach ( var element in doc.RootElement.EnumerateArray() )
				{
					var type = element.GetProperty( "type" ).GetString() ?? "";
					executed++;

					switch ( type.ToLowerInvariant() )
					{
						case "ui_click":
							var selector = element.GetProperty( "selector" ).GetString() ?? "";
							var uiRes = await SyntheticUiDriver.InteractAsync( selector, "click" );
							if ( uiRes.Status != ToolStatus.Ready )
							{
								var errMsg = uiRes.Diagnostics.FirstOrDefault()?.Message ?? "UI interaction failed";
								failures.Add( new { Step = executed, Type = type, Error = errMsg } );
							}
							break;

						case "player_input":
							var action = element.GetProperty( "action" ).GetString() ?? "forward";
							var duration = element.TryGetProperty( "durationMs", out var d ) ? d.GetInt32() : 100;
							await SyntheticInputDriver.SimulateInputAsync( action, "pulse", duration );
							break;

						case "wait":
							var waitMs = element.TryGetProperty( "durationMs", out var w ) ? w.GetInt32() : 100;
							await Task.Delay( waitMs );
							break;
					}
				}
			}
			catch ( Exception ex )
			{
				failures.Add( new { Step = executed, Error = ex.Message } );
			}
			finally
			{
				if ( recordVideo )
				{
					VideoRecordingDriver.Control( "stop" );
				}
			}

			return ToolOutcome<object>.Ready( new
			{
				Scenario = scenarioName,
				Passed = failures.Count == 0,
				StepsExecuted = executed,
				Failures = failures
			} );
		}
	}
}