Version 2022.2



Legal Notices

Warranty

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 Notice

© Copyright 2021 GameDriver, Inc.

Trademark Notices

Unity™ is a trademark of Unity Technologies AsP.

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


The only warranties for products and services of GameDriver and its affiliates and licensors (“GameDriver”)




Introduction

GameDriver uses a proprietary but familiar method to identify objects within a project that we call “HierarchyPath” (or HPath). This approach is similar to XPath, which is an industry-standard XML query language and is designed to make XML queries simple and flexible.


In this guide, we will give several examples of common uses of HierarchyPath, and some more complex scenarios to give you an idea of how to work with your project more effectively. The goal of HierarchyPath is to provide a simple yet flexible interface that can enable resilient object tree traversal.


Take the following example. We have an object in a scene that will display some text when certain criteria are met. The object is under MoveObjectScene > Canvas > TestStatus.




Resolving Objects by Name

In this example, we’re specifically looking for the value of the text property of a UnityEngine.UI.Text component, which is attached to our TestStatus object as shown here.


Initially, we expect this field to be blank, but in our test, the field will change to “Test Complete” if we meet certain criteria. We will not be covering the test case leading up to this scenario, only how to identify the objects involved in the test.




First, we will want to query this object for the value of this text field.


There are a few ways to resolve the object, depending on whether more than one copy of it exists. For now, let’s assume this is the only copy of the object.


Using the GetObjectFieldValue function which requires only an HPath argument, we can use the following line of code to query the text property attached to the Text component. GetObjectFieldValue returns the type <T> object defined in the last portion of the HierarchyPath. If no object is found at any point in the lookup, a NullReferenceException is returned.


Command          Type                            Object                                               Component            Property

       v                     v                                    v                                                            v                            v

api.GetObjectFieldValue<string>("//*[@name='TestStatus']/fn:component('UnityEngine.UI.Text')/@text");


Note that this approach can be used to query any component property as long as the type serialization is supported. Currently supported types include string, int, float, bool, and color. For the example above, we see:

  • The type of parameter being returned is a string.

  • First, the "//*[@name='TestStatus']” portion of the query refers to the relative path to the object, as indicated by the // notation. The asterisk “*” notation refers to a wildcard match for the object’s Tag. The path will return the first object with the name field of “TestStatus

  • Once found, we are looking for the child component of the returned object above, which is specifically a UnityEngine.UI.Text component. We perform this search with an internal function call fn:component containing the argument ('UnityEngine.UI.Text') and then the property of that Component we want, which is the @text property.


Another approach might be to use the absolute path to the TestStatus object, including the name or tags of each object in the path, or both.


          Parent to object                Object                                            Component            Property

                     v                                 v                                                            v                        v

