Squish for Java (AWT/Swing) Tutorials
Learn how to test applications which are based on Java AWT/Swing.
For more information about how to test other kinds of Java applications or create behavior driven development tests, see:
- Tutorial: Starting to Test Java SWT Applications
- Tutorial: Starting to Test JavaFX Applications
- Squish for JavaFX BDD Tutorials
- Tutorial: Model-based Testing
Tutorial: Starting to Test Java AWT/Swing Applications
Note: There is a 45-minute Online course about Squish Basic Usage at the if you desire some video guidance.
The AUT we are using in this tutorial is a simple Address Book application. Users can add new addresses via a dialog, edit addresses in-place, and remove addresses. They can also open and save address book data files. It has basic UI components: menus, a table, and a pop-up dialog with line edits and buttons.
For examples that show how to test lists, tables, and trees, and other common widgets, including spinners, see How to Test Java Applications.
The screenshot below shows the application in action with a user adding a new name and address.
The application sourcecode, as well as a packaged AddressBookSwing.jar
, can be found in <SQUISHDIR>/examples/java/addressbook/
.
If you compile yourself, you can use AddressBook.class
as the AUT since it also contains the main()
function. You can also use a batch file, or shell script. Some executable launchers even work as an AUT.
The tests that we will discuss in the following sections are in sub-folders, for example, the versions of the tests using the Python language are in <SQUISHDIR>/examples/java/addressbook/suite_py
, with the tests written in other languages in similarly named sub-folders.
Squish Multi-Process Architecture and IPC
Squish runs a small server, squishserver
, that handles the communication between the AUT and the test script. The test script is executed by the squishrunner
tool, which in turn connects to squishserver
. squishserver
starts the instrumented AUT on the device, which starts the Squish Hook. With the hook in place, squishserver
can query AUT objects regarding their state and can execute commands on behalf of squishrunner
. squishrunner
directs the AUT to perform whatever actions the test script specifies.
All the communication takes place using network sockets which means that everything can be done on a single machine, or the test script can be executed on one machine and the AUT can be tested over the network on another machine.
The following diagram illustrates how the individual Squish tools work together.
Tests can be written and executed using the Squish IDE, in which case squishserver
is started and stopped automatically, and the test results are displayed in the Squish IDE's Test Results view. The following diagram illustrates what happens behind the scenes when the Squish IDE is used.
Under the covers, squishrunner
is used to execute test cases. If we need to automate the execution of test cases from a script, we would use this command directly.
Making an Application Testable
In most cases, nothing special needs to be done to make a Java application testable. If you do have problems testing your own AUT with Squish, the most likely cause is a mismatch in Java versions. This is probably due to the AUT using its own packaged JRE, which is not compatible with the one Squish is configured for. You can tell Squish to use another Java by invoking the squishconfig command:
$ <SQUISHDIR>/bin/squishconfig --java=/path/to/java
Creating Test Suites from Squish IDE
Start up the Squish IDE, by clicking or double-clicking the Squish IDE icon, by launching Squish IDE from the taskbar menu or by executing squishide
on the command line, whichever you prefer and find suitable for the platform you are using.
Once Squish starts up, you might be greeted with a Welcome Page. Click the Workbench button in the upper right to dismiss it. Then, the Squish IDE will look similar to the screenshot.
Creating Test Suites from Squish IDE
Start up the Squish IDE, by clicking or double-clicking the Squish IDE icon, by launching Squish IDE from the taskbar menu or by executing squishide
on the command line, whichever you prefer and find suitable for the platform you are using.
Once Squish starts up, you might be greeted with a Welcome Page. Click the Workbench button in the upper right to dismiss it. Then, the Squish IDE will look similar to the screenshot.
Once Squish has started, click File > New Test Suite to pop-up the New Test Suite wizard shown below.
Enter a name for your test suite and choose the folder where you want the test suite to be stored. We have called it suite_py
. Once the details are complete, click Next to go on to the Toolkit (or Scripting Language) page.
For the 'Toolkit' page, select Java, which covers AWT, Swing, SWT, and other Java toolkits. Then click Next to go to the Scripting Language page.
Choose your desired scripting language on this page. Our example tests are in 5 different languages and you can pick the one that suits you. The functionality offered by Squish is the same for all languages.
If you are creating a new test suite for an AUT that Squish already knows about, simply click the combobox to pop-down the list of AUTs and choose the one you want. If the combobox is empty or your AUT isn't listed Browse to it. Navigate to <SQUISHDIR>/examples/java/addressbook
and choose AddressBookSwing.jar
. Click Finish and the wizard will close and Squish IDE will look similar to the screenshot below.
Recording Tests and Verification Points
Recordings are made into existing test cases. You can create a New Script Test Case in the following ways:
- Select File > New Test Case to open the New Squish Test Case wizard, enter the name for the test case, and select Finish.
- Click the New Script Test Case () toolbar button to the right of the Test Cases label in the Test Suites view. This creates a new test case with a default name, which you can easily change.
Give the new test case the name tst_general
.
Squish automatically creates a sub-folder inside the test suite's folder with this name and also a test file, for example test.py
. If you choose JavaScript as the scripting language, the file is called test.js
, and correspondingly for Perl, Ruby, or Tcl.
If you get a sample .feature
file instead of a "Hello World" script, click the arrow left of the Run Test Suite () and select New Script Test Case ().
To make the test script file (such as, test.js
or test.py
) appear in an Editor view, click or double-click the test case, depending on the Preferences > General > Open mode setting. This selects the Script as the active one and makes visible its corresponding Record () and Run Test Case () buttons.
The checkboxes are used to control which test cases are run when the Run Test Suite () toolbar button is clicked. We can also run a single test case by clicking its Run Test Case () button. If the test case is not currently active, the button may be invisible until the mouse is hovered over it.
Initially, the script's main()
function logs Hello World to the test results.
Once the new test case has been created, we are free to write test code manually or to record a test. Clicking on the test case's Record () button replaces the test's code with a new recording. Alternatively, you can record snippets and insert them into existing test cases, as instructed in How to Edit and Debug Test Scripts.
Recording Our First Test
Here is a test scenario we would like to record:
- Open the
MyAddresses.adr
address file. - Navigate to the second address and then add a new name and address.
- Navigate to the fourth address (that was the third address) and change the surname field.
- Navigate to the first address and remove it.
- Verify that the first address is now the new one that was added.
Click the Record () to the right of the tst_general
test case shown in the Test Suites view's Test Cases list. This will cause Squish to run the AUT so that we can interact with it. Once the AUT is running, perform the following actions—and don't worry about how long it takes since Squish doesn't record idle time:
- Click File > Open, and once the file dialog appears, type
MyAddresses.adr
into the line edit, then click the Open button. - Click the second row, then click Edit > Add, then click the first line edit in the Add dialog and type in "Jane". Now click (or tab to) the second line edit and type in "Doe". Continue similarly, to set an email address of "jane.doe@nowhere.com" and a phone number of "555 123 4567". Don't worry about typing mistakes—just backspace delete as normal and fix them. Finally, click the OK button. There should now be a new second address with the details you typed in.
- Click the fourth row's second (surname) column, delete its text and replace it with "Doe". Then press Enter to confirm your edit.
- Now click the first row, then click Edit > Remove, and then click the Yes button in the message box. The first row should be gone, so your "Jane Doe" entry should now be the first one.
- Click the Verify () toolbar button in the Squish Control Bar and select Properties.
This will make the Squish IDE appear. In the Application Objects view, use the Object Picker () or expand the
AddressBook
object, then theJRootPane
object, then theJLayeredPane
object, then theJPanel
object, then theJScrollPane
object, then theJViewport
object, and then theJTable
object. Now expand therow_0
object. Click thecolumn_0
object to make its properties appear in the Properties view, and then check thetext
property's checkbox.At this point, double-check that the Type is set to Scriptified Properties, in case it was set to something else previously.
Next, click the
column_1
object and check its text property.Finally, click the Save and Insert Verifications button in Verification Point Creator to insert the forename and surname verifications for the first row to be inserted into the recorded test script. Once the verification points are inserted, the Squish IDE's window will be hidden again, and the Control Bar window and the AUT will be back in view.
- We've now completed the test, so in the AUT click File > Quit, then click No in the message box, since we don't want to save any changes.
Once we quit the AUT, the recorded test will appear in Squish IDE as the screenshot illustrates. (Note that the exact code that is recorded will vary depending on how you interact. For example, you might invoke menu options by clicking them or by using key sequences—it doesn't matter which you use, but since they are different, Squish will record them differently.)
If the recorded test doesn't appear, click (or double-click depending on your platform and settings) the tst_general
test case; this will make Squish show the test's test.py
file in an editor window as shown in the screenshot.
Now that we've recorded the test, we should be able to play it back. The two verifications we inserted will be checked on playback as the screenshot shows.
After the recording is finished, we can later Insert Additional Verification Points through the use of breakpoints and recording snippets.
Running Tests from IDE
To run a test case in the Squish IDE, click the Run Test Case () that appears when the test case is hovered or selected in the Test Suites view.
To run two or more test cases one after another or to run only the selected test cases, click Run Test Suite ().
Running Tests from Command Line
To playback a recorded test from the command line, we execute the squishrunner
program. We provide squishrunner
the path to a test suite, and optionally also the name of a test case.
A squishserver
must be running when running a test, and we can provide squishrunner
an IP/Port of an already running one, or use the --local
option which creates one for the duration of the process. For more information, see squishserver.
For example, assuming we are in the directory that contains the test suite's directory:
squishrunner --testsuite suite_py --testcase tst_general --local
Examining the Generated Code
If you look at the code in the screenshot (or the code snippet shown below) you will see that it consists of lots of Object waitForObject(objectOrName) calls as parameters to various other calls such as activateItem(objectOrName, itemText), clickButton(objectOrName), mouseClick(objectOrName), and type(objectOrName, text). The Object waitForObject(objectOrName) function waits until a GUI object is ready to be interacted with (i.e., becomes visible and enabled), and is then followed by some function that interacts with the object. The typical interactions are activate (pop-up) a menu, click a menu option or a button, or type in some text.
For a complete overview of Squish's script commands see How to Create Test Scripts, How to Test Applications - Specifics, API Reference, and Tools Reference.
Objects are identified by names that Squish generates. See How to Identify and Access Objects for full details.
The generated code is about 35 lines of code. Here's an extract that just shows how Squish records clicking the Edit menu's Add option, typing in Jane Doe's details into the Add dialog, and clicking OK at the end to close the dialog and update the table.
activateItem(waitForObjectItem(names.address_Book_MyAddresses_adr_JMenuBar, "Edit")) activateItem(waitForObjectItem(names.edit_JMenu, "Add...")) type(waitForObject(names.address_Book_Add_Forename_JTextField), "Jane") type(waitForObject(names.address_Book_Add_Surname_JTextField), "Doe") type(waitForObject(names.address_Book_Add_Email_JTextField), "jane.doe@nowhere.com") type(waitForObject(names.address_Book_Add_Phone_JTextField), "123 555 1212") clickButton(waitForObject(names.address_Book_Add_OK_JButton))
activateItem(waitForObjectItem(names.addressBookMyAddressesAdrJMenuBar, "Edit")); activateItem(waitForObjectItem(names.editJMenu, "Add...")); type(waitForObject(names.addressBookAddForenameJTextField), "Jane"); type(waitForObject(names.addressBookAddSurnameJTextField), "Doe"); type(waitForObject(names.addressBookAddEmailJTextField), "jane.doe@nowhere.com"); type(waitForObject(names.addressBookAddPhoneJTextField), "123 555 1212"); clickButton(waitForObject(names.addressBookAddOKJButton));
activateItem(waitForObjectItem($Names::address_book_myaddresses_adr_jmenubar, "Edit")); activateItem(waitForObjectItem($Names::edit_jmenu, "Add...")); type(waitForObject($Names::address_book_add_forename_jtextfield), "Jane"); type(waitForObject($Names::address_book_add_surname_jtextfield), "Doe"); type(waitForObject($Names::address_book_add_email_jtextfield), "jane.doe\@nowhere.com"); type(waitForObject($Names::address_book_add_phone_jtextfield), "123 555 1212"); clickButton(waitForObject($Names::address_book_add_ok_jbutton));
activateItem(waitForObjectItem(Names::Address_Book_MyAddresses_adr_JMenuBar, "Edit")) activateItem(waitForObjectItem(Names::Edit_JMenu, "Add...")) type(waitForObject(Names::Address_Book_Add_Forename_JTextField), "Jane") type(waitForObject(Names::Address_Book_Add_Surname_JTextField), "Doe") type(waitForObject(Names::Address_Book_Add_Email_JTextField), "jane.doe@nowhere.com") type(waitForObject(Names::Address_Book_Add_Phone_JTextField), "123 555 1212") clickButton(waitForObject(Names::Address_Book_Add_OK_JButton))
invoke activateItem [waitForObjectItem $names::Address_Book_MyAddresses_adr_JMenuBar "Edit"] invoke activateItem [waitForObjectItem $names::Edit_JMenu "Add..."] invoke type [waitForObject $names::Address_Book_Add_Forename_JTextField] "Jane" invoke type [waitForObject $names::Address_Book_Add_Surname_JTextField] "Doe" invoke type [waitForObject $names::Address_Book_Add_Email_JTextField] "jane.doe@nowhere.com" invoke type [waitForObject $names::Address_Book_Add_Phone_JTextField] "123 555 1212" invoke clickButton [waitForObject $names::Address_Book_Add_OK_JButton]
This script has been edited for brevity. Originally, the tester used the keyboard to tab from one text field to another, but we removed those interactions since they were not necessary for playback. If the tester had moved the focus by clicking the mouse and clicked the OK button by tabbing to it and pressing Spacebar, or any other combination of interactions, the outcome would be the same, but of course Squish will have recorded the actual actions that were taken.
Notice in the code snippet that there are no explicit delays. (It is possible to force a delay using Squish's snooze(seconds) function.) This is because the Object waitForObject(objectOrName) function delays until the object it is given is ready—thus allowing Squish to run as fast as the GUI toolkit can cope with, but no faster.
Squish recordings refer to objects using variables that begin with a names.
prefix. These are known as Symbolic Names. Each variable contains, as a value, the corresponding Real Name.
The advantage of using symbolic names (instead of real names) in your scripts, is that if the application changes in a way that results in different names being needed, it is possible to update Squish's Object Map and thereby avoid the need to change our test scripts.
When a Symbolic Name is under the cursor, the editor's context menu allows you to Open Symbolic Name, showing its entry in the Object Map, or Convert to Real Name, which places an inline mapping in your script language at the cursor, allowing you to hand-edit the properties in the script itself.
Now that we have seen how to record and play back a test and have seen the code that Squish generates, let's go a step further and make sure that at particular points in the test's execution certain conditions hold.
Inserting Additional Verification Points
In the previous section we saw how easy it is to insert verification points during the recording of test scripts. Verification points can also be inserted into existing test scripts, by recording snippets, or simply by editing a test script and putting in calls to Squish's test.
functions such as test.compare and test.verify.
Squish supports these kinds of Verification Points:
- Properties VPs, which can be Scriptified or XML-based. They verify that 1 or more properties of 1 or more objects have certain values.
- Table VPs, which verify the contents of an entire table.
- Screenshot VPs, which contain an XML-encoded image for comparison. Image Search, a newer feature of Squish, is preferred over creating VPs of this type.
- Visual VPs, which contain properties and screenshots for an entire tree of objects.
Regular Verification Points are stored as XML files in the test case or test suite resources, and contain the value(s) that need to be compared. These verification points can be reused across test cases, and can verify many values in a single line of script code.
Scriptified Property Verification points are direct calls to the test.compare
function, with two arguments—the value of a particular property for a particular object, and an expected value. We can manually write calls to the test.compare
function in a script, or we can get Squish to insert them for us using Scriptified Property Verification Points.
Before asking Squish to insert verification points, it is best to make sure that we have a list of what we want to verify and when. There are many potential verifications we could add to the tst_general
test case, but since our concern here is simply to show how to do it, we will only do two—we will verify that the "Jane Doe" entry's email address and phone number match the ones entered, and put the verifications immediately after the ones we inserted during recording.
To insert a verification point using the Squish IDE we start by putting a break point in the script (whether recorded or manually written—it does not matter to Squish), at the point where we want to verify.
As the above screenshot shows, we have set a breakpoint at line 36. This is done simply by double-clicking, or right-clicking in the gutter (next to the line number in the editor) and selecting the Add Breakpoint context menu item. We chose this line because it follows the script lines where the first address is removed, so at this point (just before invoking the File menu to close the application), the first address should be that of "Jane Doe". The screenshot shows the verifications that were entered using the Squish IDE during recording. Our additional verifications will follow them. However, your line number may be different if you recorded the test in a different way, for example, using keyboard shortcuts rather than clicking menu items.
Having set the breakpoint, we now run the test as usual by clicking its Run Test Case () button, or by clicking the Run > Run Test Case menu option. Unlike a normal test run the test will stop when the breakpoint is reached (i.e., at line 34, or at whatever line you set), and Squish's main window will reappear (which will probably obscure the AUT). At this point the Squish IDE will automatically switch to the Test Debugging Perspective.
Perspectives and Views
The Squish IDE works just like the Eclipse IDE. If you aren't used to Eclipse, it is crucial to understand the following key concepts: Views and Perspectives. In Eclipse, and therefore in the Squish IDE, a View is essentially a child window, such as a dock window or a tab in an existing window. A Perspective is a collection of views arranged together. Both are accessible through the Window menu.
The Squish IDE is supplied with the following perspectives:
- Test Management Perspective that the Squish IDE starts with, and that is shown in all previous screenshots
- Test Debugging Perspective
- Spy Perspective
You can modify these perspectives to show additional views, to hide views that you don't want, or to create your own perspectives with exactly the views you want.
If you notice all of your Views change dramatically, it just means that the perspective changed. Use the Window menu to change back to the perspective you want. Keep in mind, Squish automatically changes perspectives to reflect the current situation, so you should not need to change perspective manually very often.
Inserting Verification Points
As the screenshot below shows, when Squish stops at a breakpoint the Squish IDE automatically changes to the Test Debugging Perspective. The perspective shows the Variables view, the Editor view, the Debug view, the Application Objects view, and the Properties view, Methods view, and Test Results view.
To insert a verification point we can expand items in the Application Objects view until we find the object we want to verify, or we can use the Object Picker (). In this example we want to verify the JTable
's first row's texts, so we expand the AddressBook
item, and its child items until we find the JTable
, and within that the row and column we are interested in. Once we click the column object its properties are shown in the Properties view as the screenshot shows.
The normal Test Management Perspective can be returned to at any time by choosing it from the Window menu (or by clicking its toolbar button), although the Squish IDE will automatically return to it if you stop the script or run it to completion.
Here, we can see that the text
property of the item in row 0 and column 0 has the value "Jane"; we already have a verification for this that we inserted during recording. Scroll down so that you can see the item in row 0 column 2: this is the email address. To make sure that this is verified every time the test is run, click the column_2
item in the Application Objects view to make its properties appear, and then click the text
property to check its check box. When we check it the Verification Point Creator view appears as shown in the screenshot.
At this point the verification point has not been added to the test script. We could easily add it by clicking the Save and Insert Verifications button. But before doing that we'll add one more thing to be verified.
Scroll down and click the column_3
item in the Application Objects view; then click its text
property. Now both verifications will appear in the Verification Point Creator view as the screenshot shows.
We have now said that we expect these properties to have the values shown, that is, an email address of "jane.doe@nowhere.com" and phone number of "555 123 4567". We must click the Insert button to actually insert the verification point, so do that now.
We don't need to continue running the test now, so we can either stop running the test at this point (by clicking the Stop toolbar button), or we can continue (by clicking the Resume button).
Once we have finished inserting verifications and stopped or finished running the test we should now disable the break point. Just right click the break point and click the Disable Breakpoint menu option in the context menu. We are now ready to run the test without any breakpoints but with the verification points in place. Click the Run Test Case () button. This time we will get some additional test results—as the screenshot shows—one of which we have expanded to show its details. (We have also selected the lines of code that Squish inserted to perform the verifications—notice that the code is structurally identical to the code inserted during recording.)
These particular verification points generate four tests comparing the forename, surname, email, and phone number of the newly inserted entry.
Another way to insert verification points is to insert them in code form. In theory we can just add our own calls to Squish's test functions such as Boolean test.compare(value1, value2) and Boolean test.verify(condition) anywhere we like in an existing script. In practice it is best to make sure that Squish knows about the objects we want to verify first so that it can find them when the test is run. This involves a very similar procedure to inserting them using the Squish IDE. First we set a breakpoint where we intend adding our verifications. Then we run the test script until it stops. Next we navigate in the Application Objects view until we find the object we want to verify. At this point it is wise to right-click the object we are interested in and click the Add to Object Map context menu option. This will ensure that Squish can access the object. Then right click again and click the Copy Symbolic Name context menu option—this gives us the name of the object that Squish will use to identify it. Now we can edit the test script to add in our own verification and finish or stop the execution. (Don't forget to disable the break point once it isn't needed any more.)
Although we can write our test script code to be exactly the same style as the automatically generated code, it is usually clearer and easier to do things in a slightly different style, as we will explain in a moment.
For our manual verifications we want to check the number of addresses present in the JTable
after reading in the MyAddresses.adr
file, then after the new address is added, and finally after the first address is removed. The screenshot shows two of the lines of code we entered to get one of these three verifications, plus the results of running the test script.
When writing scripts by hand, we use Squish's test
module's functions to compare or verify conditions at certain points during our test script's execution. As the screenshot (and the code snippets below) show, we begin by retrieving a reference to the object we are interested in. Using the Object waitForObject(objectOrName) function is standard practice for manually written test scripts. This function waits for the object to be available (i.e., visible and enabled), and then returns a reference to it. (Otherwise it times out and raises a catchable exception.) We then use this reference to access the item's properties—in this case the JTable
's row count—and verify that the value is what we expect it to be using the Boolean test.verify(condition) function. (Incidentally, we got the name for the object from the previous line so we didn't need to set a breakpoint and manually add the table's name to the Object Map to ensure that Squish would remember it in this particular case because Squish had already added it during the test recording.)
Here is the code we entered manually for the first verification for all the scripting languages that Squish supports. Naturally, you only need to look at the code for the language that you will be using for your own tests.
activateItem(waitForObjectItem(names.address_Book_MyAddresses_adr_JMenuBar, "Edit")) activateItem(waitForObjectItem(names.edit_JMenu, "Add...")) type(waitForObject(names.address_Book_Add_Forename_JTextField), "Jane") type(waitForObject(names.address_Book_Add_Surname_JTextField), "Doe") type(waitForObject(names.address_Book_Add_Email_JTextField), "jane.doe@nowhere.com") type(waitForObject(names.address_Book_Add_Phone_JTextField), "123 555 1212") clickButton(waitForObject(names.address_Book_Add_OK_JButton)) mouseClick(waitForObjectItem(names.address_Book_MyAddresses_adr_JTable, "3/1"), 54, 4, 0, Button.Button1) mouseClick(waitForObjectItem(names.address_Book_MyAddresses_adr_JTable, "3/1"), 54, 3, 0, Button.Button1) doubleClick(waitForObjectItem(names.address_Book_MyAddresses_adr_JTable, "3/1"), 54, 3, 0, Button.Button1) type(waitForObject(names.o_JTextField), "<Backspace>") type(waitForObject(names.o_JTextField), "<Backspace>") type(waitForObject(names.o_JTextField), "<Backspace>") type(waitForObject(names.o_JTextField), "<Backspace>") type(waitForObject(names.o_JTextField), "<Backspace>") type(waitForObject(names.o_JTextField), "Doe") type(waitForObject(names.o_JTextField), "<Return>") mouseClick(waitForObjectItem(names.address_Book_MyAddresses_adr_JTable, "0/0"), 135, 8, 0, Button.Button1) test.compare(table.getRowCount(), 126)
mouseClick(waitForObjectItem(names.addressBookMyAddressesAdrJTable, "1/0"), 124, 8, 0, Button.Button1); activateItem(waitForObjectItem(names.addressBookMyAddressesAdrJMenuBar, "Edit")); activateItem(waitForObjectItem(names.editJMenu, "Add...")); type(waitForObject(names.addressBookAddForenameJTextField), "Jane"); type(waitForObject(names.addressBookAddSurnameJTextField), "Doe"); type(waitForObject(names.addressBookAddEmailJTextField), "jane.doe@nowhere.com"); type(waitForObject(names.addressBookAddPhoneJTextField), "123 555 1212"); clickButton(waitForObject(names.addressBookAddOKJButton)); test.compare(table.getRowCount(), 126);
mouseClick(waitForObjectItem($Names::address_book_myaddresses_adr_jtable, "2/1"), 33, 7, 0, Button->Button1); mouseClick(waitForObjectItem($Names::address_book_myaddresses_adr_jtable, "2/1"), 33, 7, 0, Button->Button1); doubleClick(waitForObjectItem($Names::address_book_myaddresses_adr_jtable, "2/1"), 33, 7, 0, Button->Button1); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "<Backspace>"); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "<Backspace>"); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "<Backspace>"); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "<Backspace>"); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "<Backspace>"); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "Doe"); type(waitForObject($Names::address_book_myaddresses_adr_jtextfield), "<Return>"); activateItem(waitForObjectItem($Names::address_book_myaddresses_adr_jmenubar, "Edit")); activateItem(waitForObjectItem($Names::edit_jmenu, "Add...")); type(waitForObject($Names::address_book_add_forename_jtextfield), "Jane"); type(waitForObject($Names::address_book_add_surname_jtextfield), "Doe"); type(waitForObject($Names::address_book_add_email_jtextfield), "jane.doe\@nowhere.com"); type(waitForObject($Names::address_book_add_phone_jtextfield), "123 555 1212"); clickButton(waitForObject($Names::address_book_add_ok_jbutton)); test::compare($table->getRowCount(), 126);
activateItem(waitForObjectItem(Names::Address_Book_MyAddresses_adr_JMenuBar, "Edit")) activateItem(waitForObjectItem(Names::Edit_JMenu, "Add...")) type(waitForObject(Names::Address_Book_Add_Forename_JTextField), "Jane") type(waitForObject(Names::Address_Book_Add_Surname_JTextField), "Doe") type(waitForObject(Names::Address_Book_Add_Email_JTextField), "jane.doe@nowhere.com") type(waitForObject(Names::Address_Book_Add_Phone_JTextField), "123 555 1212") clickButton(waitForObject(Names::Address_Book_Add_OK_JButton)) Test.compare(table.rowcount, 126)
invoke mouseClick [waitForObjectItem $names::Address_Book_MyAddresses_adr_JTable "1/0"] 55 10 0 [enum Button Button1] invoke activateItem [waitForObjectItem $names::Address_Book_MyAddresses_adr_JMenuBar "Edit"] invoke activateItem [waitForObjectItem $names::Edit_JMenu "Add..."] invoke type [waitForObject $names::Address_Book_Add_Forename_JTextField] "Jane" invoke type [waitForObject $names::Address_Book_Add_Surname_JTextField] "Doe" invoke type [waitForObject $names::Address_Book_Add_Email_JTextField] "jane.doe@nowhere.com" invoke type [waitForObject $names::Address_Book_Add_Phone_JTextField] "123 555 1212" invoke clickButton [waitForObject $names::Address_Book_Add_OK_JButton] test compare [invoke $table getRowCount] 126
The coding pattern is very simple: we retrieve a reference to the object we are interested in and then verify its properties using one of Squish's verification functions. And we can, of course, call methods on the object to interact with it if we wish.
For more examples of manually written code, see Creating Tests by Hand, How to Create Test Scripts, and How to Test Applications - Specifics.
For complete coverage of verification points, see How to Create and Use Verification Points.
Test Results
After each test run finishes, the test results—including those for the verification points—are shown in the Test Results view at the bottom of the Squish IDE.
This is a detailed report of the test run and would also contain details of any failures or errors, etc. If you click on a Test Results item, the Squish IDE highlights the script line which generated the test result. And if you expand a Test Results item, you can see additional details of the test.
Squish's interface for test results is very flexible. By implementing custom report generators it is possible to process test results in many different ways, for example to store them in a database, or to output them as HTML files. The default report generator simply prints the results to stdout
when Squish is run from the command line, or to the Test Results view when Squish IDE is being used. You can save the test results from the Squish IDE as XML by right clicking on the Test Results and choosing the Export Results menu option. For a list of report generators, see squishrunner –reportgen: Generating Reports. It is also possible to log test results directly to a database. See How to Access Databases from Squish Test Scripts.
If you run tests on the command line using squishrunner, you can also export the results in different formats and save them to files. For more information, see Processing Test Results and How to Use Test Statements.
Creating Tests by Hand
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 Launch AUT toolbar button. This starts the AUT and switches to the Spy Perspective. 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, or pick the object so it is selected in the Application Objects view and use the context menu to both Add to Object Map and Copy (Symbolic | Real) Name to Clipboard (so that we can paste it into our test script). And at the end we can click the Quit AUT toolbar button to terminate the AUT and return Squish to the Test Management Perspective. See How to Use the Spy for more details on using the Spy.
We can view the Object Map by clicking the Object Map toolbar button (in the Test Suites window), or from the Script Editor context menu, Open Symbolic Name when right-clicking on an object name in script (see also, the Object Map view). 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 Object Name (to get the symbolic name variable) or Copy Real Name (to get the actual key-value pairs stored in the variable). 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.
Modifying and Refactoring Recorded Tests
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 test case. Click File > New Test Case and set the test case's name to be tst_adding
. Squish automatically creates a 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:
import names import os def main(): startApplication('"' + os.environ["SQUISH_PREFIX"] + '/examples/java/addressbook/AddressBookSwing.jar"') activateItem(waitForObjectItem(names.address_Book_JMenuBar, "File")) activateItem(waitForObjectItem(names.file_JMenu, "Open..."))
import * as names from 'names.js'; function main() { startApplication('"' + OS.getenv("SQUISH_PREFIX") + '/examples/java/addressbook/AddressBookSwing.jar"'); activateItem(waitForObjectItem(names.addressBookJMenuBar, "File")); activateItem(waitForObjectItem(names.fileJMenu, "Open..."));
require 'names.pl'; sub main { startApplication("\"$ENV{'SQUISH_PREFIX'}/examples/java/addressbook/AddressBookSwing.jar\""); activateItem(waitForObjectItem($Names::address_book_jmenubar, "File")); activateItem(waitForObjectItem($Names::file_jmenu, "Open..."));
require 'squish' require 'names' include Squish def main startApplication("\"#{ENV['SQUISH_PREFIX']}/examples/java/addressbook/AddressBookSwing.jar\"") activateItem(waitForObjectItem(Names::Address_Book_JMenuBar, "File")) activateItem(waitForObjectItem(Names::File_JMenu, "Open..."))
source [findFile "scripts" "names.tcl"] proc main {} { startApplication "\"$::env(SQUISH_PREFIX)/examples/java/addressbook/AddressBookSwing.jar\"" invoke activateItem [waitForObjectItem $names::Address_Book_JMenuBar "File"] invoke activateItem [waitForObjectItem $names::File_JMenu "Open..."]
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 Object waitForObjectItem(objectOrName, itemOrIndex) 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 tst_adding
because we could also use them in tst_general
and in other test cases. However, to keep the tutorial simple we will put the code in the tst_adding
test case. See How to Create and Use Shared Data and Shared Scripts for how to share scripts.
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_JMenuBar", and later on as ":Address Book - MyAddresses.adr_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.
Object Not Found Dialog
If the AUT appears to freeze during test execution, wait for Squish to time out the AUT (about 20 seconds), and show the Object Not Found dialog, indicating an error like this:
This usually means that Squish doesn't have an object with the given name, or property values, in the Object Map. From here, we can Pick a new object, Debug, Throw Error or, after picking a new object, Retry.
Picking a new object will update the object map entry for the symbolic name. In addition to the Object Picker (), we can use the Spy's Application Objects view to locate the objects we are interested in and use the Add to the Object Map context menu action to to access their real or symbolic names.
Naming is important because 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 to access in our tests, writing test scripts using Squish is very straightforward. Especially, as Squish most likely supports the scripting language you are most familiar with.
We are now almost ready to write our own test script. It is probably easiest to begin by recording a dummy test. So click File > New Test Case and set the test case's name to be tst_dummy
. Then click the dummy test case's Record (). Once the AUT starts, click File > New, then click the (empty) table, then click Edit > Add and add an item, then press Return or click OK. Finally, click File > Quit to finish, and say No to saving changes. Then replay this test just to confirm that everything works okay. The sole purpose of this is to make sure that Squish adds the necessary names to the Object Map since it is probably quicker to do it this way than to use the Spy for every object of interest. After replaying the dummy test you can delete it if you want to.
With all the object names we need in the Object Map, we can now 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.
import names import os def main(): startApplication('"' + os.environ["SQUISH_PREFIX"] + '/examples/java/addressbook/AddressBookSwing.jar"') invokeMenuItem("File", "New...") jtable = waitForObject({"type": "javax.swing.JTable"}) test.verify(jtable.rowcount == 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.compare(jtable.rowcount, len(data)) closeWithoutSaving()
import * as names from 'names.js'; function main() { startApplication('"' + OS.getenv("SQUISH_PREFIX") + '/examples/java/addressbook/AddressBookSwing.jar"'); invokeMenuItem("File", "New..."); var jtable = waitForObject({"type": "javax.swing.JTable"}); 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.compare(jtable.getRowCount(), data.length); closeWithoutSaving(); }
require 'names.pl'; sub main { startApplication("\"$ENV{'SQUISH_PREFIX'}/examples/java/addressbook/AddressBookSwing.jar\""); invokeMenuItem("File", "New..."); my $jtable = waitForObject({"type" => "javax.swing.JTable"}); 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 my $details (@data) { addNameAndAddress(@{$details}); } test::compare($jtable->getRowCount(), scalar(@data)); closeWithoutSaving(); }
require 'squish' require 'names' include Squish def main startApplication("\"#{ENV['SQUISH_PREFIX']}/examples/java/addressbook/AddressBookSwing.jar\"") invokeMenuItem("File", "New...") jtable = waitForObject({:type => "javax.swing.JTable"}) Test.verify(jtable.rowcount == 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"]] data.each do |oneNameAndAddress| addNameAndAddress(oneNameAndAddress) end Test.compare(jtable.rowcount, data.length) closeWithoutSaving end
source [findFile "scripts" "names.tcl"] proc main {} { startApplication "\"$::env(SQUISH_PREFIX)/examples/java/addressbook/AddressBookSwing.jar\"" invokeMenuItem "File" "New..." set jtable [waitForObject $names::Address_Book_Unnamed_JTable] test verify [expr {[property get $jtable rowcount] == 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 [property get $jtable rowcount] [llength $data] closeWithoutSaving }
We begin by starting the application with a call to the ApplicationContext startApplication(autName) function. The name we pass as a string is the name registered with Squish (normally the name of the .jar
file containing the application code or the .class
file that has the class which contains the main
method). Then we obtain a reference to the JTable
. The object name we want is not yet in the Object Map, and the existing object name for the JTable
from tst_general is too specific, and will not work at this point in a test. Therefore, we can take an existing symbolic name which is for the same type, right click on it from the editor and Convert to Real Name, removing the properties that are not relevant or too specific for the function.
The Object waitForObject(objectOrName) 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.
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 Boolean test.verify(condition) 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 Boolean test.compare(value1, value2) function rather than the Boolean test.verify(condition) 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): mouseClick(waitForObjectItem({"type": "javax.swing.JMenuBar"}, menu)) mouseClick(waitForObjectItem({"caption": menu, "type": "javax.swing.JMenu"}, item))
function invokeMenuItem(menu, item) { activateItem(waitForObjectItem({"type": "javax.swing.JMenuBar"}, menu)); activateItem(waitForObjectItem({"caption": menu, "type": "javax.swing.JMenu"}, item)); }
sub invokeMenuItem { my($menu, $item) = @_; activateItem(waitForObjectItem({"type" => "javax.swing.JMenuBar"}, $menu)); activateItem(waitForObjectItem({"caption" => $menu, "type" => "javax.swing.JMenu"}, $item)); }
def invokeMenuItem(menu, item) activateItem(waitForObjectItem({:type => "javax.swing.JMenuBar"}, menu)) activateItem(waitForObjectItem({:caption => menu, :type => "javax.swing.JMenu"}, item)) end
proc invokeMenuItem {menu item} { invoke activateItem [waitForObjectItem [::Squish::ObjectName type javax.swing.JMenuBar visible true] $menu] invoke activateItem [waitForObjectItem [::Squish::ObjectName type javax.swing.JMenu visible true caption $menu] $item] }
As we mentioned earlier, the object names Squish uses for menus and menu items (and other objects) can vary depending on the context, and often with 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.
Real names are represented by a key-value map in your script language. Every real name must specify the type property and at least one other property. Here we've used the type 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 Object waitForObjectItem(objectOrName, itemOrIndex) function to retrieve a reference to it and in this case we then apply the activateItem(objectOrName, itemText) function to it. The Object waitForObjectItem(objectOrName, itemOrIndex) 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(objectOrName, itemText) function.
def addNameAndAddress(fields): invokeMenuItem("Edit", "Add...") type(waitForObject(names.address_Book_Add_Forename_JTextField), fields[0]) type(waitForObject(names.address_Book_Add_Surname_JTextField), fields[1]) type(waitForObject(names.address_Book_Add_Email_JTextField), fields[2]) type(waitForObject(names.address_Book_Add_Phone_JTextField), fields[3]) clickButton(waitForObject(names.address_Book_Add_OK_JButton))
function addNameAndAddress(fields) { invokeMenuItem("Edit", "Add..."); type(waitForObject(names.addressBookAddForenameJTextField), fields[0]); type(waitForObject(names.addressBookAddSurnameJTextField), fields[1]); type(waitForObject(names.addressBookAddEmailJTextField), fields[2]); type(waitForObject(names.addressBookAddPhoneJTextField), fields[3]); clickButton(waitForObject(names.addressBookAddOKJButton)); }
sub addNameAndAddress { invokeMenuItem("Edit", "Add..."); type(waitForObject($Names::address_book_add_forename_jtextfield), $_[0]); type(waitForObject($Names::address_book_add_surname_jtextfield), $_[1]); type(waitForObject($Names::address_book_add_email_jtextfield), $_[2]); type(waitForObject($Names::address_book_add_phone_jtextfield), $_[3]); clickButton(waitForObject($Names::address_book_add_ok_jbutton)); }
def addNameAndAddress(oneNameAndAddress) invokeMenuItem("Edit", "Add...") type(waitForObject(Names::Address_Book_Add_Forename_JTextField), oneNameAndAddress[0]) type(waitForObject(Names::Address_Book_Add_Surname_JTextField), oneNameAndAddress[1]) type(waitForObject(Names::Address_Book_Add_Email_JTextField), oneNameAndAddress[2]) type(waitForObject(Names::Address_Book_Add_Phone_JTextField), oneNameAndAddress[3]) clickButton(waitForObject(Names::Address_Book_Add_OK_JButton)) end
proc addNameAndAddress {fields} { invokeMenuItem "Edit" "Add..." invoke type [waitForObject $names::Address_Book_Add_Forename_JTextField] [lindex $fields 0] invoke type [waitForObject $names::Address_Book_Add_Surname_JTextField] [lindex $fields 1] invoke type [waitForObject $names::Address_Book_Add_Email_JTextField] [lindex $fields 2] invoke type [waitForObject $names::Address_Book_Add_Phone_JTextField] [lindex $fields 3] invoke clickButton [waitForObject $names::Address_Book_Add_OK_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(objectOrName, text) 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 parametrizing it by the 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(names.address_Book_No_JButton))
function closeWithoutSaving() { invokeMenuItem("File", "Quit"); clickButton(waitForObject(names.addressBookNoJButton)); }
sub closeWithoutSaving { invokeMenuItem("File", "Quit"); clickButton(waitForObject($Names::address_book_no_jbutton)); }
def closeWithoutSaving invokeMenuItem("File", "Quit") clickButton(waitForObject(Names::Address_Book_No_JButton)) end
proc closeWithoutSaving {} { invokeMenuItem "File" "Quit" invoke clickButton [waitForObject $names::Address_Book_No_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 parametrized.
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 and the Tools Reference). 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.
Creating Data Driven Tests
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), .xls
, or .xlsx
(Microsoft Excel spreadsheet formats).
Note: Both .csv
and .tsv
files are assumed to use the Unicode UTF-8 encoding, which is used for all test scripts.
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 File > Import Test Resource to pop-up the Import Squish Resource dialog. Inside the dialog click the Browse button 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 Copy to Test Suite for Sharing radio button. Now click the Finish 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. The screenshot shows Squish after the test data has been added.
To import test data from outside the Squish IDE, use a file manager, such as File Explorer or Finder, or console commands. Create a directory called shared
inside the test suite's directory. Then, create a directory called testdata
inside the shared
directory. Copy the data file (in this example, MyAddresses.tsv
) into the shared\testdata
directory.
Restart the Squish IDE if it is running. If you click the Test Suite Resources view's Test Data tab, you should see the data file. Click the file name to see the file in an Editor view.
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.
import names import os def main(): startApplication('"' + os.environ["SQUISH_PREFIX"] + '/examples/java/addressbook/AddressBookSwing.jar"') jtable = waitForObject({"type": "javax.swing.JTable"}) invokeMenuItem("File", "New...") test.verify(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()
import * as names from 'names.js'; function main() { startApplication('"' + OS.getenv("SQUISH_PREFIX") + '/examples/java/addressbook/AddressBookSwing.jar"'); invokeMenuItem("File", "New..."); var jtable = waitForObject({"type": "javax.swing.JTable"}); test.verify(jtable.rowcount == 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.rowcount, row + 1); closeWithoutSaving(); }
require 'names.pl'; sub main { startApplication("\"$ENV{'SQUISH_PREFIX'}/examples/java/addressbook/AddressBookSwing.jar\""); invokeMenuItem("File", "New..."); my $jtable = waitForObject({"type" => "javax.swing.JTable"}); test::verify($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(); }
require 'squish' require 'names' include Squish def main startApplication("\"#{ENV['SQUISH_PREFIX']}/examples/java/addressbook/AddressBookSwing.jar\"") jtable = waitForObject({:type => "javax.swing.JTable"}) invokeMenuItem("File", "New...") Test.verify(jtable.rowcount == 0) limit = 10 # To avoid testing 100s of rows since that would be boring rows = 0 TestData.dataset("MyAddresses.tsv").each_with_index do |record, row| 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 Array checkNameAndAddress(jtable, record) break if row > limit rows += 1 end Test.compare(jtable.rowcount, rows + 1) closeWithoutSaving end
source [findFile "scripts" "names.tcl"] proc main {} { startApplication "\"$::env(SQUISH_PREFIX)/examples/java/addressbook/AddressBookSwing.jar\"" invokeMenuItem "File" "New..." set jtable [waitForObject [::Squish::ObjectName type javax.swing.JTable ]] test verify [expr {[property get $jtable rowcount] == 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 [property get $jtable rowcount] [expr $row + 1] closeWithoutSaving }
Squish provides access to test data through its testData
module's functions—here we used the Dataset testData.dataset(filename) function to access the data file and make its records available, and the String testData.field(record, fieldName) function to retrieve each record's individual fields.
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.
def checkNameAndAddress(jtable, record): 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) { 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) = @_; 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)); } }
def checkNameAndAddress(table, record) model = table.getModel() for column in 0...TestData.fieldNames(record).length value = model.getValueAt(0, column) Test.compare(value, TestData.field(record, column)) end end
proc checkNameAndAddress {jtable record} { 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 function to check that the value in the cell is the same as the value in the test data we used. 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.
Squish can also do keyword-driven testing. This is a bit more sophisticated than data-driven testing. See How to Do Keyword-Driven Testing.
Learning More
We have now completed the tutorial. Squish can do much more than we have shown here, but the aim has been to get you started with basic testing as quickly and easily as possible. The How to Create Test Scripts, and How to Test Applications - Specifics sections provide many more examples, including those that show how tests can interact with particular input elements, such as selects, select-ones, texts, and text-areas.
The API Reference and Tools Reference give full details of Squish's testing API and the numerous functions it offers to make testing as easy and efficient as possible. It is well worth reading the How to Create Test Scripts and How to Test Applications - Specifics, as well as browsing the API Reference and Tools Reference. The time you invested will be repaid because you'll know what functionality Squish provides out of the box and can avoid reinventing things that are already available.
The key Java AWT/Swing examples with links to the places they are used are given below.
- The Payment Form example (How to Test Stateful and Single-Valued Widgets (—AWT/Swing)) shows how to test single-valued Java widgets such as:
JButton
,JCheckBox
,JComboBox
,JSpinner
, andJTextField
- The Item Views example(How to Test JList, JTable, and JTree widgets (Java—AWT/Swing)) shows how to test Java's multi-item widgets, including:
JList
,JTable
, andJTree
, and also the models that hold their data. - The CsvTable example (How to Test JTable and Use External Data Files (Java—AWT/Swing)) shows how to do data-driven testing using external data files.
In addition to the documented examples listed above, further Java AWT/Swing example applications and their corresponding tests are provided in <SQUISHDIR>/examples/java
.
For more comprehensive examples that show how to test lists, tables, and trees, and also the most common widgets, including spinners, see How to Test Applications.
© 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.