Table of Contents
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 waitForObject
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 findObject
function, which does not
wait. Both these 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 by Squish when recording scripts. In the cases of S/Tk and S/Web, qualified and hierarchical names may also be used. For hand-written code, we 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.
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 (Section 7.11) implementation used, the syntax
for accessing the cashWidget
label differs slightly.
Here are examples in the various scripting languages using the waitForObject
function when using a Text-Based Object Map (Section 7.11.5):
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 (Section 7.11.4), we are able to pass
dictionaries to the waitForObject
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 (Section 7.11.4.3) for more information on 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 (Section 8.2.1) and the Properties view (Section 8.2.12).
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 (Section 7.11.4) works. Real names can also be expressed as strings using the legacy Text-Based Object Map (Section 7.11.5), 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 (Section 7.11.4) allows referencing other object names using plain script variables. Here, the buddy is identified using a symbolic name copied from the Object Map (Section 7.11).
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 Squish's Spy tool (How to Use the Spy (Section 5.21.3)) 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 (Section 7.11). 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 (Section 7.10).
If the waitForObject
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 (Section 8.2.18). (See How to Handle Exceptions Raised in Test Scripts (Section 5.19).) 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 object.exists
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 (Section 7.11.4) 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 object.exists
function since the waitForObject
function will wait for 20 seconds
(the default timeout, which can be changed by giving a second argument),
although both approaches are valid.
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 (Section 7.11.5) or a Script-Based Object Map (Section 7.11.4): 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 (Section 7.11) and the Object Map view (Section 8.2.10).)
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
waitForImage
. The determined location will
be returned as a ScreenRectangle which
can again serve as an input to interaction functions like
mouseClick
.
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
(
).