using System; using System.Collections; using Unity.BossRoom.Gameplay.GameState; using Unity.BossRoom.Gameplay.UI; using NUnit.Framework; using Unity.Multiplayer.Samples.Utilities; using Unity.Netcode; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; using VContainer; namespace Unity.BossRoom.Tests.Runtime { public class HostAndDisconnectTest { const string k_BootstrapSceneName = "Startup"; const string k_MainMenuSceneName = "MainMenu"; const string k_CharSelectSceneName = "CharSelect"; const string k_BossRoomSceneName = "BossRoom"; static int[] s_PlayerIndices = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }; NetworkManager m_NetworkManager; IEnumerator WaitUntilMainMenuSceneIsLoaded() { // load Bootstrap scene SceneManager.LoadSceneAsync(k_BootstrapSceneName); // validate the loading of project's Bootstrap scene yield return new TestUtilities.WaitForSceneLoad(k_BootstrapSceneName); // Bootstrap scene is loaded, containing NetworkManager instance; cache it m_NetworkManager = NetworkManager.Singleton; Assert.That(m_NetworkManager != null); // MainMenu is loaded as soon as Startup scene is launched, validate it is loaded yield return new TestUtilities.WaitForSceneLoad(k_MainMenuSceneName); yield return null; } IEnumerator WaitUntilCharacterIsSelectedAndReady(int playerIndex) { yield return new TestUtilities.WaitForSceneLoad(k_CharSelectSceneName); yield return null; // select a Character var seatObjectName = $"PlayerSeat ({playerIndex})"; var playerSeat = GameObject.Find(seatObjectName); Assert.That(playerSeat != null, $"{seatObjectName} not found!"); var uiCharSelectPlayerSeat = playerSeat.GetComponent(); Assert.That(uiCharSelectPlayerSeat != null, $"{nameof(UICharSelectPlayerSeat)} component not found on {playerSeat}!"); uiCharSelectPlayerSeat.OnClicked(); // selecting a class will enable the "Ready" button, next frame it is selectable yield return null; // hit ready ClientCharSelectState.Instance.OnPlayerClickedReady(); } /// /// For now, just tests that the host has entered the BossRoom scene. Can become more complex in the future /// (eg. testing networked abilities) /// /// IEnumerator WaitUntilBossRoomSceneIsLoaded() { yield return TestUtilities.AssertIsNetworkSceneLoaded(k_BossRoomSceneName, m_NetworkManager.SceneManager); } IEnumerator WaitUntilDisconnectedAndMainMenuSceneIsLoaded() { // once loaded into BossRoom scene, disconnect var uiSettingsCanvas = GameObject.FindObjectOfType(); Assert.That(uiSettingsCanvas != null, $"{nameof(UISettingsCanvas)} component not found!"); uiSettingsCanvas.OnClickQuitButton(); yield return new WaitForFixedUpdate(); var uiQuitPanel = GameObject.FindObjectOfType(true); Assert.That(uiQuitPanel != null, $"{nameof(UIQuitPanel)} component not found!"); uiQuitPanel.Quit(); // Netcode TODO: OnNetworkDespawn() errors pop up here // Line below should not be necessary, logged here: https://jira.unity3d.com/browse/MTT-3376 yield return new WaitForSeconds(1f); // wait until shutdown is complete yield return new WaitUntil(() => !m_NetworkManager.ShutdownInProgress); Assert.That(!NetworkManager.Singleton.IsListening, "NetworkManager not fully shut down!"); // MainMenu is loaded as soon as a shutdown is encountered; validate it is loaded yield return new TestUtilities.WaitForSceneLoad(k_MainMenuSceneName); } /// /// Smoke test to validating hosting inside Boss Room. The test will load the project's bootstrap scene, /// Startup, and commence the game IP flow as a host, pick and confirm a parametrized character, and jump into /// the BossRoom scene, where the test will disconnect the host. /// [UnityTest] public IEnumerator IP_HostAndDisconnect_Valid([ValueSource(nameof(s_PlayerIndices))] int playerIndex) { yield return WaitUntilMainMenuSceneIsLoaded(); var clientMainMenuState = GameObject.FindObjectOfType(); Assert.That(clientMainMenuState != null, $"{nameof(clientMainMenuState)} component not found!"); var container = clientMainMenuState.Container; var ipUIMediator = container.Resolve(); Assert.That(ipUIMediator != null, $"{nameof(IPUIMediator)} component not found!"); var ipHostingUI = ipUIMediator.IPHostingUI; Assert.That(ipHostingUI != null, $"{nameof(IPHostingUI)} component not found!"); // select "DIRECT IP" button clientMainMenuState.OnDirectIPClicked(); yield return null; // select the "HOST" button ipHostingUI.OnCreateClick(); // confirming hosting will initialize the hosting process; next frame the results will be ready yield return null; // verify hosting is successful Assert.That(m_NetworkManager.IsListening && m_NetworkManager.IsHost); // CharSelect is loaded as soon as hosting is successful, validate it is loaded yield return WaitUntilCharacterIsSelectedAndReady(playerIndex); // selecting ready as host with no other party members will load BossRoom scene; validate it is loaded yield return WaitUntilBossRoomSceneIsLoaded(); // Netcode TODO: the line below prevents a NullReferenceException on NetworkSceneManager.OnSceneLoaded // Line below should not be necessary, logged here: https://jira.unity3d.com/browse/MTT-3376 yield return new WaitForSeconds(2f); yield return WaitUntilDisconnectedAndMainMenuSceneIsLoaded(); } [UnityTearDown] public IEnumerator DestroySceneGameObjects() { foreach (var sceneGameObject in GameObject.FindObjectsOfType()) { GameObject.DestroyImmediate(sceneGameObject); } yield break; } } }