4.3. Recording Tests and Verification Points

Squish records tests using the scripting language that was specified for the test suite, rather than using a proprietary language. Once a test has been recorded we can run the test and Squish will faithfully repeat all the actions that we performed when recording the test, but without all the pauses that humans are prone to but which computers don't need. It is also possible—and very common—to edit recorded tests, or to copy parts of recorded tests into manually created tests, as we will see later on in the tutorial.

Recordings are always made into existing tests, so we must begin by creating a new "empty" test. There are two ways we can do this. One way is to click File|New Test Case.... This will pop up the New Squish Test Case wizard (Section 16.3.6)—simply enter the name for the test case and then click Finish. Another way is to click the New Test Case toolbar button (to the right of the "Test Cases" label in the Test Suites view); this will create a new test case with a default name (which you can easily change). Use one of these methods and 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, in this case test.py. (If we had chosen JavaScript as our scripting language the file would be called test.js, and correspondingly for Perl or Tcl.)

The Squish IDE with the tst_general test case

To make the test script file (e.g., test.py) appear in an Editor view (Section 16.2.6), click—or double-click depending on the Window|Preferences|General|Open mode setting—the test case. (Incidentally, the checkboxes are used to control which test cases are run when the Run Test Suite toolbar button is clicked; we can always run a single test case by clicking its Run Test button.) Initially, the script is empty. If we were to create a test manually (as we will do later on in the tutorial), we must create a main function. The name "main" is special to Squish—tests may contain as many functions and other code as we like (providing it is legal for the scripting language), but when the test is executed (i.e., run), Squish always executes the main function. This is actually very convenient since it means we are free to create other functions, import libraries, and so on, without problems. It is also possible to share commonly used code between test scripts—this is covered in the User Guide (Chapter 13). (In fact, two other function names are special to Squish, cleanup and init; see Tester-Created Special Functions (Section 14.1) for details.)

Once the new empty test case has been created we are now free to write test code manually, or to record a test. If we choose to record we can either replace all the test's code with the recorded code, or insert recorded code into the middle of some existing test code. We will only concern ourselves with recording and replacing in the tutorial.

[Note]For command-line users

Creating a new test case from the command line is an easy two-step process: first, create a test case directory; and second, create an empty test case script.

  1. Create a new subdirectory inside the test suite directory. For example, inside the squish/examples/qt4/addressbook/suite_py directory, create the tst_general directory.

  2. Inside the test case's directory create an empty file called test.py (or test.js if you are using the JavaScript scripting language, and similarly for the other languages).

Before we dive into recording let's briefly review our very simple (and far from thorough) test plan:

  1. Open the MyAddresses.adr address file.

  2. Navigate to the second address and then add a new name and address.

  3. Navigate to the fourth address (that was the third address) and change the surname field.

  4. Navigate to the first address and remove it.

  5. Verify that the first address is now the new one that was added.

We are now ready to record our first test. Click the Record Test Case toolbar button () that's to the right of the tst_general test case shown in the Test Suites view (Section 16.2.16)'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:

  1. Click File|Open, and once the file dialog appears, click the MyAddresses.adr filename, then click the Open button.

  2. Click the second row, then click Edit|Add..., then in the Add dialog's first line edit 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.

  3. Click the fourth row's second (surname) column, delete its text and replace it with "Doe". (You can do this simply by overtyping.) Then press Enter to confirm your edit.

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

  5. Click the Insert Object Properties Verification Point toolbar button in the Squish Control Bar (the second button from the left). This will make the Squish IDE appear. In the Application Objects view expand the MainWindow object, then the QTableWidget object. Click the “item_0/0QModelIndex object to make its properties appear in the Properties view (Section 16.2.11), and then check the text property's checkbox. Now click the “item_0/1QModelIndex and check its text property. Finally, click the Insert button (at the top-right of the Verification Point Creator view (Section 16.2.19)) to have the forename and surname verifications for the first row inserted into the recorded test script. (See the screenshot below.) 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.

  6. We've now completed the test plan, so in the AUT click File|Quit, then click No in the message box, since we don't want to save any changes.

The Squish IDE showing two verification points about to be inserted

Once we quit the AUT, the recorded test will appear in Squish's 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.)

