├── .gitignore ├── Assets ├── UITest.meta └── UITest │ ├── DependencyInjector.cs │ ├── DependencyInjector.cs.meta │ ├── Examples.meta │ ├── Examples │ ├── FirstScreen.cs │ ├── FirstScreen.cs.meta │ ├── FirstScreen.prefab │ ├── FirstScreen.prefab.meta │ ├── MockNetworkClient.cs │ ├── MockNetworkClient.cs.meta │ ├── NetworkClient.cs │ ├── NetworkClient.cs.meta │ ├── SecondScreen.cs │ ├── SecondScreen.cs.meta │ ├── SecondScreen.prefab │ ├── SecondScreen.prefab.meta │ ├── TestableGameScene.unity │ ├── TestableGameScene.unity.meta │ ├── UITestExample.cs │ └── UITestExample.cs.meta │ ├── README.txt │ ├── README.txt.meta │ ├── UITest.cs │ └── UITest.cs.meta ├── LICENSE ├── ProjectSettings ├── AudioManager.asset ├── ClusterInputManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── GraphicsSettings.asset ├── InputManager.asset ├── NavMeshAreas.asset ├── NetworkManager.asset ├── Physics2DSettings.asset ├── ProjectSettings.asset ├── ProjectVersion.txt ├── QualitySettings.asset ├── TagManager.asset ├── TimeManager.asset ├── UnityAdsSettings.asset └── UnityConnectSettings.asset └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | /test-report 8 | 9 | # Autogenerated VS/MD solution and project files 10 | ExportedObj/ 11 | *.csproj 12 | *.unityproj 13 | *.sln 14 | *.suo 15 | *.tmp 16 | *.user 17 | *.userprefs 18 | *.pidb 19 | *.booproj 20 | *.svd 21 | 22 | 23 | # Unity3D generated meta files 24 | *.pidb.meta 25 | 26 | # Unity3D Generated File On Crash Reports 27 | sysinfo.txt 28 | 29 | # Builds 30 | *.apk 31 | *.unitypackage 32 | 33 | # Rider 34 | .idea 35 | Assets/Plugins/Editor/JetBrains* 36 | 37 | -------------------------------------------------------------------------------- /Assets/UITest.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 490e402bddcc74083b7457bfa0caf324 3 | folderAsset: yes 4 | timeCreated: 1475075251 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/UITest/DependencyInjector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Linq; 5 | 6 | [AttributeUsage(AttributeTargets.Field)] 7 | public sealed class InjectAttribute : Attribute 8 | { 9 | } 10 | 11 | public static class DependencyInjector 12 | { 13 | public static readonly Dictionary components = new Dictionary(); 14 | static readonly object placeholder = new object(); 15 | 16 | static readonly Dictionary cachedFields = new Dictionary(); 17 | 18 | const BindingFlags FIELD_FLAGS = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly; 19 | 20 | public static void InjectObject(object target) 21 | { 22 | FieldInfo[] fields = GetFields(target.GetType()); 23 | for(int i = 0; i < fields.Length; i++) 24 | fields[i].SetValue(target, Resolve(fields[i].FieldType)); 25 | } 26 | 27 | static FieldInfo[] GetFields(Type type) 28 | { 29 | FieldInfo[] fields; 30 | if (!cachedFields.TryGetValue(type, out fields)) { 31 | fields = GetInjectFields(type); 32 | cachedFields.Add(type, fields); 33 | } 34 | return fields; 35 | } 36 | 37 | public static void Inject(this UnityEngine.MonoBehaviour target) 38 | { 39 | InjectObject(target); 40 | } 41 | 42 | static readonly List injectFieldsBuffer = new List(); 43 | 44 | static FieldInfo[] GetInjectFields(Type type) 45 | { 46 | while (type != null) 47 | { 48 | var typeFields = type.GetFields(FIELD_FLAGS); 49 | for (int i = 0; i < typeFields.Length; i++) 50 | { 51 | if (HasInjectAttribute(typeFields[i])) 52 | injectFieldsBuffer.Add(typeFields[i]); 53 | } 54 | type = type.BaseType; 55 | } 56 | 57 | var resultFields = injectFieldsBuffer.ToArray(); 58 | injectFieldsBuffer.Clear(); 59 | return resultFields; 60 | } 61 | 62 | static bool HasInjectAttribute(MemberInfo member) 63 | { 64 | return member.GetCustomAttributes(typeof(InjectAttribute), true).Any(); 65 | } 66 | 67 | public static T Resolve() 68 | { 69 | return (T)Resolve(typeof(T)); 70 | } 71 | 72 | static object Resolve(Type type) 73 | { 74 | object component; 75 | 76 | if (components.TryGetValue(type, out component)) 77 | { 78 | if (placeholder == component) 79 | throw new Exception("Cyclic dependency detected in " + type); 80 | } 81 | else 82 | { 83 | components[type] = placeholder; 84 | component = components[type] = CreateComponent(type); 85 | } 86 | 87 | return component; 88 | } 89 | 90 | public static void ReplaceComponent(T newComponent) 91 | { 92 | components[typeof(T)] = newComponent; 93 | foreach (var c in components.Values) { 94 | foreach (var f in GetFields(c.GetType())) { 95 | if (f.FieldType == typeof(T)) f.SetValue(c, newComponent); 96 | } 97 | } 98 | } 99 | 100 | public static void ClearCache() 101 | { 102 | foreach (var componentPair in components) { 103 | var cleanableComponent = componentPair.Value as IDisposable; 104 | if (cleanableComponent != null) 105 | cleanableComponent.Dispose(); 106 | } 107 | cachedFields.Clear(); 108 | components.Clear(); 109 | GC.Collect(); 110 | } 111 | 112 | static object CreateComponent(Type type) 113 | { 114 | try { 115 | return Activator.CreateInstance(type); 116 | } 117 | catch (TargetInvocationException e) { 118 | throw e.InnerException; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Assets/UITest/DependencyInjector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3fab898b6d636fa45b570f417655e9c2 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UITest/Examples.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f73623eda10f644afab42015a6c22ca7 3 | folderAsset: yes 4 | timeCreated: 1475075251 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/FirstScreen.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using UnityEngine.UI; 4 | 5 | public class FirstScreen : MonoBehaviour 6 | { 7 | [Inject] NetworkClient networkClient; 8 | 9 | [SerializeField] GameObject secondScreenPrefab; 10 | [SerializeField] Text responseText; 11 | 12 | void Start() 13 | { 14 | this.Inject(); 15 | } 16 | 17 | public void OpenSecondScreen() 18 | { 19 | var s = Object.Instantiate(secondScreenPrefab); 20 | s.name = secondScreenPrefab.name; 21 | s.transform.SetParent(transform.parent, false); 22 | } 23 | 24 | public void SendNetworkRequest() 25 | { 26 | responseText.text = networkClient.SendServerRequest("i_need_data"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/FirstScreen.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c99d57dd5573d40bb95d44dc3c6a7029 3 | timeCreated: 1475077980 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/FirstScreen.prefab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taphos/unity-uitest/cca98ec9dd4a784c929fc34571124ace4afe7795/Assets/UITest/Examples/FirstScreen.prefab -------------------------------------------------------------------------------- /Assets/UITest/Examples/FirstScreen.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bf8ef328cd27a41579126ec480ec2748 3 | timeCreated: 1475078429 4 | licenseType: Pro 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/MockNetworkClient.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class MockNetworkClient : NetworkClient 5 | { 6 | public string mockRequest; 7 | public string mockResponse; 8 | 9 | public override string SendServerRequest(string request) 10 | { 11 | mockRequest = request; 12 | return mockResponse; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/MockNetworkClient.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ebb241f2f8ae44bfa927e314709f17fc 3 | timeCreated: 1475079370 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/NetworkClient.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System; 4 | 5 | public class NetworkClient 6 | { 7 | public virtual string SendServerRequest(string request) 8 | { 9 | // Seems like our example server is offline, but this method is still testable 10 | throw new Exception("Server unavailable"); 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/NetworkClient.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 08859966dfde7458d96cb9eb0510f419 3 | timeCreated: 1475079370 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/SecondScreen.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class SecondScreen : MonoBehaviour 5 | { 6 | public void Close() 7 | { 8 | Destroy(gameObject); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/SecondScreen.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 050adff2ac2f44ab58d8141e97d15a60 3 | timeCreated: 1475078317 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/SecondScreen.prefab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taphos/unity-uitest/cca98ec9dd4a784c929fc34571124ace4afe7795/Assets/UITest/Examples/SecondScreen.prefab -------------------------------------------------------------------------------- /Assets/UITest/Examples/SecondScreen.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7469927f71acf4557acbfc4625f730c6 3 | timeCreated: 1475078423 4 | licenseType: Pro 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/TestableGameScene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taphos/unity-uitest/cca98ec9dd4a784c929fc34571124ace4afe7795/Assets/UITest/Examples/TestableGameScene.unity -------------------------------------------------------------------------------- /Assets/UITest/Examples/TestableGameScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cf4b10a3abc1a406b82c6e856ad8899f 3 | timeCreated: 1475077919 4 | licenseType: Pro 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/UITestExample.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using NUnit.Framework; 4 | using UnityEngine.TestTools; 5 | 6 | public class UITestExample : UITest 7 | { 8 | MockNetworkClient mockNetworkClient; 9 | 10 | [SetUp] 11 | public void Init() 12 | { 13 | mockNetworkClient = new MockNetworkClient(); 14 | // Replace the real networkClient with mock object, it will be injected later into FirstScreen component 15 | DependencyInjector.ReplaceComponent(mockNetworkClient); 16 | } 17 | 18 | [UnityTest] 19 | public IEnumerator SecondScreenCanBeOpenedFromTheFirstOne() 20 | { 21 | yield return LoadScene("TestableGameScene"); 22 | 23 | // Wait until object with given component appears in the scene 24 | yield return WaitFor(new ObjectAppeared()); 25 | 26 | // Wait until button with given name appears and simulate press event 27 | yield return Press("Button-OpenSecondScreen"); 28 | 29 | yield return WaitFor(new ObjectAppeared()); 30 | 31 | // Wait until Text component with given name appears and assert its value 32 | yield return AssertLabel("SecondScreen/Text", "Second screen"); 33 | 34 | yield return Press("Button-Close"); 35 | 36 | // Wait until object with given component disappears from the scene 37 | yield return WaitFor(new ObjectDisappeared()); 38 | } 39 | 40 | [UnityTest] 41 | public IEnumerator SuccessfulNetworkResponseIsDisplayedOnTheFirstScreen() 42 | { 43 | yield return LoadScene("TestableGameScene"); 44 | 45 | yield return WaitFor(new ObjectAppeared()); 46 | 47 | // Predefine the mocked server response 48 | mockNetworkClient.mockResponse = "Success!"; 49 | 50 | yield return Press("Button-NetworkRequest"); 51 | 52 | // Check the response displayed on UI 53 | yield return AssertLabel("FirstScreen/Text-Response", "Success!"); 54 | 55 | // Assert the requested server parameter 56 | Assert.AreEqual(mockNetworkClient.mockRequest, "i_need_data"); 57 | } 58 | 59 | [UnityTest] 60 | public IEnumerator FailingBoolCondition() 61 | { 62 | yield return LoadScene("TestableGameScene"); 63 | 64 | yield return WaitFor(new ObjectAppeared("FirstScreen")); 65 | var s = Object.FindObjectOfType(); 66 | 67 | // Wait until FirstScene component is disabled, this line will fail by timeout 68 | // BoolCondition can be used to wait until any condition is satisfied 69 | yield return WaitFor(new BoolCondition(() => !s.enabled)); 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Assets/UITest/Examples/UITestExample.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cf1eb601bce74fbfa42c55e10c0aa10 3 | timeCreated: 1475078584 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/UITest/README.txt: -------------------------------------------------------------------------------- 1 | # Unity UI Test Automation Framework 2 | 3 | This version of framework is supported by ***Unity 2017*** and later. 4 | 5 | Comparing to the previous version it is integrated with Unity PlayMode Test Runner instead of custom runner implementation. 6 | Note that running tests in platform players (for example Standalone, Android, or iOS) from command line is not currently supported by Unity. https://docs.unity3d.com/Manual/testing-editortestsrunner.html 7 | 8 | If you would like to execute tests from command line or write tests for older Unity versions check out previous version of the framework https://github.com/taphos/unity-uitest/tree/1.0 9 | 10 | 11 | # Features 12 | 13 | * Allows to write automated tests that drive the game in a way similar to how a user would 14 | * Integrated with Unity UI solution, can easily be integrated with custom solutions like NGUI or EZGUI 15 | * Integrated with Unity Test Runner 16 | * Tests can be executed in Unity Player or Editor (PlayMode) 17 | * Includes lightweight dependency injection framework for object mocking 18 | 19 | 20 | # Running 21 | 22 | * In Editor Open Window->Test Runner 23 | * Switch to PlayMode tab 24 | * Click Run 25 | 26 | 27 | # Implementing tests 28 | 29 | * To add a new test create a new class anywhere in the project extending UITest 30 | * Use UnityTest, SetUp and TearDown attributes same way as you would in Unit tests 31 | * Checkout example in Assets/UITest/Examples/UITestExample.cs 32 | 33 | 34 | # API 35 | 36 | API is designed to be readable as a natural language so it can be understood by non technical people too. All API calls are designed to wait until its function could be executed with a certain timeout. 37 | 38 | * `Press()` - Simulates a button press. If an object with a given name is not found in the scene, it waits for it to appear. 39 | * `LoadScene()` - Load new scene and wait until scene is fully loaded. 40 | * `AssertLabel(, )` - Asserts text value, waits until value is changed. 41 | * `WaitFor()` - Generic method to wait until given condition is satisfied. 42 | * `WaitFor(new LabelTextAppeared(, ))` - Wait for label with given text to appear 43 | * `WaitFor(new SceneLoaded())` - Wait until scene is fully loaded 44 | * `WaitFor(new ObjectAppeared())` - Wait for object with given name to appear 45 | * `WaitFor(new ObjectAppeared())` - Wait for object with component of given type to appear 46 | * `WaitFor(new ObjectDisappeared())` - Wait for object with given name to disappear 47 | * `WaitFor(new ObjectDisappeared())` - Wait for object with component of given type to disappear 48 | * `WaitFor(new BoolCondition())` - Generic condition is satisfied when a given bool expression becomes true 49 | 50 | 51 | Check out my blog post for in depth description http://blog.filippkeks.com/2016/11/21/why-game-developers-are-afraid-of-test-automation.html 52 | Have fun testing ;) 53 | 54 | Filipp Keks 55 | -------------------------------------------------------------------------------- /Assets/UITest/README.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 702129dc70dc54fd6afb12291e63e397 3 | timeCreated: 1475244041 4 | licenseType: Pro 5 | TextScriptImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/UITest/UITest.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System; 4 | using System.IO; 5 | using UnityEngine.UI; 6 | using UnityEngine.EventSystems; 7 | using UnityEngine.SceneManagement; 8 | 9 | public class UITest 10 | { 11 | const float WaitTimeout = 2; 12 | const float WaitIntervalFrames = 10; 13 | static MonoBehaviour mb; 14 | 15 | public class MB : MonoBehaviour 16 | { 17 | } 18 | 19 | protected void CreateMonoBehaviour() 20 | { 21 | if (mb == null) 22 | { 23 | var go = new GameObject("mb"); 24 | GameObject.DontDestroyOnLoad(go); 25 | go.hideFlags = HideFlags.HideAndDontSave; 26 | mb = go.AddComponent(); 27 | } 28 | } 29 | 30 | protected Coroutine StartCoroutine(IEnumerator enumerator) 31 | { 32 | CreateMonoBehaviour(); 33 | return mb.StartCoroutine(enumerator); 34 | } 35 | 36 | protected Coroutine WaitFor(Condition condition) 37 | { 38 | return StartCoroutine(WaitForInternal(condition, Environment.StackTrace)); 39 | } 40 | 41 | protected Coroutine LoadScene(string name) 42 | { 43 | return StartCoroutine(LoadSceneInternal(name)); 44 | } 45 | 46 | IEnumerator LoadSceneInternal(string name) 47 | { 48 | #if UNITY_EDITOR 49 | if (name.Contains(".unity")) 50 | { 51 | UnityEditor.EditorApplication.LoadLevelInPlayMode(name); 52 | yield return WaitFor(new SceneLoaded(Path.GetFileNameWithoutExtension(name))); 53 | yield break; 54 | } 55 | #endif 56 | SceneManager.LoadScene(name); 57 | yield return WaitFor(new SceneLoaded(name)); 58 | } 59 | 60 | protected Coroutine AssertLabel(string id, string text) 61 | { 62 | return StartCoroutine(AssertLabelInternal(id, text)); 63 | } 64 | 65 | protected Coroutine Press(string buttonName) 66 | { 67 | return StartCoroutine(PressInternal(buttonName)); 68 | } 69 | 70 | protected Coroutine Press(GameObject o) 71 | { 72 | return StartCoroutine(PressInternal(o)); 73 | } 74 | 75 | IEnumerator WaitForInternal(Condition condition, string stackTrace) 76 | { 77 | float time = 0; 78 | while (!condition.Satisfied()) 79 | { 80 | if (time > WaitTimeout) 81 | throw new Exception("Operation timed out: " + condition + "\n" + stackTrace); 82 | for (int i = 0; i < WaitIntervalFrames; i++) { 83 | time += Time.unscaledDeltaTime; 84 | yield return null; 85 | } 86 | } 87 | } 88 | 89 | IEnumerator PressInternal(string buttonName) 90 | { 91 | var buttonAppeared = new ObjectAppeared(buttonName); 92 | yield return WaitFor(buttonAppeared); 93 | yield return Press(buttonAppeared.o); 94 | } 95 | 96 | IEnumerator PressInternal(GameObject o) 97 | { 98 | yield return WaitFor(new ButtonAccessible(o)); 99 | Debug.Log("Button pressed: " + o); 100 | ExecuteEvents.Execute(o, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler); 101 | yield return null; 102 | } 103 | 104 | IEnumerator AssertLabelInternal(string id, string text) 105 | { 106 | yield return WaitFor(new LabelTextAppeared(id, text)); 107 | } 108 | 109 | protected abstract class Condition 110 | { 111 | protected string param; 112 | protected string objectName; 113 | 114 | public Condition() 115 | { 116 | } 117 | 118 | public Condition(string param) 119 | { 120 | this.param = param; 121 | } 122 | 123 | public Condition(string objectName, string param) 124 | { 125 | this.param = param; 126 | this.objectName = objectName; 127 | } 128 | 129 | public abstract bool Satisfied(); 130 | 131 | public override string ToString() 132 | { 133 | return GetType() + " '" + param + "'"; 134 | } 135 | } 136 | 137 | protected class LabelTextAppeared : Condition 138 | { 139 | public LabelTextAppeared(string objectName, string param) : base(objectName, param) {} 140 | 141 | public override bool Satisfied() 142 | { 143 | return GetErrorMessage() == null; 144 | } 145 | 146 | string GetErrorMessage() 147 | { 148 | var go = GameObject.Find(objectName); 149 | if (go == null) return "Label object " + objectName + " does not exist"; 150 | if (!go.activeInHierarchy) return "Label object " + objectName + " is inactive"; 151 | var t = go.GetComponent(); 152 | if (t == null) return "Label object " + objectName + " has no Text attached"; 153 | if (t.text != param) return "Label " + objectName + "\n text expected: " + param + ",\n actual: " + t.text; 154 | return null; 155 | } 156 | 157 | public override string ToString() 158 | { 159 | return GetErrorMessage(); 160 | } 161 | } 162 | 163 | protected class SceneLoaded : Condition 164 | { 165 | public SceneLoaded(string param) : base (param) {} 166 | 167 | public override bool Satisfied() 168 | { 169 | return SceneManager.GetActiveScene().name == param; 170 | } 171 | } 172 | 173 | protected class ObjectAnimationPlaying : Condition 174 | { 175 | public ObjectAnimationPlaying(string objectName, string param) :base (objectName, param) {} 176 | 177 | public override bool Satisfied() 178 | { 179 | GameObject gameObject = GameObject.Find(objectName); 180 | return gameObject.GetComponent().IsPlaying(param); 181 | } 182 | } 183 | 184 | protected class ObjectAppeared : Condition where T : Component 185 | { 186 | public override bool Satisfied() 187 | { 188 | var obj = GameObject.FindObjectOfType(typeof (T)) as T; 189 | return obj != null && obj.gameObject.activeInHierarchy; 190 | } 191 | } 192 | 193 | protected class ObjectDisappeared : Condition where T : Component 194 | { 195 | public override bool Satisfied() 196 | { 197 | var obj = GameObject.FindObjectOfType(typeof(T)) as T; 198 | return obj == null || !obj.gameObject.activeInHierarchy; 199 | } 200 | } 201 | 202 | protected class ObjectAppeared : Condition 203 | { 204 | protected string path; 205 | public GameObject o; 206 | 207 | public ObjectAppeared(string path) 208 | { 209 | this.path = path; 210 | } 211 | 212 | public override bool Satisfied() 213 | { 214 | o = GameObject.Find(path); 215 | return o != null && o.activeInHierarchy; 216 | } 217 | 218 | public override string ToString() 219 | { 220 | return "ObjectAppeared(" + path + ")"; 221 | } 222 | } 223 | 224 | protected class ObjectDisappeared : ObjectAppeared 225 | { 226 | public ObjectDisappeared(string path) : base(path) {} 227 | 228 | public override bool Satisfied() 229 | { 230 | return !base.Satisfied(); 231 | } 232 | 233 | public override string ToString() 234 | { 235 | return "ObjectDisappeared(" + path + ")"; 236 | } 237 | } 238 | 239 | protected class BoolCondition : Condition 240 | { 241 | private Func _getter; 242 | 243 | public BoolCondition(Func getter) 244 | { 245 | _getter = getter; 246 | } 247 | 248 | public override bool Satisfied() 249 | { 250 | if (_getter == null) return false; 251 | return _getter(); 252 | } 253 | 254 | public override string ToString() 255 | { 256 | return "BoolCondition(" + _getter + ")"; 257 | } 258 | } 259 | 260 | protected class ButtonAccessible : Condition 261 | { 262 | GameObject button; 263 | 264 | public ButtonAccessible(GameObject button) 265 | { 266 | this.button = button; 267 | } 268 | 269 | public override bool Satisfied() 270 | { 271 | return GetAccessibilityMessage() == null; 272 | } 273 | 274 | public override string ToString() 275 | { 276 | return GetAccessibilityMessage() ?? "Button " + button.name + " is accessible"; 277 | } 278 | 279 | string GetAccessibilityMessage() 280 | { 281 | if (button == null) 282 | return "Button " + button + " not found"; 283 | if (button.GetComponent