Now that we have seen how to record a test and modify it by inserting verification points, we are ready to see how to create tests manually. The easiest way to do this is to modify and refactor recorded tests, although it is also perfectly possible to create manual tests from scratch.
Potentially the most challenging part of writing manual tests is to use the right object names, but in practice, this is rarely a problem. We can either copy the symbolic names that Squish has already added to the Object Map when recording previous tests, or we can copy object names directly from recorded tests. And if we haven't recorded any tests and are starting from scratch we can use the Spy. We do this by clicking the toolbar button. This starts the AUT and switches to the Squish Spy Perspective (Section 17.1.2.1). We can then interact with the AUT until the object we are interested in is visible. Then, inside the Squish IDE we can navigate to the object in the Application Objects view and use the context menu to both add the object to the Object Map (so that Squish will remember it) and to the clipboard (so that we can paste it into our test script). And at the end we can click the toolbar button to terminate the AUT and return Squish to the Squish Test Management Perspective (Section 17.1.2.2). (See How to Use the Spy (Section 15.2.3) in the User Guide (Chapter 15) for more details on using the Spy.)
We can view the Object Map by clicking the toolbar button (see also, the Object Map view (Section 17.2.8)). Every application object that Squish interacts with is listed here, either as a top-level object, or as a child object (the view is a tree view). We can retrieve the symbolic name used by Squish in recorded scripts by right-clicking the object we are interested in and then clicking the context menu's Copy item. This is useful for when we want to modify existing test scripts or when we want to create test scripts from scratch, as we will see later on in the tutorial.