The Squish IDE showing the recorded tst_general test

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 are able to play it back, i.e., run it. This in itself is useful in that if the play back failed it might mean that the application has been broken. Furthermore, the two verifications we put in will be checked on play back as the screenshot shows.

Inserting verification points during test recording is very convenient. Here we inserted two in one go, but we can insert as many as we like as often as we like during the test recording process. However, sometimes we might forget to insert a verification, or later on we might want to insert a new verification. We can easily insert additional verifications into a recorded test script as we will see in the next section, Inserting Additional Verification Points (Section 4.4).

Before going further we will look at how to record a test from the command line. Then we will see how to run a test, and we will also look at some of the code that Squish generated to record the test and discuss some of its features.

[Note]For command-line users

First and foremost, the squishserver must always be running when recording or running a test. This is handled automatically by the Squish IDE, but for command line users the squishserver must be started manually. (See squishserver (Section 15.4.2) for further details.)

To record a test from the command line we execute the squishrunner program and specify the test suite we want to record inside and the name we want to give to the test case. For example (assuming we are in the directory that contains the test suite's directory):

squishrunner --testsuite suite_py --record tst_general --useWaitFor

It is always best to record using the --useWaitFor option since this records calls to the waitForObject function which is more reliable than using the snooze function which for historical reasons is the default. (Note that the Squish IDE automatically uses the waitForObject function.)

To run a test case in the IDE just click the Run Test Case toolbar button (the green right-pointing triangle that appears when the test case is selected in the Test Suites view (Section 16.2.16)). This will cause Squish to run the AUT and replay every action (omitting human idle time, but allowing just enough time for the GUI toolkit to keep up). It is worth trying out since it has quite an impressive effect, especially if you haven't seen it in action before.

When we have two or more test cases we can run them individually by clicking the test case we want to run to select it and then clicking the Run Test button, or we can run them all (one after the other) by clicking the Run Test Suite toolbar button (which is above and slightly to the left of the Run Test button. (Actually, only those test cases that are checked are run by clicking the Run Test Suite toolbar button, so we can easily run a particular group of tests.)

[Note]For command-line users

As noted earlier, the squishserver must always be running when recording or running a test. (See squishserver (Section 15.4.2) for further details.)

To play back a recorded test from the command line we execute the squishrunner program and specify the test suite our recorded script is in and the test case we want to play. For example (assuming we are in the directory that contains the test suite's directory):

squishrunner --testsuite suite_py --testcase tst_general

If you look at the code in the screenshot (or the code snippet shown below) you will see that it consists of lots of waitForObject calls as parameters to various other calls such as activateItem, clickButton, clickItem, and type. The waitForObject 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 the User Guide (Chapter 13), the API Reference Manual (Chapter 14), and the Tools Reference Manual (Chapter 15). Objects are identified by names that Squish generates. (See How to Identify and Access Objects (Section 13.1) 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.

[Note]Scripting Language Support

Although the screenshots only show the Python test suite in action, for the code snippets quoted here and throughout the tutorial, we show the code for all the scripting languages that Squish supports. In practice you would normally only use one of them of course, so feel free to just look at the snippets in the language you are interested in and skip the others. (In the HTML version of this manual you can use the combobox at the top of the page to select the language you use—this will hide the code snippets in other languages.)

Python

    activateItem(waitForObjectItem(":Address Book - MyAddresses.adr_QMenuBar", "Edit"))
    activateItem(waitForObjectItem(":Address Book - MyAddresses.adr.Edit_QMenu", "Add..."))
    type(waitForObject(":Forename:_LineEdit"), "Jane")
    type(waitForObject(":Forename:_LineEdit"), "<Tab>")
    type(waitForObject(":Surname:_LineEdit"), "Doe")
    type(waitForObject(":Surname:_LineEdit"), "<Tab>")
    type(waitForObject(":Email:_LineEdit"), "jane.doe@nowhere.com")
    type(waitForObject(":Email:_LineEdit"), "<Tab>")
    type(waitForObject(":Phone:_LineEdit"), "555 123 4567")
    clickButton(waitForObject(":Address Book - Add.OK_QPushButton"));

JavaScript

    activateItem(waitForObjectItem(":Address Book - MyAddresses.adr_QMenuBar", "Edit"));
    activateItem(waitForObjectItem(":Address Book - MyAddresses.adr.Edit_QMenu", "Add..."));
    type(waitForObject(":Forename:_LineEdit"), "Jane");
    type(waitForObject(":Forename:_LineEdit"), "<Tab>");
    type(waitForObject(":Surname:_LineEdit"), "Doe");
    type(waitForObject(":Surname:_LineEdit"), "<Tab>");
    type(waitForObject(":Email:_LineEdit"), "jane.doe@nowhere.com");
    type(waitForObject(":Email:_LineEdit"), "<Tab>");
    type(waitForObject(":Phone:_LineEdit"), "555 123 4567");
    clickButton(waitForObject(":Address Book - Add.OK_QPushButton"));

Perl

    activateItem(waitForObjectItem(":Address Book - MyAddresses.adr_QMenuBar", "Edit"));
    activateItem(waitForObjectItem(":Address Book - MyAddresses.adr.Edit_QMenu", "Add..."));
    type(waitForObject(":Forename:_LineEdit"), "Jane");
    type(waitForObject(":Forename:_LineEdit"), "<Tab>");
    type(waitForObject(":Surname:_LineEdit"), "Doe");
    type(waitForObject(":Surname:_LineEdit"), "<Tab>");
    type(waitForObject(":Email:_LineEdit"), "jane.doe\@nowhere.com");
    type(waitForObject(":Email:_LineEdit"), "<Tab>");
    type(waitForObject(":Phone:_LineEdit"), "555 123 4567");
    clickButton(waitForObject(":Address Book - Add.OK_QPushButton"));

Ruby

  activateItem(waitForObjectItem(":Address Book - MyAddresses.adr_QMenuBar", "Edit"))
  activateItem(waitForObjectItem(":Address Book - MyAddresses.adr.Edit_QMenu", "Add..."))
  type(waitForObject(":Forename:_LineEdit"), "Jane")
  type(waitForObject(":Forename:_LineEdit"), "<Tab>")
  type(waitForObject(":Surname:_LineEdit"), "Doe")
  type(waitForObject(":Surname:_LineEdit"), "<Tab>")
  type(waitForObject(":Email:_LineEdit"), "jane.doe@nowhere.com")
  type(waitForObject(":Email:_LineEdit"), "<Tab>")
  type(waitForObject(":Phone:_LineEdit"), "555 123 4567")
  clickButton(waitForObject(":Address Book - Add.OK_QPushButton"))

Tcl

    invoke activateItem [waitForObjectItem ":Address Book - MyAddresses.adr_QMenuBar" "Edit"]
    invoke activateItem [waitForObjectItem ":Address Book - MyAddresses.adr.Edit_QMenu" "Add..."]
    invoke type [waitForObject ":Forename:_LineEdit"] "Jane" 
    invoke type [waitForObject ":Forename:_LineEdit"] "<Tab>" 
    invoke type [waitForObject ":Surname:_LineEdit"] "Doe" 
    invoke type [waitForObject ":Surname:_LineEdit"] "<Tab>" 
    invoke type [waitForObject ":Email:_LineEdit"] "jane.doe@nowhere.com" 
    invoke type [waitForObject ":Email:_LineEdit"] "<Tab>" 
    invoke type [waitForObject ":Phone:_LineEdit"] "555 123 4567" 
    invoke clickButton [waitForObject ":Address Book - Add.OK_QPushButton"]

As you can see the tester used the keyboard to tab from one text field to another and clicked the OK button using the mouse rather than with a key press. If the tester had clicked the button any other way (for example, by pressing Alt+O, or by tabbing to the OK button and pressing the spacebar), the outcome would be the same, but of course Squish will have recorded the actual actions that were taken.

[Note]Lookup Efficiency

In the code snippet it looks as though Squish is having to look up the same objects by name time after time. In fact Squish cache's names, so after a name has been looked up once, subsequent lookups are very fast. Another thing to notice is that there are no explicit delays. (It is possible to force a delay using Squish's snooze function.) This is because the waitForObject 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.

Another point to notice is that all the object names begin with a colon. This identifies them as symbolic names. Squish supports several naming schemes, all of which can be used—and mixed—in scripts. The advantage of using symbolic names is that if the application changes in a way that results in different names being generated, we can simply update Squish's Object Map (which relates symbolic names to real names), and thereby avoid the need to change our test scripts. (See the Object Map (Section 15.10) and the Object Map view (Section 16.2.9) for more about the Object Map.)

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.