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
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
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
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
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:
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
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:
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. ) 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:
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 fn:components() to Resolve multiple Components of the same Type
In situations where selecting and returning field values or calling methods from a Component among a list of components of the same type on an object, fn:components() can be handy.
Notice the index '' which selects the third component on the GameObject named 'TestObject' and then returns the 'intVal' property.
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']”);
Using Parent Axis
It is possible to find objects using references to their ancestor or descendent objects by utilizing the parent axis ('..').
api.GetObjectPosition(“//TagButton[../@name = 'Canvas']”);
The above will look for an object with the tag “TagButton” that has a parent object with the name “Canvas”.
The parent axis can be used multiple times in sequence to reference objects that are not immediate relatives (i.e. not a direct parent or child) of the object. If, for example, we wanted to find an object that we had no information about, other than that it had a grandchild object (a child object of one of its own child objects) with the name "ButtonText", we could find it via repeated uses of the parent axis such as the following:
The first usage of the parent axis elevates the reference to the "ButtonText" object's immediate parent, and then the second use elevates us to its parent's parent (i.e. the grandparent).
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 also be used for forming predicates based around a substring.
The above would return the first object that had the substring "Text" somewhere in its name. The same can be applied to any string property a component might have.
Order of Operations
It is important to remember the order of operations of HierarchyPath parsing, particularly when it comes to the use of the index predicates.
The above two lines of code seem nearly indistinct, and in many cases, they might even produce the same result, but the distinction can make a significant difference.
In both cases, it starts by finding objects with the name "Button" but then diverges due to the difference in what the index predicate ('') is considered to be modifying. The first statement considers the index predicate to be modifying the wild card tag, and will therefore return all objects that are the second child of an object named "Button". The second statement considers the index predicate to be modifying the entire path (due to enclosing the path in parenthesis), and will therefore first find all objects that are children of an object called 'Button', and then return only the second object in the list of objects found.
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:
For the same object, the following appears when we select the “Tag and Name” option (ignoring the unnecessary output):
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 is used for locating also using the predicate notation, e.g. . 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
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.