Suppose we want to test the AUT's Add functionality by adding three new names and addresses. We could of course record such a test but it is just as easy to do everything in code. The steps we need the test script to do are: first click File|New to create a new address book, then for each new name and address, click Edit|Add, then fill in the details, and click OK. And finally, click File|Quit without saving. We also want to verify at the start that there are no rows of data and at the end that there are three rows. We will also refactor as we go, to make our code as neat and modular as possible.
First we must create a new empty test case. Click
| and set the test case's name to be
tst_adding. Squish will automatically create an
empty test.py (or test.js, and
so on) file.
Command line users can simply create a tst_adding
directory inside the test suite's directory and create and edit the
test.py file (or test.js and
so on) within that directory.
The first thing we need is a way to start the AUT and then invoke a menu
option. Here are the first few lines from the
recorded tst_general script:
def main():
startApplication("AddressBook.class")
activateItem(waitForObjectItem(":Address Book_javax.swing.JMenuBar", "File"))
activateItem(waitForObjectItem(":File_javax.swing.JMenu", "Open..."))
function main()
{
startApplication("AddressBook.class");
activateItem(waitForObjectItem(":Address Book_javax.swing.JMenuBar", "File"));
activateItem(waitForObjectItem(":File_javax.swing.JMenu", "Open..."));
sub main
{
startApplication("AddressBook.class");
activateItem(waitForObjectItem(":Address Book_javax.swing.JMenuBar", "File"));
activateItem(waitForObjectItem(":File_javax.swing.JMenu", "Open..."));
proc main {} {
startApplication "AddressBook.class"
invoke activateItem [waitForObjectItem ":Address Book_javax.swing.JMenuBar" "File"]
invoke activateItem [waitForObjectItem ":File_javax.swing.JMenu" "Open..."]
Notice that the pattern in the code is simple: start the AUT, then wait
for the menu bar, then activate the menu bar; wait for the menu item,
then activate the menu item. In both cases we have used the
waitForObjectItem function. This function
is used for a multi-valued objects (such as lists, tables,
trees—or in this case, a menubar and a menu), and allows us to
access the object's items (which are themselves objects of course), by
passing the name of the object containing the item and the item's text
as arguments.
![]() | Note |
|---|---|
It may seem a waste to put our functions in
|
If you look at the recorded test (tst_general) or
in the Object Map you will see that Squish sometimes uses
different names for the same things. For example,
the menubar is identified in two different ways, initially as
":Address Book_javax.swing.JMenuBar", and
then later on as ":Address Book - MyAddresses.adr_javax.swing.JMenuBar".
The reason for this is that Squish needs to uniquely identify every
object in a given context, and it uses whatever information it has to
hand. So in the case of identifying menubars (and many other objects),
Squish uses the window title text to give it some context. (For
example, an application's File or Edit menus may have different options
depending on whether a file is loaded and what state the application is
in.)
Naturally, when we write test scripts we don't want to have to know or care which particular variation of a name to use, and Squish supports this need by providing alternative naming schemes, as we will see shortly.
Once we start writing tests, sometimes the AUT will appear to freeze when we run one of our tests. When this happens, just wait for Squish to time out the AUT (about 20 seconds), and then look at the error message in the test log window. If you get an error similar to this:
Error Script Error Apr 9, 2010 Detail LookupError: Item 'New...' in object ':File_javax.swing.JMenu' not found or ready. Called from: C:\squish\examples\java\addressbook\suite_py\tst_adding\test.py: 18 Location C:\squish\examples\java\addressbook\suite_py\tst_adding\test.py:3
don't worry! It just means that Squish doesn't have an object with the given name in the Object Map. We can easily add the names we need either by recording a dummy test and interacting with all the AUT objects we plan to use in our tests or by using the Spy tool. In addition to the Spy's object picker we can also use the Spy's Application Objects view (Section 17.2.1) to locate the objects we are interested in and use the context menu to add them to the Object Map. However, recording a dummy test is often quicker for adding lots of objects to the Object Map, providing we interact with all the AUT objects we are interested in.
We've spent a bit of time on the issue of naming since it is probably the part of writing scripts that leads to the most error messages (usually of the "object ... not found" kind shown above.) Once we have identified the objects we are going to access in our tests, writing test scripts using Squish is very straightforward. And of course you can almost certainly use the scripting language you are most familiar with since Squish supports the most popular ones available.
So now we are ready to write our own test script completely from scratch.
We will start with the main function, and then we will
look at the supporting functions that the main function
uses.
def main():
startApplication("AddressBook.class")
jtable = waitForObject(":Address Book_javax.swing.JTable")
invokeMenuItem("File", "New...")
test.verify(jtable.getRowCount() == 0)
data = [("Andy", "Beach", "andy.beach@nowhere.com", "555 123 6786"),
("Candy", "Deane", "candy.deane@nowhere.com", "555 234 8765"),
("Ed", "Fernleaf", "ed.fernleaf@nowhere.com", "555 876 4654")]
for fields in data:
addNameAndAddress(fields)
test.verify(jtable.getRowCount() == len(data))
closeWithoutSaving()
function main()
{
startApplication("AddressBook.class");
var jtable = waitForObject(":Address Book_javax.swing.JTable");
invokeMenuItem("File", "New...");
test.verify(jtable.getRowCount() == 0);
var data = [
["Andy", "Beach", "andy.beach@nowhere.com", "555 123 6786"],
["Candy", "Deane", "candy.deane@nowhere.com", "555 234 8765"],
["Ed", "Fernleaf", "ed.fernleaf@nowhere.com", "555 876 4654"]];
for (var row = 0; row < data.length; ++row) {
addNameAndAddress(data[row]);
}
test.verify(jtable.getRowCount() == data.length);
closeWithoutSaving();
}
sub main
{
startApplication("AddressBook.class");
my $jtable = waitForObject(":Address Book_javax.swing.JTable");
invokeMenuItem("File", "New...");
test::verify($jtable->getRowCount() == 0);
my @data = (["Andy", "Beach", "andy.beach\@nowhere.com", "555 123 6786"],
["Candy", "Deane", "candy.deane\@nowhere.com", "555 234 8765"],
["Ed", "Fernleaf", "ed.fernleaf\@nowhere.com", "555 876 4654"]);
foreach $details (@data) {
addNameAndAddress(@{$details});
}
test::verify($jtable->getRowCount() == scalar(@data));
closeWithoutSaving;
}
proc main {} {
startApplication "AddressBook.class"
set jtable [waitForObject ":Address Book_javax.swing.JTable"]
invokeMenuItem "File" "New..."
test compare [invoke $jtable getRowCount] 0
set data [list \
[list "Andy" "Beach" "andy.beach@nowhere.com" "555 123 6786"] \
[list "Candy" "Deane" "candy.deane@nowhere.com" "555 234 8765"] \
[list "Ed" "Fernleaf" "ed.fernleaf@nowhere.com" "555 876 4654"] ]
for {set i 0} {$i < [llength $data]} {incr i} {
addNameAndAddress [lindex $data $i]
}
test compare [invoke $jtable getRowCount] [llength $data]
closeWithoutSaving
}
We begin by starting the application with a call to the startApplication function. The name we pass as a
string is the name registered with Squish (normally the name of the
.class file that has the class which contains the
main method). Then we obtain a reference to the JTable.
The object name we used was not put in the Object Map when the
tst_general test case was recorded, so we recorded
a dummy test to make sure the name was added—we could just as
easily have used the Spy of course. We then copied the name from the
Object Map into our code. The waitForObject
function waits until an object is ready (visible and enabled) and
returns a reference to it—or it times out and raises a catchable
exception. We have used a symbolic name to access
the table—these are the names that Squish uses when recording
tests—rather than a real/multi-property name (which we will soon
see an example of). It is best to use symbolic names where possible
because if any AUT object name changes, with symbolic names we just have
to update the Object Map (Section 16.10), without needing to change
our test code.The jtable variable can be used to access any
of the JTable's public methods and properties.
The invokeMenuItem function is one we have created
specially for this test. It takes a menu name and a menu option name and
invokes the menu option. After using the invokeMenuItem
function to do
File|New, we verify that the table's row count is 0. The test.verify function is useful when we simply
want to verify that a condition is true rather than compare two
different values. (For Tcl we usually use the test.compare function rather than the test.verify function simply because it is
slightly simpler to use in Tcl.)
Next, we create some sample data and call a custom
addNameAndAddress function to populate the table with the
data using the AUT's Add dialog. Then we again compare the table's row
count, this time to the number of rows in our sample data. And finally
we call a custom closeWithoutSaving function to terminate the
application.
We will now review each of the three supporting functions, so as to
cover all the code in the tst_adding test case,
starting with the invokeMenuItem function.
def invokeMenuItem(menu, item):
activateItem(waitForObjectItem("{type='javax.swing.JMenuBar' visible='true'}", menu))
activateItem(waitForObjectItem("{type='javax.swing.JMenu' caption='%s'}" % menu, item))
function invokeMenuItem(menu, item)
{
activateItem(waitForObjectItem("{type='javax.swing.JMenuBar' visible='true'}", menu));
activateItem(waitForObjectItem("{type='javax.swing.JMenu' caption='" + menu + "'}", item));
}
sub invokeMenuItem
{
my($menu, $item) = @_;
activateItem(waitForObjectItem("{type='javax.swing.JMenuBar' visible='true'}", $menu));
activateItem(waitForObjectItem("{type='javax.swing.JMenu' caption='$menu'}", $item));
}
proc invokeMenuItem {menu item} {
invoke activateItem [waitForObjectItem "{type='javax.swing.JMenuBar' visible='true'}" $menu]
invoke activateItem [waitForObjectItem "{type='javax.swing.JMenu' caption='$menu'}" $item]
}
As we mentioned earlier, the symbolic names Squish uses for menus and menu items (and other objects) can vary depending on the context, and often with the start of the name derived from the window's title. For applications that put the current filename in the title—such as the Address Book example—names will include the filename, and we must account for this.
In the case of the Address Book example, the main window's title is “Address Book” (at startup), or “Address Book - Unnamed” (after File|New, but before File|Save or File|Save As), or “Address Book - filename” where the filename can of course vary. Our code accounts for all these cases by making use of real (multi-property) names.
Symbolic names always begin with a colon and embed various bits of
information about an object and its type. Real names are represented by
a brace enclosed list of space-separated key–value pairs. Every
real name must specify the type property and at least one
other property. Here we've used the type and
visible properties to uniquely identify the menubar, and
the type and caption properties to uniquely
identify the menu.
Once we have identified the object we want to interact with we use the
waitForObjectItem function to retrieve a
reference to it and in this case we then apply the
activateItem function to it. The waitForObjectItem function pauses Squish until
the specified object and its item are visible and enabled. So, here, we
waited for the menu bar and one of its menu bar items, and then we
waited for a menu bar item and one of its menu items. And as soon as the
waiting is over each time we activate the object and its item using the
activateItem function.
def addNameAndAddress(fields):
invokeMenuItem("Edit", "Add...")
for name, text in zip(("Forename", "Surname", "Email", "Phone"), fields):
type(waitForObject(":Address Book - Add.%s:_javax.swing.JTextField" % name), text)
clickButton(waitForObject(":Address Book - Add.OK_javax.swing.JButton"))
function addNameAndAddress(fields)
{
invokeMenuItem("Edit", "Add...");
var fieldNames = ["Forename", "Surname", "Email", "Phone"];
for (var i = 0; i < fields.length; ++i)
type(waitForObject(":Address Book - Add." + fieldNames[i] + ":_javax.swing.JTextField"), fields[i]);
clickButton(waitForObject(":Address Book - Add.OK_javax.swing.JButton"));
}
sub addNameAndAddress
{
invokeMenuItem("Edit", "Add...");
my @fieldNames = ("Forename", "Surname", "Email", "Phone");
for (my $i = 0; $i < scalar(@fieldNames); $i++) {
my $fieldName = $fieldNames[$i];
my $text = $_[$i];
type(waitForObject(":Address Book - Add.$fieldName:_javax.swing.JTextField"), $text);
}
clickButton(waitForObject(":Address Book - Add.OK_javax.swing.JButton"));
}
proc addNameAndAddress {fields} {
invokeMenuItem "Edit" "Add..."
set fieldNames [list "Forename" "Surname" "Email" "Phone"]
for {set field 0} {$field < [llength $fieldNames]} {incr field} {
set fieldName [lindex $fieldNames $field]
set text [lindex $fields $field]
invoke type [waitForObject ":Address Book - Add.$fieldName:_javax.swing.JTextField"] $text
}
invoke clickButton [waitForObject ":Address Book - Add.OK_javax.swing.JButton"]
}
For each set of name and address data we invoke the Edit|Add menu option
to pop up the Add dialog. Then for each value received we populate the
appropriate field by waiting for the relevant JTextField to be ready and
then typing in the text using the type
function.
And at the end we click the dialog's OK button. We got the line at the
heart of the function by copying it from the recorded
tst_general test and simply parameterizing it by
the field name and text. Similarly, we copied the code for clicking the
OK button from the tst_general test case's code.
def closeWithoutSaving():
invokeMenuItem("File", "Quit")
clickButton(waitForObject(":Address Book - Unnamed.No_javax.swing.JButton"))
function closeWithoutSaving()
{
invokeMenuItem("File", "Quit");
clickButton(waitForObject(":Address Book - Unnamed.No_javax.swing.JButton"));
}
sub closeWithoutSaving
{
invokeMenuItem("File", "Quit");
clickButton(waitForObject(":Address Book - Unnamed.No_javax.swing.JButton"));
}
proc closeWithoutSaving {} {
invokeMenuItem "File" "Quit"
invoke clickButton [waitForObject ":Address Book - Unnamed.No_javax.swing.JButton"]
}
Here we use the invokeMenuItem function to do File|Quit,
and then click the "save unsaved changes" dialog's No button. The last
line was copied from the recorded test, but with the filename changed
from "MyAddresses.adr" to "Unnamed" since in the course of the test we
invoked File|New but never saved the file. (An alternative would have
been to use a real (multi-property) name to identify the unsaved changes
dialog's No button.)
The entire test is under 30 lines of code—and would be even less
if we put some of the common functions (such as
invokeMenuItem and closeWithoutSaving) in
a shared script. And much of the code was copied directly from the
recorded test, and in some cases parameterized.
This should be sufficient to give a flavor of writing test scripts for an AUT. Keep in mind that Squish provides far more functionality than we used here, (all of which is covered in the API Reference for Test Scripts (Section 16.1) in the Tools Reference Manual (Chapter 16)). And Squish also provides access to the entire public APIs of the AUT's objects.
However, one aspect of the test case is not very satisfactory. Although embedding test data as we did here is sensible for small amounts, it is rather limiting, especially when we want to use a lot of test data. Also, we didn't test any of the data that was added to see if it correctly ended up in the JTable. In the next section we will create a new version of this test, only this time we will pull in the data from an external data source, and check that the data in the JTable is correct.
In the previous section we put three hard-coded names and addresses in
our test. But what if we want to test lots of data?
One approach is to import a dataset into Squish and use the dataset as
the source of the values we insert into our tests. Squish can import
data in
.tsv (tab-separated values format),
.csv (comma-separated values format), and
.xls (Microsoft® Excel™ spreadsheet format).
[4]
Test data can either be imported using the Squish IDE, or manually using a file manager or console commands. We will describe both approaches, starting with using the Squish IDE.
For the addressbook application we want to
import the MyAddresses.tsv data file. To do this we
must start by clicking
| to pop-up the "Import Squish Resource" dialog (Section 17.3.3). Inside the dialog click
the to choose the file to import—in
this case MyAddresses.tsv. Make sure that the
Import As combobox is set to
“TestData”. By default the Squish IDE will import the test data
just for the current test case, but we want the test data to be
available to all the test suite's test cases: to do this check the
radio button. Now
click the button. You can now see the file
listed in the Test Suite Resources view (in the Test Data tab), and if
you click the file's name it will be shown in an Editor view (Section 17.2.5). The screenshot shows Squish after the
test data has been added.
![]() | For command-line users |
|---|---|
It is also possible to import test data outside the Squish IDE using a file
manager (such as File Explorer) or console commands. To do this, create
a directory inside the test suite's directory called
|

Although in real life we would modify our
tst_adding test case to use the test data, for the
purpose of the tutorial we will make a new test case called
tst_adding_data that is a copy of
tst_adding and which we will modify to make use of
the test data.
The only function we have to change is main, where
instead of iterating over hard-coded items of data, we iterate over all
the records in the dataset. We also need to update the expected row
count at the end since we are adding a lot more records now, and we will
also add a function to verify each record that's added.
def main():
startApplication("AddressBook.class")
jtable = waitForObject(":Address Book_javax.swing.JTable")
invokeMenuItem("File", "New...")
test.compare(jtable.getRowCount(), 0)
limit = 10 # To avoid testing 100s of rows since that would be boring
for row, record in enumerate(testData.dataset("MyAddresses.tsv")):
forename = testData.field(record, "Forename")
surname = testData.field(record, "Surname")
email = testData.field(record, "Email")
phone = testData.field(record, "Phone")
addNameAndAddress((forename, surname, email, phone)) # pass as a single tuple
checkNameAndAddress(jtable, record)
if row >= limit:
break
test.compare(jtable.getRowCount(), row + 1)
closeWithoutSaving()
function main()
{
startApplication("AddressBook.class");
var jtable = waitForObject(":Address Book_javax.swing.JTable");
invokeMenuItem("File", "New...");
test.compare(jtable.getRowCount(), 0);
var limit = 10; // To avoid testing 100s of rows since that would be boring
var records = testData.dataset("MyAddresses.tsv");
var row = 0;
for (; row < records.length; ++row) {
var record = records[row];
var forename = testData.field(record, "Forename");
var surname = testData.field(record, "Surname");
var email = testData.field(record, "Email");
var phone = testData.field(record, "Phone");
addNameAndAddress([forename, surname, email, phone]);
checkNameAndAddress(jtable, record);
if (row >= limit)
break;
}
test.compare(jtable.getRowCount(), row + 1);
closeWithoutSaving();
}
sub main
{
startApplication("AddressBook.class");
my $jtable = waitForObject(":Address Book_javax.swing.JTable");
invokeMenuItem("File", "New...");
test::compare($jtable->getRowCount(), 0);
my $limit = 10; # To avoid testing 100s of rows since that would be boring
my @records = testData::dataset("MyAddresses.tsv");
my $row = 0;
for (; $row < scalar(@records); $row++) {
my $record = $records[$row];
my $forename = testData::field($record, "Forename");
my $surname = testData::field($record, "Surname");
my $email = testData::field($record, "Email");
my $phone = testData::field($record, "Phone");
addNameAndAddress($forename, $surname, $email, $phone);
checkNameAndAddress($jtable, $record);
if ($row >= $limit) {
last;
}
}
test::compare($jtable->getRowCount(), $row + 1);
closeWithoutSaving;
}
proc main {} {
startApplication "AddressBook.class"
set jtable [waitForObject ":Address Book_javax.swing.JTable"]
invokeMenuItem "File" "New..."
test compare [invoke $jtable getRowCount] 0
# To avoid testing 100s of rows since that would be boring
set limit 10
set data [testData dataset "MyAddresses.tsv"]
set columns [llength [testData fieldNames [lindex $data 0]]]
set row 0
for {} {$row < [llength $data]} {incr row} {
set record [lindex $data $row]
set forename [testData field $record "Forename"]
set surname [testData field $record "Surname"]
set email [testData field $record "Email"]
set phone [testData field $record "Phone"]
set fields [list $forename $surname $email $phone]
addNameAndAddress $fields
checkNameAndAddress $jtable $record
if {$row >= $limit} {
break
}
}
test compare [invoke $jtable getRowCount] [expr $row + 1]
closeWithoutSaving
}
Having used the test data to populate the JTable we want to be confident
that the data in the table is the same as what we have added, so that's
why we added the checkNameAndAddress function. We also added a limit to
how many records we would compare, just to make the test run faster.
Squish provides access to test data through its testData
module's functions—here we used the testData.dataset function to access the data file
and make its records available, and the testData.field function to retrieve each record's
individual fields.
def checkNameAndAddress(jtable, record):
waitForObject(":Address Book - Unnamed_javax.swing.JTable")
tableModel = jtable.getModel()
for column in range(len(testData.fieldNames(record))):
value = tableModel.getValueAt(0, column)
test.compare(value, testData.field(record, column))
function checkNameAndAddress(jtable, record)
{
waitForObject(":Address Book - Unnamed_javax.swing.JTable");
var tableModel = jtable.getModel();
for (var column = 0; column < testData.fieldNames(record).length; ++column) {
var value = tableModel.getValueAt(0, column).toString();
test.compare(value, testData.field(record, column));
}
}
sub checkNameAndAddress
{
my($jtable, $record) = @_;
waitForObject(":Address Book - Unnamed_javax.swing.JTable");
my $tableModel = $jtable->getModel();
my @columnNames = testData::fieldNames($record);
for (my $column = 0; $column < scalar(@columnNames); $column++) {
my $value = $tableModel->getValueAt(0, $column);
test::compare($value, testData::field($record, $column));
}
}
proc checkNameAndAddress {jtable record} {
waitForObject ":Address Book - Unnamed_javax.swing.JTable"
set tableModel [invoke $jtable getModel]
set columns [llength [testData fieldNames $record]]
for {set column 0} {$column < $columns} {incr column} {
set value [invoke $tableModel getValueAt 0 $column]
test compare $value [testData field $record $column]
}
}
This function accesses the JTable's underlying TableModel and extracts
each cell's value.
We then use Squish's test.compare function to check that the value in
the cell is the same as the value in the test data we used. Note that
this particular AUT always adds a new row before the current row (or as
the first row if there are no rows yet), and always makes the added row
the current row. The effect of this is that every new name and address
is always added as the first row, and this is why we hard-coded the row
to be 0.
The screenshot show Squish's Test Summary log after the data-driven tests have been run.

[4]
Both .csv and .tsv files are
assumed to use the Unicode UTF-8 encoding—the same encoding used
for all test scripts.