Object Name Generation
Object Names
When recording a test case or picking objects in the Spy, Squish automatically creates a name for each object that is accessed so that it can be identified later, for example, in a test script. This name could be a multi-property (real) name or a hierarchical name. Hierarchical names are still used by default when testing some toolkits, such as Web and Tk. For most toolkits, the default naming scheme is multi-property (real) names since they lead to much more flexible and reliable object identification in the face of application changes.
As discussed in the Object Map chapter, the representation of real names differs depending on the kind of object map implementation used; for Text-Based Object Map, a real name consists of a set of space-separated <propertyName>=<value> pairs enclosed in {curly braces}. For example, a multi-property name which identifies the object whose type is Button
and whose text is "Hello" will be a string like this, in all script languages:
{type='Button' text='Hello'}
The section Structure of Script-Based Object Maps explains that the very same object name can also be expressed as a native dictionary or map (the syntax of course varies slightly depending on the scripting language used):
{"type": "Button", "text": "Hello"}
{type: 'Button', text: 'Hello'};
{:type => 'Button', :text => 'Hello'}
{'type' => 'Button', 'text' => 'Hello'}
ObjectName type Button text Hello
Every multi-property name must have at least two properties, one of which is a mandatory toolkit-specific property. The toolkit-specific properties are:
- Java: type or basetype (but not both)
- Web: tagName
- All others: type
Some objects can only be found inside other containing objects because of their class design. In those cases, a container property is also required in the real name. This is an object reference to a container (or window, or parentItem). Container properties are needed whenever Squish needs to cross otherwise unrelated object hierarchies to find an object. Some examples are: List, Tree and Table items, QGraphicsItem
s, QtQuick Items, and HTML elements from an embedded HTML control, such as a QWebView
.
Note that the type, basetype, tagName properties (and a few others) can only be matched exactly—most other string properties can be matched using wildcards or regexes to make Squish test scripts more flexible in the face of changes. (See Improving Object Identification.)
Many objects have lots of properties, and Squish must choose amongst them to create names that will correctly and uniquely identify each object, and that are as short as possible, i.e., that use as few properties as possible. Unfortunately, these two requirements conflict with each other: uniqueness can be ensured by using all or most of an object's properties, but short names require us to use as few properties as possible.
Why do we need short names at all? Because the fewer properties we use to identify an object, the less chance there is that a change to the application which affects one of the object's properties will affect one of the properties that we are using to identify the object. So short names (i.e., names with as few properties as possible), are more robust in the face of application changes. On the other hand, if we use too few properties we might end up with names that are too general, that match two or more objects, and if Squish cannot uniquely identify an object, how can it know which object is intended to be accessed in a test script?
There is no one right or perfect answer to this problem. Names which are invalidated because of application changes (i.e., names where the value of one or more of the properties used in the name have changed), result in test scripts that fail. Similarly, names which are no longer unique (perhaps due to using too few properties for an object and then having an application change that leads to another object with identical properties being added to the application), also lead to test script failure. So Squish must find the right balance when creating names so that they are both unique and robust.
Squish uses a set of built-in heuristics ("rules of thumb") to determine what properties to use. In most everyday testing situations these work fine and Squish creates names that are both unique and robust.
Unfortunately, there are some situations where the heuristics Squish uses produce poor results. For example, when writing a Qt test, Squish will use the QObject
objectName property (which Squish calls name) to identify an object, providing this property has some text in it. In most cases this works very well, especially if the AUT's developers have chosen unique names for their QObject
s. However, if the property changes over time, then clearly Squish cannot rely on it for identification purposes, and we need to have some way of excluding it from the list of properties that Squish makes use of.
Defining Property Sets
In Squish, the property list used to create real names can be configured by editing some straightforward XML files. How to use these files to customize the name creation process is explained in the following two sections.
Note: The following sections do not apply to the testing of Web applications. For these, see Name Generation Algorithm used by Squish for Web.
Descriptor File Locations
The list of properties that Squish uses to create multi-property (real) names for objects are specified using XML files. For each wrapper which your application uses there can be up to two XML files—called "descriptor files" in Squish terminology—which must follow a particular naming scheme so that Squish can find them:
- SQUISHDIR/etc/wrapper_descriptors.xml: This XML file contains the default properties which Squish uses for creating real (multi-property) names. Depending on the type of package you use, you might find
qtwrapper_descriptors.xml
,javawrapper_descriptors.xml
or others in <SQUISHDIR>/etc
.<SQUISHDIR> stands for the directory where you installed your Squish package.
- Squish_User_Settings/wrapper_user_descriptors.xml: If you want to override the default behavior, or add new properties, you can do so by creating a user descriptors file for each wrapper you want to affect. These files have names similar to the predefined descriptor files supplied with Squish, but with
_user_
inserted in the middle of the name as shown above.<Squish_User_Settings> stands for the directory where user specific settings are stored. On Windows, this is
%APPDATA%\froglogic\Squish
, and on Unix-like systems, such as Linux and macOS, it is~/.squish
. If you set the environment variable SQUISH_USER_SETTINGS_DIR to point to a different directory, that directory is used instead for storing the user settings—and also for user descriptor files.
Exactly the same file format is used for the predefined descriptor files that are supplied with Squish and for the user descriptor files that you can create to customize how Squish generates names.
Every descriptor file contains a list of types together with the names of the properties that can be used when generating names for objects of each particular type. Squish reads its own predefined descriptor files first, and then it reads any user descriptor files. This makes it possible for user descriptor files (those with filenames of the form wrapper_user_descriptors.xml
), to override the behavior specified in the predefined descriptor files, and to add new type descriptors.
Descriptor File Format
The list of types and the properties that can be used for their objects are specified using a simple XML format. Here's a short example for a fictional application toolkit which has a Button type:
<objectdescriptors> <descriptor> <type name="Button"/> <realidentifiers> <property>caption</property> </realidentifiers> </descriptor> </objectdescriptors>
This descriptor file defines just one <descriptor> which says that for all objects of type Button
, caption should be used when generating the real name.
Note: Name inheritance means that not only objects which are instances of the Button
class will get the caption property in their real name, but also instances of classes which inherit Button
. When deciding which descriptors to use for generating the name for a given object, Squish takes the inheritance hierarchy into account and will use all descriptors whose type name shows up in the inheritance hierarchy of the given object.
In this example, only the name of the type was used for identifying the object. However, it is also possible to apply constraints, so that only those objects of the given type and that meet specified constraints are identified. Here's a slightly longer example to illustrate this:
<objectdescriptors> <descriptor> <type name="Button"> <constraint name="visible">false</constraint> </type> <realidentifiers> <property>caption</property> <property>tooltip</property> </realidentifiers> </descriptor> <descriptor> <type name="Button"/> <realidentifiers> <property>caption</property> <property>xpos</property> <property>ypos</property> </realidentifiers> </descriptor> </objectdescriptors>
Here we have created two separate descriptors, although they both refer to objects of the same type.
The first descriptor applies to objects of type Button
—but only when the button's visible property is false
, in other words, this descriptor only applies to hidden Buttons. So when the application has a hidden button, this descriptor says that the properties that should be used to identify it when creating real names are caption and tooltip.
The second descriptor applies to objects of type Button
, but only visible ones since hidden Buttons are handled by the first descriptor. This descriptor says that when creating real names for Button
objects, their caption, xpos, and ypos properties should be used.
Although we have only shown the use of a single constraint, it is possible to use as many as we like. In such cases the descriptor will only be used if all the constraints are met.
In general, when multiple descriptors are specified which apply to the same type, Squish will try to use the one that is the best match for the object it is accessing, essentially working from the descriptor with the most constraints to the one with the least.
So in terms of the second example above, if Squish encounters a Button
, it will first try the first descriptor (since that has the most, i.e., one, constraints). If the button is hidden, Squish has a match and will generate a real name that uses the Button's caption and tooltip. However, if the Button is visible, the first descriptor won't match, so Squish will try the next one, and this matches (since it has no constraints, so will match any Button
), and generates a real name that uses the Button's caption, xpos, and ypos.
Advanced Property Set Definitions
The Catch-All *
Descriptor
In addition to the normal descriptors, which match an object by the type name (and optionally, by constraints), there's a special descriptor called *
(star or asterisk), which matches objects of any type. This special descriptor can also have constraints applied to it, in exactly the same way as for normal descriptors. The toolkit wrappers that support *
as a catch-all are Qt, Java, and Mac. For Windows or Android, use WinGUIObject
or Control
for the type name instead. Here's an example of how it might be used:
<objectdescriptors> <descriptor> <type name="*"/> <realidentifiers> <property>id</property> </realidentifiers> </descriptor> <descriptor> <type name="Button"/> <realidentifiers> <property>caption</property> </realidentifiers> </descriptor> </objectdescriptors>
In this example, a catch-all descriptor is defined. This means that for all objects, no matter what their type name is, id will be used when generating real names. If a particular object does not have an id, the property will be silently ignored and this will not trigger an error.
Note: It is harmless to list properties in a catch-all descriptor which don't exist for some object types. For example, if almost all of our object types have an id that we normally want to use when real names are generated, the best approach is to specify this property using a catch-all descriptor. This will ensure that id is always used for real names for those objects that have it, and yet it is safely and silently ignored for those few that don't.
When real names are generated, the properties used are those for the matching class descriptor, plus those for the matching descriptor of the object's base class, and so on, up the inheritance hierarchy. In addition, any catch-all descriptors are also used.
Given the example descriptors shown above, if a Button
object was encountered—and assuming that Buttons have an id, Squish would generate a real name that would use caption (from the Button
descriptor) and id (from the *
descriptor). And if the Button
didn't have an id, Squish would simply use caption and ignore id. If the Button
derived from another type, for example, Widget
, that had a descriptor that specified, say, hasfocus, then that property would also be included in the generated real name.
Groups of Exclusive Properties
Suppose we have a descriptor file that contains the following rule for Button
:
<objectdescriptors> <descriptor> <type name="Button"/> <realidentifiers> <property>id</property> <property>caption</property> <property>tooltip</property> <property>enabled</property> </realidentifiers> </descriptor> </objectdescriptors>
This descriptor specifies four properties: id, caption, tooltip, and enabled, that will be used to generate the real names for Button
objects. Unfortunately, in practice this might lead to less robust real names. This is because we are using too many properties, so our test scripts will be vulnerable if a Button's caption or its tooltip changes. What we really want to say is that if caption has a value, then we should use it, but if caption is empty then we should use tooltip as a fallback.
Squish provides a means of solving this problem. The mechanism is part of the descriptor file format, and it allows us to specify two or more properties such that Squish will only use one of them—the first one which actually has a value. The mechanism is called "property groups". Here is an example:
<objectdescriptors> <descriptor> <type name="Button"/> <realidentifiers> <property>id</property> <group> <property>caption</property> <property>tooltip</property> </group> <property>enabled</property> </realidentifiers> </descriptor> </objectdescriptors>
Here we have put caption and tooltip in a <group> together. The effect of this is to tell Squish to use caption if it has a value (i.e., if it isn't an empty string), and to use tooltip otherwise. So when Squish generates a real name for a Button
using this descriptor, the name will have id, enabled, and either caption or tooltip—but never both—plus any properties from the matching descriptors of the classes that Button
inherits from, plus any properties from any catch-all *
descriptor.
Properties with Object Name Values
In addition to specifying the names and values of properties, real names can also specify references to related objects, and such references can help to uniquely identify an object of a particular type.
For example, we might have a form with several single line editors. In themselves these editors might all have the same properties with nothing to distinguish them, but in all probability they will all have labels beside them so that the user knows what they are expected to type into them. In some toolkits such labels might be identified by their position in relation to the object—left or above—and in other toolkits they are identified by their relationship to the object—for example, they are its buddy.
Here's an example real name for a fictional single line editor which is identified by its own properties, and also by its buddy whose value is a reference to a related object:
{type='LineEdit' maxChars='32' allowDigits='false' buddy={type='Label' text='Last Name:'}}
Normal property values are enclosed in quotes, but when the value is a reference to a related object, quotes are not used and instead the related object's real name is used, enclosed in braces. (So we end up with one real name nested inside another.)
The same object name can of course also be expressed when using Script-Based Object Map:
{"type": "LineEdit", "maxChars": 32, "allowDigits": False, "buddy": {"type": "Label", "text": "Last Name:"}}
{type: 'LineEdit', maxChars: 32, allowDigits: false, buddy: {type: 'Label', text: 'Last Name:'}}
{:type => 'LineEdit', :maxChars => 32, :allowDigits => false, :buddy => {:type => 'Label', :text => 'Last Name:'}}
{'type' => 'LineEdit', 'maxChars' => 32, 'allowDigits' => 'false', 'buddy' => {'type' => 'Label', 'text' => 'Last Name:'}}
ObjectName type LineEdit maxChars 32 allowDigits false buddy [ObjectName type Label text {Last Name:}]
If we want to use a property that refers to another object in a descriptor, we simply name it using the <object> tag instead of the <property> tag, as the following example illustrates:
<objectdescriptors> <descriptor> <type name="LineEdit"/> <realidentifiers> <property>maxChars</property> <property>allowDigits</property> <object>buddy</object> </realidentifiers> </descriptor> </objectdescriptors>
What this descriptor says is that when Squish encounters a LineEdit
, it should generate a real name that includes maxChars and allowDigits, and also buddy which is an object reference.
Excluding Individual Properties From Some Objects' Real Names
In some situations we want a particular property to be used when generating real names for most objects of a type, but not for absolutely all of them. For example, we might have a bunch of objects derived from Widget
with a dropEnabled. If most of the derived objects are editing widgets, the property makes sense, but it probably doesn't make sense for a derived Button
object. So we want to create a descriptor that includes dropEnabled for all Widget
s, except for Button
. Squish allows us to do this by using the exclude attribute of <property> as the following example shows:
<objectdescriptors> <descriptor> <type name="Widget"/> <realidentifiers> <property>dropEnabled</property> <property>text</property> </realidentifiers> </descriptor> <descriptor> <type name="Button"/> <realidentifiers> <property exclude="yes">dropEnabled</property> </realidentifiers> </descriptor> </objectdescriptors>
In this example, Widget
is the base class of all GUI objects. The first descriptor tells Squish to use dropEnabled and text when generating real names for Widget
objects and for all objects that derive from it (such as LineEdit
and Button
). There is no <descriptor> for LineEdit
since they are derived from Widget
and we have no properties to add or change. But for Button
we have created a <descriptor> so that we can stop dropEnabled from being used for Button
names.
If we were to use the descriptor file above with our fictional toolkit, it would change the way that Squish creates real names. For LineEdit
s (assuming they are derived from Widget
) Squish will create real names that have dropEnabled, text (and of course type since that is always included), but for Button
s, Squish will create real names that only use text and type.
Name Generation Algorithm used by Squish for Web
Squish for Web has a different heuristic to generate object names, it does not use the descriptor files mentioned in the previous section. The name generation first checks all extensions that registered a hook for name generation through String Squish.nameOf(objectOrName). Afterwards it applies the following rules, taking the first one that matches. All multi-property names include the tagName, this one is required for Squish for Web object names. The names can also include a context containing the name of the frame/iframe element in which the object is located. This property is not included for objects in the toplevel page.
SELECT
,INPUT
andBUTTON
elements include the properties name, id, type and innerText if they are not empty. In addition a form property is generated if the containing form has a name or id property set.SELECT
andINPUT
fields also get the type property added to the object name.TD
elements having thecMenuTD
class include the properties class, id, name and innerText if they are not empty.IMG
elements having id, name or alt set to a non-empty string will generate a multi property name including those properties. Otherwise Squish uses a hierarchical name forIMG
elements. The alt attribute of the element will be reflected as img_alt in the multi-property name- The next step is walking up the hierarchy of elements to the one for which a name is to be generated. For each parent, Squish checks if it is a
link
element. If it is, the following rules are applied to generate a name for it:- If the element has a non-empty id, name or innerText property it will get a multi-property name including those properties.
- If all of the three are empty or not present Squish looks at the content of the link to see if there is an IMG element inside. If there is such an element and it has a non-empty id, name or alt attribute the name for the
link
element will use those values as img_id, img_name and img_alt respectively for its multi-property name. - If the inner
IMG
element does not have a non-empty id, name or alt attribute, Squish checks the src attribute. If that attribute is not-empty, Squish extracts the substring after the last '/' from the src attribute and includes that using the img_src property in the multi-property name for the link. - If all of the above mentioned attributes are empty, Squish falls back to using a hierarchical name for the
link
element.
If the top-level element is reached, Squish returns to the original object for which a name is to be generated and continues with the next rule.
- If the element has a tagName and either a title, id or name attribute set, Squish generates a multi-property name including those properties.
- If the element is a
SPAN
,DIV
,LI
,TD
orTR
element and it has a non-empty innerText attribute, then Squish generates a multi-property name using that attribute as a property. - In all other cases, Squish will generate a hierarchical name for the object
The last step is to calculate the occurrence property in case a multi-property name has been generated.
Squish for Web also allows customization of name generation. For details on that see Web Object Extension API.
© 2024 The Qt Company Ltd.
Documentation contributions included herein are the copyrights of
their respective owners.
The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation.
Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property
of their respective owners.