How to Identify and Access Objects

Probably the most important issue to face testers when writing scripts from scratch (or when modifying recorded scripts), is how to access objects in the user interface. We can obtain a reference to an object using the Object waitForObject(objectOrName) function. This function waits for the object to become visible and available and then returns a reference to it, or raises a catchable exception if it times out. If we need a reference to an object that isn't visible we must use the Object findObject(objectName) function, which does not wait. Both functions take an object name, but getting the right name can be tricky, so we will explain the issues and solutions here before going into the Squish edition-specific and scripting language-specific details.

Squish supports a few completely different naming schemes, symbolic names, real names (also known as multi-property names), qualified names, and hierarchical names. In most editions of Squish, symbolic names are used when recording scripts. In Squish for Tk and Squish for Web, qualified and hierarchical names may also be used. For hand-written code, you can use symbolic names or real names. It is best to use symbolic names, although for some purposes real names are more convenient.

In cases where an object cannot be identified by name, a purely visual search can be performed based on a sub-image.

How to Access Named Objects

The easiest situation is where an application object has been given an explicit name by the programmer. For example, using the Qt toolkit, an object can be given a name like this:

    cashWidget->setObjectName("CashWidget");

When an object is given a name in this way, we can identify it using a real name that specifies just two properties: type and name. Depending on the Object Map implementation used, the syntax for accessing the cashWidget label differs slightly.

Here are examples in the various scripting languages using the Object waitForObject(objectOrName) function when using a Text-Based Object Map:

cashWidget = waitForObject("{name='CashWidget' type='QLabel'}")
var cashWidget = waitForObject("{name='CashWidget' type='QLabel'}");
my $cashWidget = waitForObject("{name='CashWidget' type='QLabel'}");
cashWidget = waitForObject("{name='CashWidget' type='QLabel'}")
set cashWidget [waitForObject "{name='CashWidget' type='QLabel'}"]

To create a string that represents a real (multi-property) name, we create a string which has an opening brace, then one or more space-separated property items (each having the form, propertyname='value'), and finally a closing brace.

When using a Script-Based Object Map, we can pass dictionaries to the Object waitForObject(objectOrName) function:

    cashWidget = waitForObject({"name": "CashWidget", "type": "QLabel"})
    var cashWidget = waitForObject({'name':'CashWidget', 'type':'QLabel'});
    my $cashWidget = waitForObject({'name'=>'CashWidget', 'type'=>'QLabel'});
    cashWidget = waitForObject({:name=>'CashWidget', :type=>'QLabel'})
    set cashWidget [waitForObject [::Squish::ObjectName name CashWidget type QLabel ]]

With Script-Based object maps, a dictionary object is constructed which accepts the properties to match a given object against as a set of key-value pairs. See Script-Based Object Map API for more information about the available API for constructing object names.

For most toolkits, at least two properties must be specified with one being the object's type. If the object has an object name, using just the type and name properties is sufficient (providing that the name is unique amongst objects of the specified type).

Once we have a reference to an object we can access its properties, for example, to check them against expected values, or to change them. We will see how to do this in the Squish edition-specific sections that follow.

See also, the Application Objects view and the Properties view.

How to Access Objects Using Real (Multi-Property) Names

Some objects are not identifiable by name, in which case, a special property, unnamed is 1. When we are faced with unnamed objects we can usually identify them using other properties. For example, here is one way to identify and access a payButton button.

    payButtonName = {"text": "Pay", "type": "QPushButton", "visible": 1}
    payButton = waitForObject(payButtonName)
    var payButtonName = {'type':'QPushButton', 'text':'Pay', 'visible':'1'};
    var payButton = waitForObject(payButtonName);
    my $payButtonName = {'type'=>'QPushButton', 'text'=>'Pay', 'visible'=>'1'};
    my $payButton = waitForObject($payButtonName);
    payButtonName = {:type=>'QPushButton', :text=>'Pay', :visible=>'1'}
    payButton = waitForObject(payButtonName)
    set payButtonName [::Squish::ObjectName text Pay type QPushButton visible 1]
    set payButton [waitForObject $payButtonName]

The real name above is expressed as a native key-value mapping in each script language, which is how the Script-Based Object Map works. Real names can also be expressed as strings using the legacy Text-Based Object Map, used prior to Squish 6.4:

payButtonName = "{type='QPushButton' text='Pay' visible='1'}"
payButton = waitForObject(payButtonName)
var payButtonName = "{type='QPushButton' text='Pay' visible='1'}";
var payButton = waitForObject(payButtonName);
my $payButtonName = "{type='QPushButton' text='Pay' visible='1'}"
my $payButton = waitForObject($payButtonName);
paybuttonName = "{type='QPushButton' text='Pay' visible='1'}"
payButton = waitForObject(payButtonName)
set payButtonName {{type='QPushButton' text='Pay' visible='1'}}
set payButton [waitForObject $payButtonName]

These names specify enough criteria for this particular GUI, such that there is only one button on the form with the text of Pay.

