Version 2024.01


Legal Notices

Warranty

The only warranties for products and services of GameDriver and its affiliates and licensors (“GameDriver”) are set forth in the express warranty statements accompanying such products and services. Nothing herein should be construed as constituting an additional warranty. GameDriver shall not be liable for technical or editorial errors or omissions contained herein. The information contained herein is subject to change without notice.


Restricted Rights

Contains Confidential Information. Except as specifically indicated otherwise, a valid license is required for possession, use, or copying.


© Copyright GameDriver, Inc.

GameDriver is a registered trademark.

Unreal Engine™ is a trademark of Epic Games.

Microsoft® and Visual Studio® are U.S. registered trademarks of Microsoft Corporation.


Introduction

Thank you for choosing GameDriver! We believe in better video games through the power of test automation and want to help you get the most out of your testing efforts. This guide will help you through the installation and initial configuration process. Any errors or omissions can be reported to [email protected].


Prerequisites


Unreal Agent Installation

The GDIO Unreal Agent is installed by placing a small plugin in your game. That plugin communicates with our API, which you use to drive your game remotely.


Be sure to register for a trial license follow the quickstart link for the appropriate Unreal release from GameDriver.io/license. For example:

The link to the quickstart document will contain the plugin and instructions on how to install it. The DLLs to be referenced in your test projects are also bundled in as a zip file. Once you’ve completed the quick start, return here for instructions on installing the license file. 

Adding the License File

To run a test, a license file needs to be present. If you didn't download the license in Step 1, do it now. The license file will need to be copied to the Content/GameDriver folder in your game project for the agent to run.


Your final Project will look like the following:

SomeProjectPath/

└─ Plugins/

   └─ GameDriver  <----- The GameDriver folder goes under Plugins. If there is no Plugins folder, create one.

└─ Content/

   └─ GameDriver/   

        └─ gdio.license    <----- Place your license under Content/GameDriver

        └─ gdio.unreal_agent.config    <----- Place the agent config under the same folder if needed.

└─ MyProject.uproject    

└─ MyProject.sln    



Working with GameDriver

The GameDriver API can be used with a test framework such as NUnit, which provides essential capabilities such as test execution, base reporting, and assertions (checks). It is also cross-platform compatible, which is ideal for our purposes. To demonstrate, we will use Visual Studio 2022 Community Edition for Windows, and Visual Studio for Mac Community Edition, along with NUnit. Other development environments and test frameworks will vary.


                1. In Windows, open Visual Studio and create a new Test Project by selecting Create a new project -> Select NUnit Test Project. Make sure this is the one using C#, as shown with the tags below.



Note: If you do not see this option when starting a new project, you may need to modify your Visual Studio installation to include the Desktop Development workload shown below.



In macOS, use the Other > .NET > Library template from the Visual Studio project creation screen:


                2. Give your test project a new name, and save it to an easy-to-find location.


                3. Select .Net 6.0 as the target framework of your test project.


                4. After creating the project, navigate to the NuGet Package Manager, find and install the NUnitNUnit3TestAdapter, and NUnit Console packages. These are needed for the use of, visualization, and execution of NUnit tests respectively.


                5. In the project references, you will also need to add the following DLLs - gdio.common.objects, gdio.plugin.serializer, and gdio.unreal_api. In Visual Studio, simply right-click the "References" node in the Solution view, shown below, then navigate to the libraries. The libraries will be bundled with the GameDriver plugin as a zip file. Extract them to a folder of your choosing before referencing them.


                6. Finally, add the following directives to your code.

using System;
using System.Diagnostics;
using NUnit.Framework;
using gdio.unreal_api;
using gdio.common.objects;

You are now ready to start automating your testing! 



Building Tests

Attaching to your Game

Once you have configured your game and test environment, it is necessary to connect to the GameDriver agent via API in order to run a test. There are a few ways to connect to your game and initiate testing; either attaching to the Unreal editor in Play Mode or executing the game build executable.


  1. Attaching to the editor in Play Mode. With the Unreal editor open and Play Mode initiated, simply add the following method to your test script:

api.Connect("localhost");
  1. Executing the Game Build executable. Simply add the following method to your test script, with the full path to the executable for your game:

