How to Test Tk Applications

Squish's Tk-specific API enables you to find and query objects, access properties, and evaluate arbitrary Tcl code in the AUT's interpreter:

In addition, the Tk Convenience API provides functions for executing common GUI actions, such as clicking a button or selecting a menu item.

The How to Test Tk Widgets section presents various examples that show how to use the scripting Tk API to access and test complex Tk widgets.

Tk Object Names (Qualified Names)

Squish uses a completely different object naming scheme for Tk applications than for other toolkits. Tk identifies objects using qualified names, such as myapp.myframe.mylabel. Squish takes advantage of Tk's existing naming scheme and uses it for identifying objects in Tk tests.

A qualified object name is a name like myapp.frame1.okbutton. The period notation is used as a separator (rather like / or \ in file paths), that is used to identify a particular object by its position in the object hierarchy. The application's main window is the root of the hierarchy, and contains all the application's top-level widgets, some of which contain child widgets, and so on. In the example above, the okbutton is a child of frame1, which in turn is a child of the applicaton's main window, myapp.

How to Find and Query Tk Objects

The Object waitForObject(objectOrName) function returns a reference to the object with the given qualified object name.

To find out the name of an object, you can use the Spy tool to introspect the application. Alternatively, record a quick throw-away test in which you interact with all the AUT objects you are interested in to populate the Object Map with the objects' names.

To get a reference to an object—which can then be queried to check the object's properties, or which can be used to interact with the object—use the Object waitForObject(objectOrName) function. For example, in Tcl you would use code like this:

set button [waitForObject "myapp.frame1.okbutton"]

If Object waitForObject(objectOrName) can't find the specified object—or if the object is not available before the timeout, for example if it is hidden—a script error is thrown which stops the script execution. To check whether the object exists and only interact with the object if it is found, use the Boolean object.exists(objectName) function.

For example, suppose you want to find the okbutton as you did before, and click it—but only if it exists. In Tcl you can achieve this with the following code:

if {[object exists "myapp.frame1.okbutton"]} {
    set button [waitForObject "myapp.frame1.okbutton"]
    invoke clickButton $button
}

Using qualified object names with the Object waitForObject(objectOrName) function means that test engineers can query and interact with all the objects in the AUT's object hierarchy.

How to Access Tk Object Properties

Using the Tk script API it is possible to access almost all of Tk's widget properties.

For example, if you want to change the text in an entry widget, you can do so using the following Tcl code, and of course, substituting the qualified object name and the new text appropriately:

set entry [waitForObject "myapp.frame1.e1"]
property set $entry text "New text"
set text [property get $entry text]
test log [toString $text]

The first two lines set the new text; the third line creates a new variable, text, and the last line prints the text to the Test Results view.

How to Use tcleval

Although Squish test scripts can access the Tk widget properties, this is not sufficient for testing purposes, because not all the information you want to query is available through these properties. Fortunately, Squish provides a solution for this: the String tcleval(code) function. This function can execute arbitrary Tcl code which is interpreted within the scope of the AUT.

Note: This function is only available with Tcl/Tk applications, but not with Perl/Tk.

For example, if you want to retrieve the contents of a Tk text widget, you cannot do so through the widget's properties since the text is not available as a property. What you can do instead is to call the text widget's get function, since this returns the text widget's text between given indices. So to get the entire text you use indices 1.0 and end. Here's how you can use the tcleval function to call get on a text widget:

set text [invoke tcleval ".textfield get 1.0 end"]

The entire argument to tceval is passed as a string. The ".textfield" is the name of the text widget (recall that . is the root of the widget hierarcy in pure Tcl/Tk).

How to Use the Tk Convenience API

This section provides a glimpse of the script API Squish offers on top of Tk to make it easy to perform common user actions such as clicking a button. Details of the full API are given in the Tk Convenience API section of the Tools Reference. Here we will just show a few examples to give a taste of what the API offers and how to use it.

invoke clickButton [waitForObject \
    ":addressbook\\.tcl.dialog.buttonarea.ok"]
invoke type [waitForObject ":addressbook\\.tcl.dialog.email"] "com"
waitForObjectItem ":addressbook\\.tcl.#menuBar" "File"
invoke activateItem ":addressbook\\.tcl.#menuBar" "File"
waitForObjectItem ":addressbook\\.tcl.#menuBar.#file" "Open..."
invoke activateItem ":addressbook\\.tcl.#menuBar.#file" "Open..."

Here, we click a button, type some text into an entry widget, and invoke the File > Open menu option. These are the most commonly used Tk convenience functions, although there are additional ones in the API. For more examples of testing a variety of Tk widgets in AUTs see How to Test Tk Widgets.

How to Test Tk Widgets

This section illustrates how to test Tk applications using Tcl—and in particular, how to test some of the standard Tk widgets. Although only a few widgets are shown, the same principles and practices apply to all Tk widgets, so by the end of this section you should be able to test any of your AUT's widgets.