(“/*[@name='Canvas']/*[@name='TestStatus']/fn:component('UnityEngine.UI.Text')/@text")


This might be useful if there is more than one TestStatus object, and the one we want to work with isn’t the first. However, there is an alternative approach for working with multiple instances of an object which is particularly useful for working with cloned objects, which are often created at runtime. For example, take the following scene hierarchy:




The above examples will work for the first instance of TestStatus, but not the second. If that’s the instance we’re looking for, we need to add an instance number to the object query in predicate form [ ], such as:


            Parent(s)                    Object         Instance                            Component               Property

                    v                            v                     v                                            v                        v

(“/*[@name='Canvas']/*[@name='TestStatus'][0]/fn:component('UnityEngine.UI.Text')/@text")


The above will find the first instance of an object with the name TestStatus, with the parent object named Canvas. This may require a different instance number than the absolute path depending on whether there are other objects with the same name in other parts of the object tree.


Additional functions are provided to find the first or last instance of an object, which can be used as follows:


Parent(s)                                Object         Instance

    v                                           v                     v

(“/*[@name='Canvas']/*[@name='TestStatus'][first()]/fn:component('UnityEngine.UI.Text')/@text");



Resolving Objects by Tag

Using object tags to resolve objects is similar to using their name, but can be useful when there are groups of objects with the same name but different tags, or groups of objects with different names and the same tag. This combination of tag and name allows us to fine-tune out object identification so that tests are more resilient to change. Using the same object in our first example, we can use the following for an absolute path:

api.GetObjectPosition(“/Untagged/Untagged[3]”);


This represents the 4th untagged object (arrays start at 0) that is the child of a single object with no duplicates. Not very intuitive, and there is a likelihood these will change at runtime and break our tests. However but if we add the names to that path, it starts to look more clear:


                                                                        Tag                        Name              Tag                          Name

                                                                          v                              v                    v                                v

api.GetObjectPosition(“/Untagged[@name='Canvas']/Untagged[@name='TestStatus']”);


That’s better. Now if the developers add more descriptive tags to these objects, we start to see something more useful. Take the example above where there were two child objects to Canvas named TestStatus. Only this time, the second one is tagged with Popup.



Using the absolute HPath to the second object, we get the following:

api.GetObjectPosition(“/Untagged[@name='Canvas']/Popup[@name='TestStatus']”);


Note the /Popup in the HPath, which will be able to locate that object even if another “TestStatus” object is inserted between the two unless it also uses the Popup tag. Then we would need to add the instance predicate (i.e. [1]) before the end quote.


If your project is undergoing a lot of change and the relative position of the “TestStatus” object changes, it might be better to use a combination of the Relative Path covered in the first section, and the use of tags covered here. Using both, we end up with the following search:

api.GetObjectPosition(“//Popup[@name='TestStatus']”);


The above will resolve to the first instance of an object with the name “TestStatus” and the tag of “Popup”, regardless of where it exists in the scene hierarchy.



Using Boolean Operators

GameDriver HierarchyPath also supports any combination of object names or tags using the boolean operators "and" or "or", contained within a predicate clause such as:

api.GetObjectPosition(“//*[@name='Button' and fn:component('UnityEngine.UI.Text')/@text = 'Default String Value']”);


It is also supported to use the parent or grandparent object properties as well, such as:

api.GetObjectPosition(“//TagButton[../@name = 'Canvas']”);


The above will look for an object with the tag “TagButton”, and a parent objects with the name “Canvas”.



Using "contains" to locate objects by property value

HierarchyPath can locate objects using any property value associated with that object, using the syntax contains(haystack, needle), such as the following:

api.WaitForObject("contains(.,fn:component('TMPro.TextMeshProUGUI')/@text = 'America')");


The above command will search for any object in the path "." (starting from the root), having a component of TextMeshPro input field with a text field with the property value of "America". The same format can be used in conjunction with the boolean operators above. The `contains` function can only be used with property values at this time.



Working with the HierarchyPath Plugin for Unity

The HierarchyPath plugin for Unity allows you to build tests simply by right-click selecting an object in the game and outputting the object’s HPath to the console using either the Relative or Absolute value and for absolute values using either the object Tag, Name, or both.



The output will look something like this:

//*[@name='GameObject']
UnityEngine.Debug:Log(Object)
UnityHPathPlugin.HierarchyPathPlugin:RelativePath()
UnityEditor.GenericMenu:CatchMenu(Object, String[], Int32) (at C:/buildslave/unity/build/Editor/Mono/GUI/GenericMenu.cs:119)


The above reflects the Relative path to an object with the name “GameObject”. We can copy and paste this value into a test query, ignoring the rest of the output above. For example:

api.GetObjectPosition(“//*[@name='GameObject']”);


For the same object, the following appears when we select the “Tag and Name” option (ignoring the unnecessary output):

/Untagged[@name='GameObject']


We can use either in the object query, i.e. api.GetObjectPosition(“/Untagged[@name='GameObject']”); will still be valid.


The HierarchyPath plugin can also be used in Play mode, to help identify objects that are created at run-time.



Working with the Object Explorer Plugin for Unity

Another option for capturing HierarchyPath for a given object, including component and property names, is the Object Explorer that was introduced in GameDriver 2.0. The Object Explorer can be found under the Window > GameDriver > Object Explorer menu in the Unity editor.


Once opened, you can anchor the Object Explorer the same as any native Unity editor panel or window.


The GameDriver Object Explorer allows you to traverse every object in the active Scene, and export the HierarchyPath from any supported Object, Component, or Property to either the console log or directly to the clipboard.



This makes writing tests simpler, ensuring proper HierarchyPath syntax for all object interactions.


Note: Not all object properties or types are supported at this time and will sometimes throw errors indicating a property cannot be serialized. These errors can be safely ignored.



Putting it All Together

In this document, we covered the basics of using HierarchyPath in your GameDriver tests. There are many ways to combine the concepts presented here to build resilient automation for your Unity projects. You can locate objects by any combination of the following properties:


  • Relative object path using the //* notation

  • The full path to the object, such as “/Untagged[@name=’ParentObject’]/MyTag[@name=’MyObject’]

  • Search predicates within square brackets,e.g. “[@name=’MyObject’ and @tag=’MyTag’]

  • An object instance number, used for locating also using the predicate notation, e.g. [3]. Remember, indexes start with 0.

  • Component types, such as fn:component(‘UnityEngine.UI.Text’) or fn:component(‘UnityEngine.MeshRenderer’)

  • Field properties of the object, or component of that object, such as “//*Untagged[@name=’MyObject’]/fn:component(‘UnityEngine.UI.Image’)/@color


The HierarchyPath structure is designed to be flexible, allowing you to handle a wide range of scenarios in your GameDriver tests. If you find a scenario that you are unfamiliar with, start with the basics and refine from there. For example, you might start by searching for an object using the output from the GameDriver plugin for Unity, and printing the output to the console. Once you have refined your object search to return the necessary properties, you can incorporate them into your tests.


For additional news and information regarding the use of GameDriver and HierarchyPath, please visit the knowledge base at support.gamedriver.io



Advanced Configuration

You can choose between tag and name as the primary attribute for HPath, simply by modifying the setting in gdio.unity_agent.config:


<hierarchypath primaryattribute="name" />  or <hierarchypath primaryattribute="tag" />


For example, if the configuration is set to <hierarchypath primaryattribute="name" /> then //MyObject is same as //*[@name='MyObject'].


The default is set to tag.