ApiClient.Launch(@"c:\path\to\MyGame.exe");

            On macOS, the path would look something like this:

ApiClient.Launch(@"/Users/user/Desktop/MyGame.app/Contents/MacOS/MyGame");

Note: The @ Symbol in this method is used to escape the entire string. Without this, it is necessary to escape all special characters in the path.




Building Tests using the NUnit Framework

The NUnit test framework provides basic tools for assertions (or checks) and execution of tests inside Visual Studio and other common IDEs, as well as from build systems such as Jenkins. NUnit follows a structure that the IDE interprets for configuration at run-time, which also allows the test to exclude some of the standard components of C# coding, such as the Main method. Generally, the template supplied by the IDE can be followed for test creation. More information on the structure and usage of NUnit tests can be found here.


The tests should be structured as follows. The comment sections are provided as information only and can be removed.

using gdio.common.objects;
using gdio.unreal_api;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace StackOBotTests
{
    public partial class Tests
    {
        [DllImport("user32.dll")]
        public static extern int SetForegroundWindow(int hwnd);
        public static bool standalone = false;

        //These parameters can be used to override settings used to test when running from the NUnit command line
        public string testMode = TestContext.Parameters.Get("Mode", "IDE");
        public string editorVersion = TestContext.Parameters.Get("EditorVersion", "5.1"); //4.27 5.0 5.1
        public string testProjName = TestContext.Parameters.Get("ProjectName", "StackOBot"); //StackOBot MyProject
        public string pathToExe = TestContext.Parameters.Get("PathToExe", null);
        //public string pathToExe = @"D:\Work\UnrealProjects\5.1\StackOBot\Build-Win\WindowsNoEditor\StackOBot.exe";


        ApiClient api;


        [OneTimeSetUp]
        public void Setup()
        {
            if (standalone)
            {
                Process[] appsrunning = Process.GetProcessesByName(testProjName);
                if (appsrunning.Length == 0)
                {
                    Process unreal = System.Diagnostics.Process.Start(pathToExe);
                }
                else
                {
                    SetForegroundWindow((int)appsrunning[0].MainWindowHandle);
                }
            }
            else
            {
                Process[] appsrunning;
                if (editorVersion == "4.27")
                    appsrunning = Process.GetProcessesByName("UE4Editor");
                else
                    appsrunning = Process.GetProcessesByName("UnrealEditor");

                if (appsrunning.Length == 0)
                {
                    Console.WriteLine($"If you want to test in the editor, launch the editor and start the game first");
                    Assert.Fail();//process has to run, and the PIE editor running. 
                                  //  Process unreal = System.Diagnostics.Process.Start("E:\\UE_4.27\\Engine\\Binaries\\Win64\\UE4Editor.exe");
                }
                else
                {
                    SetForegroundWindow((int)appsrunning[0].MainWindowHandle);
                }
            }

            api = new ApiClient();
            api.Connect("localhost");
        }


        [Test, Order(0)]
        public void fanPuzzle()
        {

            api.Wait(5000);


            //Wait for the level to load
            api.WaitForObject("/*[@name = 'BP_PressurePlate_C_UAID_B42E9936F5429ADA00_2086838180']");
            api.Wait(1000);

            //Move Actor to the Button
            api.CallMethod("/*[@name = 'BP_Bot_C_0']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_PressurePlate_C_UAID_B42E9936F5429ADA00_2086838180']") });
            api.Wait(1000);

            //Deactivate Player
            api.KeyPress(new KeyCode[] { KeyCode.F }, 1);
            api.Wait(1000);

            //Move to Fan Position
            api.CallMethod("/*[@name = 'BP_Bot_C_1']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_Fan_C_UAID_B42E9936F5429ADA00_2086843187'] ") });
            api.Wait(1000);

            //Check if Fan moved Player upwards
            Assert.IsTrue((api.GetObjectPosition("/*[@name = 'BP_Bot_C_1']").z > api.GetObjectPosition("/*[@name = 'BP_Bot_C_0']").z), "Fan did not activate");
            api.Wait(1000);

            //Deactivate Player
            api.KeyPress(new KeyCode[] { KeyCode.F }, 1);
            api.Wait(1000);

        }

        [Test, Order(1)]
        public void doorPuzzle()
        {
            Assert.IsTrue(api.WaitForObject("/*[@name = 'BP_Bot_C_2']"), "Incorrect Bot is active");
            api.Wait(1000);

            api.CallMethod("/*[@name = 'BP_Bot_C_2']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_PressurePlate_C_UAID_B42E9936F5429ADA00_2086841184'] ") });
            api.Wait(1000);

            api.KeyPress(new KeyCode[] { KeyCode.F });
            api.Wait(1000);

            api.CallMethod("/*[@name = 'BP_Bot_C_3']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_PressurePlate_C_UAID_B42E9936F5429ADA00_2086841185'] ") });
            api.Wait(1000);

            api.KeyPress(new KeyCode[] { KeyCode.F });
            api.Wait(1000);

            float doorPositionX = api.GetObjectPosition("/*[@name = 'BP_Door_C_UAID_B42E9936F5429ADA00_2086828164'] ").x;
            api.CallMethod("/*[@name = 'BP_Bot_C_4']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_Door_C_UAID_B42E9936F5429ADA00_2086828164'] ") });
            api.Wait(1000);

            api.KeyPress(new KeyCode[] { KeyCode.A }, (ulong)api.GetLastFPS() * 2);
            api.KeyPress(new KeyCode[] { KeyCode.S }, (ulong)api.GetLastFPS() * 2);
            api.Wait(4000);

            Assert.IsTrue(api.GetObjectPosition("/*[@name = 'BP_Bot_C_4']").x < doorPositionX);
            api.KeyPress(new KeyCode[] { KeyCode.F });
            api.Wait(1000);
        }

        [Test, Order(2)]
        public void walkwayPuzzle()
        {
            float orbPosX = 58;
            api.Wait(1000);

            api.CallMethod("/*[@name = 'BP_Bot_C_5']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_PressurePlate_C_UAID_B42E9936F542FAD500_1318833800'] ") });
            api.Wait(1000);

            api.KeyPress(new KeyCode[] { KeyCode.F });
            api.Wait(1000);

            api.CallMethod("/*[@name = 'BP_Bot_C_6']", "K2_SetActorLocation", new object[] { api.GetObjectPosition("/*[@name = 'BP_PressurePlate_C_UAID_B42E9936F542FAD500_1333270801'] ") });
            api.Wait(1000);

            api.KeyPress(new KeyCode[] { KeyCode.F });
            api.Wait(1000);

            api.CallMethod("/*[@name = 'BP_Bot_C_7']", "K2_SetActorLocation", new object[] { new Vector3(58, 2811, 1506) });
            api.Wait(1000);

            api.KeyPress(new KeyCode[] { KeyCode.A }, (ulong)api.GetLastFPS() * 4);
            api.KeyPress(new KeyCode[] { KeyCode.S }, (ulong)api.GetLastFPS() * 4);
            api.Wait(6000);

            api.KeyPress(new KeyCode[] { KeyCode.SpaceBar }, (ulong)api.GetLastFPS() * 4);
            api.KeyPress(new KeyCode[] { KeyCode.A }, (ulong)api.GetLastFPS() * 2);
            api.KeyPress(new KeyCode[] { KeyCode.S }, (ulong)api.GetLastFPS() * 2);


            Assert.IsTrue(api.GetObjectPosition("/*[@name = 'BP_Bot_C_7']").x < orbPosX, "Bot unable to walk up ramp");

        }


        [OneTimeTearDown]

        public void Disconnect()
        {
            api.Disconnect();
        }
    }
}


Common issues

  1. To simulate mouse clicks in the editor you will need to unset the "High DPI Support" flag in the editor preferences. Screenshot of the editor preference window, to illustrate how to disable "High DPI Support"

  2. You may want to "build clean" for your project. Doing so will delete the precompiled binaries for the plugin. To rectify this copy the "Binary" and " Intermediate" folders from within the original plugin to your project's Plugin folder after making a build(or recopy the entire Plugin folder - if you prefer).

  3. To deploy on mobile devices you'll need to add the Content/GameDriver folder to the list of "Additional non-Asset directories to Package" by navigating to Edit -> Project Settings -> Packaging.


Limitations

  • Mouse and touch events do not work with headless execution at this time.