In some cases, the object we are interested in has neither a name nor any unique text of its own. But even in such cases it is usually possible to identify it. For example, an unnamed spinbox might well be the buddy of an associated label, so we can use this relationship to uniquely identify the spinbox. A Script-Based Object Map allows referencing other object names using plain script variables. Here, the buddy is identified using a symbolic name copied from the Object Map.

    paymentSpinBoxName = {"buddy": names.make_Payment_This_Payment_QLabel,
                          "type": "QSpinBox", "unnamed": 1}
    paymentSpinBox = waitForObject(paymentSpinBoxName)
    var paymentSpinBoxName = {'buddy':names.makePaymentThisPaymentQLabel,
        'type':'QSpinBox', 'unnamed':'1'};
    var paymentSpinBox = waitForObject(paymentSpinBoxName);
    my $paymentSpinBoxName = {'buddy'=>$Names::make_payment_this_payment_qlabel,
        'type'=>'QSpinBox', 'unnamed'=>'1'};
    my $paymentSpinBox = waitForObject($paymentSpinBoxName);
    paymentSpinBoxName = {:buddy=>Names::Make_Payment_This_Payment_QLabel,
        :type=>'QSpinBox', :unnamed=>'1'}
    paymentSpinBox = waitForObject(paymentSpinBoxName)
    set paymentSpinBoxName [::Squish::ObjectName type QSpinBox unnamed 1 \
        buddy $names::Make_Payment_This_Payment_QLabel ]
    set paymentSpinBox [waitForObject $paymentSpinBoxName]

If there is no obvious way of identifying an object, either use the Spy tool to get Squish to provide a suitable name, or record a quick throwaway test in which you interact with the object of interest and then put the mouse over the symbolic name, right-click and select Open Symbolic Name to see its real name in the Object Map. You can use one or the other in your real test.

In some cases we might want to use a property whose text varies. For example, if we want to identify a window whose caption text changes depending on the window's contents. This is possible using Squish's sophisticated matching capabilities and is described later in Improving Object Identification.

If the Object waitForObject(objectOrName) function cannot find the object with the given name, an exception is raised. For most languages this is a base class exception, but for Python it is the more specialized LookupError and for Ruby Squish::LookupError. If the exception isn't caught an error entry to be added to Squish's log in the Test Results view. (See How to Handle Exceptions Raised in Test Scripts.) This is normally what we want since it probably means we mistyped one of the property's values. However, if an object may exist only in some cases (for example, if a particular tab of a tab widget is chosen), we can use the Boolean object.exists(objectName) function to check if an object of the given name exists, and if it does to perform any tests we want on it in that case. For example, in Python we could write this (assuming an Script-Based Object Map is used):

moreOptionsButtonName = {'type': 'QPushButton', 'name': 'More Options'}
if object.exists(moreOptionsButtonName):
    moreOptionsButton = waitForObject(moreOptionsButtonName)
    clickButton(moreOptionsButton)

One advantage of this approach is that if the object does not exist the script finds out straight away. Compare it with this approach:

try:
    moreOptionsButtonName = {'type': 'QPushButton', 'name': 'More Options'}
    moreOptionsButton = waitForObject(moreOptionsButtonName)
except LookupError:
    pass # button doesn't exist so don't try to click it
else:
    clickButton(moreOptionsButton)

This is potentially slower than using the Boolean object.exists(objectName) function since the Object waitForObject(objectOrName) function will wait for 20 seconds (the default timeout, which can be changed by giving a second argument), although both approaches are valid.

How to Access Objects Using Symbolic Names

When Squish records a test it uses symbolic names to identify the widgets. Symbolic names assume different forms depending on whether the test uses a Text-Based Object Map or a Script-Based Object Map: in the former case, symbolic names are plain strings starting with a :, in the latter case symbolic names are script variables. Some symbolic names are quite easy to understand, for example, ":fileNameEdit_QLineEdit", while others can be more cryptic, for example, ":CSV Table - before.csv.File_QTableWidget"—this symbolic name includes the window caption which shows the name of the current file. Symbolic names are generated programmatically by Squish although they can also be used in hand-written code, or when modifying or using extracts from recorded tests.

Symbolic names have one major advantage over real names: if a property that a real name depends on changes (i.e., due to a change in the AUT), the real name will no longer be valid, and all uses of it in test scripts will have to be updated. But if a symbolic name has been used, the real name that the symbolic name refers to, (i.e., the name's properties and their values), can simply be updated in the Object Map, and no changes to tests are necessary. It is for this reason that it is almost always better to use symbolic names rather than real names whenever possible. (See Object Map and the Object Map view.)

How to Access Objects Using Images

Accessing objects by name as described above is the preferred approach. In case of dynamic user interfaces the selection of fitting properties may require some work but will result in the most robust test scripts. In light of changes in the application's GUI style for example.

In some cases, however, object identification cannot be tackled by properties. Not as easily at least. Such cases are:

  • Controls belonging to a custom or 3rd party UI toolkit.
  • Graphical shapes on a 2D or 3D canvas.
  • Access to applications other than the main AUT. This includes menus, task bars and desktop controls of the operating system itself.

To deal with such cases outside of the direct control, Squish provides an alternative look-up based on sub-images. Images of arbitrary size and shape can be searched for on the screen with ScreenRectangle waitForImage(imageFile, [parameterMap], [searchRegion]). The determined location will be returned as a ScreenRectangle which can again serve as an input to interaction functions like mouseClick(screenPoint, modifierState, button).

mouseClick(waitForImage("customcontrol.png"))

Creation of the needed sub-images as well as insertion of the respective script functions is most conveniently achieved interactively by using the Record Test Case action ( ) and e.g. the Insert > mouseClick([Image]) action ( ).

© 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.