The most challenging aspect of implementing test scripts is usually creating test verifications. As shown in the chapter Inserting Additional Verification Points in the Tutorial: Starting to Test Tk Applications, this can be done using the Spy and its point & click interface. But in some cases it is actually more convenient—and more flexibile—to implement verification points directly in code.

To test and verify a widget and its properties or contents in code, first you need access to the widget in the test script. To obtain a reference to the widget, the Object waitForObject(objectOrName) function is used. This function finds the widget with the given name and returns a reference to it. For this purpose you need to know the name of the widget you want to test, and you can get the name using the Spy tool and adding the object to the Object Map (so that Squish will remember it) and then copying the object's name (preferably its symbolic name) to the clipboard ready to be pasted into our test. If you need to gather the names of lots of widgets it is probably faster and easier to record a dummy test during which you make sure that you access every widget you want to verify in our manually written test script. This will cause Squish to add all the relevant names to the Object Map, which you can then copy and paste into our code.

How to Test Widget States

One common requirement is to test the state of a widget, in particular whether it is enable or disabled. The widget's state property holds the information you want—here are a couple of examples that show it in use:

set entry1 [waitForObject ":myapp.entry1"]
test compare [property get $entry1 state] "normal"

set entry2 [waitForObject ":myapp.entry2"]
test compare [property get $entry2 state] "disabled"

This code verifies that the entry1 widget is enabled and that the entry2 widget is disabled.

Checkbuttons and Radiobuttons

Although the need to verify whether a standard Tk radiobutton or checkbutton is checked is a common requirement, neither of these widgets has a convenient property that you can use, so you must write a little bit more code than might have been expected.

We will start by verifying that a particular radiobutton is checked. First we must retrieve the radiobutton's variable and value properties, and then we must evaluate the variable to see if it is equal to the value—if it is, then the radiobutton is checked.

set radiobutton [waitForObject ":myapp.radiobutton"]
set variable [property get $radiobutton "variable"]
set value [property get $radiobutton "value"]
set actual_value [invoke tcleval "return \$$variable"]
test compare $actual_value $value

First we retrieve a reference to the radiobutton, then we retrieve the two properties we are interested in. Next we evaluate the variable to get its actual value using the String tcleval(code) function, and finally we compare the actual value with the property value to see if they're the same.

We must use a similar approach for checkbuttons, except that their relevant properties are onvalue and offvalue.

set checkbutton [waitForObject ":myapp.checkbutton"]
set variable [property get $checkbutton "variable"]
set onvalue [property get $checkbutton "onvalue"]
set actual_value [invoke tcleval "return \$$variable"]
test compare $actual_value $onvalue

Here, we retrieve a reference to the checkbutton, and then to the checkbutton's variable and onvalue properties. And just like we did for the radiobutton, we evaluate the variable to get its actual value, and compare this with the onvalue to see if they are the same.

To verify that a checkbutton was not checked, you can retrieve the offvalue property and compare that with the actual value. If they are the same, then the checkbutton is not checked.

Text fields

A standard Tk entry widget's contents can be queried using the getvalue property.

set entry [waitForObject ":myapp.entry"]
test compare [property get $entry getvalue] "Houston"

Here we check that an entry contains the text "Houston".

To query the contents of Tk's multiline text widget, we call the widget's get method, giving it the start and end indexes for the text to check:

set text [invoke tcleval ".textfield get 1.0 end"]
test compare $text "line 1\nline 2"

Rather than retrieve a reference to the multiline text widget, we use the String tcleval(code) function to execute the widget's get method with indexes that span the entire contents. This returns all of the widget's text. We then check that the text contains exactly two lines (with text "line 1" and "line 2").

Squish isn't limited to Tk's standard widgets—for example, we can test a BWidget Entry widget.

set bentry [waitForObject ":myapp.bentry"]
test compare [property get $entry text] "Apollo"

Here we retrieve the BWidget's text using its text property, and compare it to the text "Apollo".

Listbox

One common requirement is to check the text of a Tk listbox's active item. This is easily done using the listbox's get method.

set active [invoke tcleval ".listbox get active"]
test compare $active "Gemini"

Similarly to what we did for the multiline text widget, rather than retrieve a reference to the listbox, we use the String tcleval(code) function to execute the listbox's get method with an argument of active to return the listbox's active item's text. We then compare the text as usual, in this case with the literal text "Gemini".

iwidget Radiobox

The iwidget Radiobox differs from the standard Tk radiobutton in that it has a getvalue property that holds the text of its currently checked radiobutton.

set radiobox [waitForObject ":myapp.rbox"]
test compare [property get $radiobox getvalue] "Mercury"

If the Radiobox has radiobuttons, "Mercury", "Venus", and "Mars", we can verify that the "Mercury" radiobutton is checked by retrieving a reference to the Radiobox, and then comparing the value of its getvalue property to see if it matches the text of the radiobutton that should be checked.

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