4.12. Squish for Android Tutorials

4.12.1. Tutorial: Starting to Test Android Applications
4.12.2. Tutorial: Designing Behavior Driven Development (BDD) Tests
4.12.3. Tutorial: Migration of existing tests to BDD

Learn how to test Android applications.

4.12.1. Tutorial: Starting to Test Android Applications

This tutorial will show you how to create, run, and modify tests for an example application for Android. In the process you will learn about Squish's most frequently used features so that by the end of the tutorial you will be able to start writing your own tests for your own applications.

This chapter presents most of the major concepts behind Squish and provides the information you need to get started using Squish for testing your own applications. This tutorial does not discuss all of Squish's features, and those that it does cover are not covered in full detail. After reading this tutorial we recommend reading the User Guide (Chapter 5), and at least skimming the API Reference Manual (Chapter 6) and the Tools Reference Manual (Chapter 7), so that you are familiar with all the features that Squish has to offer, even if you don't need to use them all straight away.

This tutorial is divided into several sections. If you are new to Squish (or to the new IDE introduced in Squish 4), it is best to read all of them. If you are already using Squish you might want to just skim the tutorial, stopping only to read those sections that cover any new features that you haven't used before—or you could just skip straight to the User Guide (Chapter 5).

Whenever we show how to achieve something using the IDE we will always follow with an explanation of how to do the same thing using the command line tools. Using an IDE is the easiest and best way to start, but once you build up lots of tests you will want to automate them, (e.g., doing nightly runs of your regression test suite), so it is worth knowing how to use the command line tools since they can be run from batch files or shell scripts.

The application we will test is a very simple Address Book application. Users can add new addresses or edit them via an Activity, and remove addresses. There is demo data using the menu on the main activity. Although the application is very simple it has all the standard features that you are likely to want to use in your own tests, including menus, a list, a pop-up dialog, and with line edits and buttons. Once you know how to test any of these user interface elements you will be able to apply the same principles to testing elements present in your own applications that are not used in the tutorial, such as spinners and date and time controls. (The User Guide (Chapter 5) has more comprehensive examples.)

The screenshot shows the application in action with a user adding a new name and address.

The AddressBook for Android example.

The application (i.e., the AUT—Application Under Test) can be found with Squish's examples in squish/examples/android/AddressBook/AddressBook-debug.apk. The tests that we will discuss in the following sections are in folders, for example, the versions of the tests using the Python language are in squish/examples/android/suite_AddressBook_py, with the tests written in other languages in similarly named sub-folders.

In the following sections we will create a test suite and then create some tests, but first we will very briefly review some key Squish concepts.

4.12.1.1. Squish Concepts

To perform testing, two things are required:

  1. an application to test—known as the Application Under Test (AUT), and

  2. a test script that exercises the AUT.

One fundamental aspect of Squish's approach is that the AUT and the test script that exercises it are always executed in two separate processes. This ensures that even if the AUT crashes, it should not crash Squish. (In such cases the test script will fail gracefully and log an error message.) In addition to insulating Squish and test scripts from AUT crashes, running the AUT and the test script in separate processes brings other benefits. For example, it makes it easier to store the test scripts in a central location, and it also makes it possible to perform remote testing on different machines and platforms. The ability to do remote testing is particularly useful for testing AUTs that run on multiple platforms, and also when testing AUTs that run on embedded devices.

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 the squishserver. The squishserver starts the AUT and injects the Squish hook into it. The hook is a small library that makes the AUT's live running objects accessible and that can communicate with the squishserver. With the hook in place, the squishserver can query AUT objects regarding their state and can execute commands—all on behalf of the squishrunner. And the squishrunner itself requests that the AUT performs 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.

From the test engineer's perspective this separation is not noticeable, since all the communication is handled transparently behind the scenes.

Tests can be written and executed using the Squish IDE, in which case the squishserver is started and stopped automatically, and the test results are displayed in the Squish IDE's Test Results view (Section 8.2.15). The following diagram illustrates what happens behind the scenes when the Squish IDE is used.

The Squish tools can also be used from the command line without the Squish IDE—this is useful for those testers who prefer to use their own tools (for example, their favorite editor), and also for performing automatic batch testing (for example, when running regression tests overnight). In these cases, the squishserver must be started manually, and stopped when all the testing is complete (or, if preferred, started and stopped for each test).

For Squish to make it possible for test scripts to be able to query and control an AUT, Squish must be able to access the AUT's internals, and this is made possible by the use of bindings. Bindings are in effect libraries that provide access to the objects—and in turn to the objects' properties and methods—that are available from a GUI toolkit, or from the AUT itself.

There are two sets of bindings that are of interest when developing tests using Squish.

  1. GUI toolkit bindingsSquish provides bindings for all the GUI toolkits it supports, including Qt, Java AWT/Swing, Java SWT, Web, etc. This means that all the standard objects (including the GUI widgets) provided by these toolkits can be queried and controlled by Squish test scripts.

  2. AUT-specific bindings—it is possible to create bindings that provide access to the AUT's own API for those cases where the toolkit's bindings don't provide sufficient functionality for proper testing. (Note that for Java- and Qt-based AUTs Squish automatically creates bindings to the AUTs objects—including custom classes; see How to Create and Access Application Bindings (Section 5.25).)

The need to make AUT-specific bindings is rarely needed in practice, but if it really is necessary, Squish provides a tool to make the process as simple as possible. The tool, squishidl (Section 7.4.3), is used to instrument the AUT (and any additional components) to generate AUT-specific bindings. The generated bindings library is seamlessly integrated with the standard GUI toolkit bindings and in the same way will automatically be loaded on demand by the Squish test tools.

When Squish automatically creates bindings to AUT classes, for Qt applications this means that the properties and slots of the AUT's custom widgets can be accessed without having to take any special action, and for Java AUTs this means that objects of custom classes are automatically available in test scripts without needing to be registered.

[Note]Terminology

The Squish documentation mostly uses the term widget when referring to GUI objects (i.e., buttons, menus, menu items, labels, table controls, etc). Windows users might be more familiar with the terms control and container, but here we use the term widget for both. Similarly, Mac OS X users may be used to the term view; again, we use the term widget for this concept.

4.12.1.1.1. Making an Application Testable

In most cases, nothing special needs to be done to make an application testable, since the toolkit's API (e.g., Qt) provides enough functionality to implement and record test scripts. The connection to the squishserver is also established automatically, when the Squish IDE starts the AUT.

[Note]The Squish Directory

Throughout the manual, we often refer to the SQUISH directory. This means the directory where Squish is installed, which might be C:\Squish, /usr/local/squish, /opt/local/squish, or somewhere else, depending on where you installed it. The exact location doesn't matter, so long as you mentally translate the SQUISH directory to whatever the directory really is when you see paths and filenames in this manual.

4.12.1.2. Creating a Test Suite

A test suite is a collection of one or more test cases (tests). Using a test suite is convenient since it makes it easy to share tests scripts and test data between a group of related tests.

Here, and throughout the tutorial, we will start by describing how to do things using the IDE, with the information for command line users following.

[Note]Note

Before going forward, make sure you have both the Android SDK and a Java JDK, version 7 or higher, installed on your system running Squish.

It is recommended for this tutorial to now launch the Android emulator and/or attach a running Android device via USB with USB debugging turned on.

Make sure no other Android development environment is running that can access the emulator or device. That includes Eclipse with the ADT plugin and Android Developer Studio. These tools prevent Squish from accessing the AUT (the app we're going to test).

To begin with start up the Squish IDE, either by clicking or double-clicking the squishide icon, or by launching squishide from the taskbar menu or by executing squishide on the command line—whichever you prefer and that is suitable for the platform you are using. Once Squish starts up it will look similar to the screenshot—but probably slightly different depending on the windowing system, colors, fonts, and theme that you use, and so on.

The Squish IDE with no Test Suites

Once Squish has started click File|New Test Suite... to pop-up the New Test Suite wizard shown below.

The New Test Suite wizard's Name & Directory page

Enter a name for your test suite and choose the folder where you want the test suite to be stored. In the screenshot we have called the test suite suite_py and will put it inside the addressbook folder. (For your own tests you might use a more meaningful name such as "suite_addressbook"; we chose "suite_py" because for the sake of the tutorial we will create several suites, one for each scripting language that Squish supports.) Naturally, you can choose whatever name and folder you prefer. Once the details are complete, click Next to go on to the Toolkit (or Scripting Language) page.

[Note]Toolkits

Different versions of Squish support different toolkits—if your version only supports one toolkit, this page may not appear, and you may be taken directly to the Scripting Language page instead. And if you do get this page, the toolkits listed on it might be different from those shown here, depending on what options you built Squish with.

The New Test Suite wizard's Toolkit page

If you get this wizard page, click the toolkit your AUT uses. For this example, we must click Android since we are testing an application for Android. Then click Next to go to the Scripting Language page.

[Note]Scripting Languages

Squish supports several different scripting languages, and different installations may include support for some or all of these—so the scripting languages shown in the screenshot may be different from those shown by your version of Squish.

The New Test Suite wizard's Scripting Language page

Choose whichever scripting language you want—the only constraint is that you can only use one scripting language per test suite. (So if you want to use multiple scripting languages, just create multiple test suites, one for each scripting language you want to use.) The functionality offered by Squish is the same for all languages. Having chosen a scripting language, click Next once more to get to the wizard's last page.

The New Test Suite wizard's AUT page

This tutorial uses com.froglogic.addressbook, a very simple address book application.

The Instrument and Deploy an application dialog

Click the New... and fill in the missing fields. Click on Browse... and select examples/android/AddressBook/AddressBook-debug.apk in the Squish directory. This files contains the app com.froglogic.addressbook.

You can leave the Path to JDK empty if the JDK's bin directory is in your PATH. Typically, users working on MS Windows have to specify this directory and Linux or Mac users can leave this field empty, but do have to make sure Java™ version 7 or higher is installed.

You should see one or more devices and/or emulators listed. Select one or more and press Install.

The Test Suite wizard's AUT page with package

The wizard will show com.froglogic.addressbook in the selection box, once ready.

Finally, click Finish and Squish will create a sub-folder with the same name as the test suite, and will create a file inside that folder called suite.conf that contains the test suite's configuration details. Squish will also register the AUT with the squishserver. The wizard will then close and Squish's IDE will look similar to the screenshot below.

The Squish IDE with the suite_py test suite

We are now ready to start creating tests. Read on to learn how to create test suites without using the IDE, or skip ahead to Recording Tests and Verification Points (Section 4.12.1.3) if you prefer.

[Note]For command-line users

To create a new test suite from the command line, three steps are necessary: first, create a directory for the test suite; second, create a test suite configuration file; instrument and deploy an apk file.

  1. Create a new directory to hold the test suite—the directory's name should begin with suite. In this example we have created the squish/examples/android/suite_AddressBook_py directory for Python tests. (TODO: We also have similar subdirectories for other languages but this is purely for the sake of example, since normally we only use one language for all our tests.)

  2. Create a plain text file (ASCII or UTF-8 encoding) called suite.conf in the suite subdirectory. This is the test suite's configuration file, and at the minimum it must identify the AUT, the scripting language used for the tests, and the wrappers (i.e., the GUI toolkit or library) that the AUT uses. The format of the file is key = value, with one key–value pair per line. For example:

    AUT      = com.froglogic.addressbook
    LANGUAGE = Python
    WRAPPERS = Android
    

    The AUT for programs for Android is the full Java-language-style package name for the application. [16] The LANGUAGE can be set to whichever one you prefer—currently Squish is capable of supporting JavaScript, Python 2, Perl, Ruby, and Tcl, but the precise availability may be different depending on how Squish was installed.

  3. Installing Squish for Android (Section 3.1.12) describes how to instrument and deploy the apk file in detail. Basically, from the Squish directory run on Windows

    bin\apk-tool -j "C:\Program Files\Java\jdkx.y.z" -pkg "<your-apk>" -o "%TEMP% -d <device>
    

    and on Linux or Mac

    bin/apk-tool -pkg "<your-apk>" -o /tmp -d <device>
    

    in which the device is the target device or emulator. Run

    squishrunner --info androidDevices
    

    to get a list of all connected devices and/or emulators.

We are now ready to record our first test.

4.12.1.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 8.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, Ruby, or Tcl.)

The Squish IDE with the tst_general test case
[Note]Note

If you get a sample feature file instead of an empty script file, then click on the arrow left to the Run Test Suite button and select New Script Test Case.

To make the test script file (e.g., test.py) appear in an Editor view (Section 8.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 5). (In fact, two other function names are special to Squish, cleanup and init; see Tester-Created Special Functions (Section 6.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/android/AddressBook_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. Load addresses.

  2. Edit the surname of the second address entry.

  3. Navigate to the first address and remove it.

We are now ready to record our first test. Click the Record Test Case button () that's to the right of the tst_general test case shown in the Test Suites view (Section 8.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. Tap the menu button on the device and tap Demo Data

  2. Tap the second row, the Edit Address page will open. Then tap the second line edit somewhere right of the text, tap the backspace a few times and type "Doe". Don't worry about typing mistakes—just backspace delete as normal and fix them. Finally, tap the Save button—just press the back button when the on-screen keyboard is in the way. The second row should now have the adjusted Surname you typed in.

  3. Now tap the first row. On the Edit Address page, tap the menu button and then tap Delete Address, and then tap the Delete button in the message box. The first row should be gone, so the modified "Doe" entry should now be the first one.

  4. Click the Insert Object Properties Verification Point toolbar button in the Squish Control Bar.

    This will make the Squish IDE appear. In the Application Objects view, expand the AddressBook object and repeat until the ListView first LinearLayout object is expanded. For the emulator used in this tutorial, that requires also expanding the FrameLayout object and then the LinearLayout object. Click the Doe object to make its properties appear in the Properties view (Section 8.2.11), and then check the text property's checkbox.

    The Squish IDE showing two verification points about to be inserted

    Finally, click the Insert (at the top-right of the Verification Point Creator view (Section 8.2.19)) button to have the surname verification 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.

  5. We've now completed the test plan, so in the AUT press the menu button and tap Quit. Finally click the Stop Recording toolbar button in the Squish Control Bar.

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 verification that we inserted will be checked on play back (as shown in the screenshot above).

Inserting verification points during test recording is very convenient. Here we inserted only one, 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.12.1.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 7.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_AddressBook_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.)

When you have multiple devices and/or emulators attached, then you need to specify the target using --device some-device

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 8.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). This should be done to verify that the new script executes reliably.

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 7.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_AddressBook_py --testcase tst_general

When you have multiple devices and/or emulators attached, then you need to specify the target using --device some-device

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 openMenu, tapMenuItem, tapObject, 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, tap 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 5), the API Reference Manual (Chapter 6), and the Tools Reference Manual (Chapter 7). Objects are identified by names that Squish generates. (See How to Identify and Access Objects (Section 5.1) for full details.)

The generated code is about 23 lines of code. Here's an extract that just shows how Squish records tapping an address entry, changing the surname, and tapping Save at the end to close the page 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.)

JavaScript

    tapObject(waitForObject(":Abdul-Wahhab_Text"), 34, 10);
    tapObject(waitForObject(":Edit Address.Surname_Edit"), 150, 15);
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Forename_Edit"), "Doe");
    tapObject(waitForObject(":Edit Address.Save_Button"), 20, 10);
Perl

    tapObject(waitForObject(":Abdul-Wahhab_Text"), 34, 10);
    tapObject(waitForObject(":Edit Address.Surname_Edit"), 150, 15);
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>");
    type(waitForObject(":Edit Address.Forename_Edit"), "Doe");
    tapObject(waitForObject(":Edit Address.Save_Button"), 20, 10);
Python

    tapObject(waitForObject(":Abdul-Wahhab_Text"), 34, 10)
    tapObject(waitForObject(":Edit Address.Surname_Edit"), 150, 15)
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Forename_Edit"), "Doe")
    tapObject(waitForObject(":Edit Address.Save_Button"), 20, 10)
Ruby

    tapObject(waitForObject(":Abdul-Wahhab_Text"), 34, 10)
    tapObject(waitForObject(":Edit Address.Surname_Edit"), 150, 15)
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Surname_Edit"), "<Backspace>")
    type(waitForObject(":Edit Address.Forename_Edit"), "Doe")
    tapObject(waitForObject(":Edit Address.Save_Button"), 20, 10)
Tcl

    invoke tapObject [waitForObject ":Abdul-Wahhab_Text"] 34 10
    invoke tapObject [waitForObject ":Edit Address.Surname_Edit"] 150 15
    invoke type [waitForObject ":Edit Address.Surname_Edit"] "<Backspace>"
    invoke type [waitForObject ":Edit Address.Surname_Edit"] "<Backspace>"
    invoke type [waitForObject ":Edit Address.Surname_Edit"] "<Backspace>"
    invoke type [waitForObject ":Edit Address.Surname_Edit"] "<Backspace>"
    invoke type [waitForObject ":Edit Address.Surname_Edit"] "<Backspace>"
    invoke type [waitForObject ":Edit Address.Surname_Edit"] "<Backspace>"
    invoke type [waitForObject ":Edit Address.Forename_Edit"] "Doe"
    invoke tapObject [waitForObject ":Edit Address.Save_Button"] 20 10

As you can see the tester used touch to set the input focus on an edit field. If the tester had moved the focus by using the cursor keys from a hardware keyboard, the outcome would be the same, but of course Squish will have recorded the actual actions that were taken.

[Note]Note

Notice in the code snippet 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 7.10) and the Object Map view (Section 8.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.

4.12.1.4. 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, either by setting a breakpoint and using the Squish IDE, 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 two kinds of verification points: those that verify that a particular condition holds—known as "Object Property Verifications"; and those that verify that two images match—known as "Screenshot Verifications". Although the screenshot verifications are very impressive, by far the most commonly used kind are object property verifications, and it is these that we will cover in the tutorial. (See also How to Do Screenshot Verifications (Section 5.22.3).)

In fact, object property verification points (which we'll just call "verification points" in the rest of the tutorial), are simply 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 insert calls to the test.compare function in a recorded or hand written script, or we can get Squish to insert them for us using the IDE. In the previous section we showed how to use the Squish IDE to insert verifications during recording. Here we will first show how to use the Squish IDE to insert verifications into an existing test script, and then we will show how to insert a verification by hand.

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 fore- and surname are correctly added to the address list immediately after recording adding one address.

To insert a verification point using the 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.

The Squish IDE showing the tst_add_address test case with a breakpoint

As the above screenshot shows, we have set a breakpoint at line 13. This is done simply by right-clicking the line number and then clicking the Toggle Breakpoint menu item in the context menu. We chose this line because it follows the script lines where the address is saved, so at this point (just before invoking the menu to quit the application), the first address should be that of "Zikra Glen". (Note that 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 the Run Test 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 13, 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 Squish Test Debugging Perspective (Section 8.1.2.3).

[Note]Perspectives and Views

The Squish IDE works just like the Eclipse IDE. This provides a sophisticated user interface. If you aren't used to Eclipse it is crucial to understand one key concept: Views and Perspectives. In Eclipse (and therefore in the new Squish IDE), a View is essentially a child window (perhaps a dock window, or a tab in an existing window). And a Perspective is a collection of Views arranged together. Both are accessible through the Window menu.

The Squish IDE is supplied with three Perspectives—the Squish Test Management Perspective (Section 8.1.2.2) (which is the Perspective that the Squish IDE starts with, and the one we have seen in all previous screenshots), Squish Test Debugging Perspective (Section 8.1.2.3), and Squish Spy Perspective (Section 8.1.2.1). You can change these Perspectives to include additional Views (or to get rid of any Views that you don't want), and you can create your own Perspectives with exactly the Views you want. So if your windows change dramatically it just means that the Perspective changed; you can always use the Window menu to change back to the Perspective you want. In practice, Squish will automatically change perspective to reflect the current situation, so it isn't really necessary to change perspective manually.

As the screenshot below shows, when Squish stops at a breakpoint the Squish IDE automatically changes to the Squish Test Debugging Perspective (Section 8.1.2.3). The perspective shows the Variables view (Section 8.2.18), the Editor view (Section 8.2.6), the Debug view (Section 8.2.5), the Application Objects view (Section 8.2.1), and the Properties view (Section 8.2.11), Methods view (Section 8.2.8), and Test Results view (Section 8.2.15).

To insert a verification point we can expand items in the Application Objects view until we find the object we want to verify. In this example we want to verify the first row's texts, so we expand the AddressBook item, and its child items until we find the ListView, and within that search for the objects we are interested in. Once we click the Zikra object, its properties are shown in the Properties view (Section 8.2.11) as the screenshot shows.

Finding an object to verify in the Application Objects view

The normal Squish Test Management Perspective (Section 8.1.2.2) 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 this TextView item has the value “Zikra”. To make sure that this is verified every time the test is run, click this TextView item in the Application Objects view (Section 8.2.1) 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 (Section 8.2.19) appears as shown in the screenshot.

Choosing a property value to verify

At this point the verification point has not been added to the test script. We could easily add it by clicking the Insert button (next to the Scriptified Properties VP combobox). But before doing that we'll add one more thing to be verified.

Scroll down and click the “GlenTextView item in the Application Objects view (Section 8.2.1); then click its text property. Now both verifications will appear in the Verification Point Creator view (Section 8.2.19) as the screenshot shows.

Choosing several property values to verify

We have now said that we expect these properties to have the values shown, that is, a forname of “Zikra” and surname of “Glen”. 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 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.)

The newly inserted verification points in action

These particular verification points generate two tests comparing the forename and surname 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 test.compare and test.verify 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 (Section 8.2.1) 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 to clipboard (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 list after inserting an entry. The screenshot shows two of the lines of code we entered to get the verification, plus the results of running the test script.

Manually entered verification points in action

When writing scripts by hand, we use Squish's test module's functions to verify conditions at certain points during our test script's execution. As the screenshot shows, we begin by retrieving a reference to the object we are interested in. Using the waitForObject 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 ListView's rowCount—and verify that the value is what we expect it to be using the test.verify 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 list'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.)

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.

We will see more examples of manually written code shortly, in the Creating Tests by Hand (Section 4.12.1.5) section, and further examples are in the User Guide (Chapter 5).

For complete coverage of verification points, see How to Create and Use Verification Points (Section 5.22) in the User Guide (Chapter 5).

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

4.12.1.5. 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 Squish Spy Perspective (Section 8.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 Quit AUT toolbar button to terminate the AUT and return Squish to the Squish Test Management Perspective (Section 8.1.2.2). (See How to Use the Spy (Section 5.21.3) in the User Guide (Chapter 5) for more details on using the Spy.)

We can view the Object Map by clicking the Object Map toolbar button (see also, the Object Map view (Section 8.2.9)). 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.

Squish's Object Map

Alternatively real names can be used, also by right-clicking the object we are interested in and then clicking the context menu's Copy Real Name. For this “List” object the real name is {container=':Address Book_Activity' type='List' visible='true'}. The following three tapObject commands are in the context of this test suite equivalent:

tapObject(":Address Book_List")
tapObject("{container=':Address Book_Activity' type='List' visible='true'}")
tapObject("{container={text='Address Book' type='Activity' visible='true'} type='List' visible='true'}")

This can be useful when dynamically creating object names. A symbolic name is mapped to a real name in the object map. When using real names directly, no entry in the object map is needed. Note the quoting of the container property when using a symbolic name or a real name. See How to Access Named Objects (Section 5.1.1) for more information.

4.12.1.5.1. Modifying and Refactoring Recorded Tests

Suppose we want to test the AUT's Add functionality by adding three new addresses. We could of course record such a test but it isjust as easy to do everything in code. The steps we need the test script to do are: first tap the “Add Address” button, then fill in the fields and finally tap the “Save” button. 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.

Lets start with the “tst_add_address” from previous section and turn this in a function that gets the fields as argument.

Python

def addNameAndAddress(fields):
    forname,surname,email,phone = fields
    tapObject(waitForObject(":Address Book.Add Address_Button"))
    tapObject(waitForObject(":Edit Address.Forename_Edit"))
    type(waitForObject(":Edit Address.Forename_Edit"), forname)
    tapObject(waitForObject(":Edit Address.Surname_Edit"))
    type(waitForObject(":Edit Address.Surname_Edit"), surname)
    tapObject(waitForObject(":Edit Address.Phone_Edit"))
    type(waitForObject(":Edit Address.Phone_Edit"), phone)
    tapObject(waitForObject(":Edit Address.Email_Edit"))
    type(waitForObject(":Edit Address.Email_Edit"), email)
    tapObject(waitForObject(":Edit Address.Save_Button"))
JavaScript

function addNameAndAddress(fields)
{
    tapObject(waitForObject(":Address Book.Add Address_Button"));
    tapObject(waitForObject(":Edit Address.Forename_Edit"));
    type(waitForObject(":Edit Address.Forename_Edit"), fields[0]);
    tapObject(waitForObject(":Edit Address.Surname_Edit"));
    type(waitForObject(":Edit Address.Surname_Edit"), fields[1]);
    tapObject(waitForObject(":Edit Address.Phone_Edit"));
    type(waitForObject(":Edit Address.Phone_Edit"), fields[3]);
    tapObject(waitForObject(":Edit Address.Email_Edit"));
    type(waitForObject(":Edit Address.Email_Edit"), fields[2]);
    tapObject(waitForObject(":Edit Address.Save_Button"));
}
Perl

sub addNameAndAddress {
    my ($forname,$surname,$email,$phone) = @_;
    tapObject(waitForObject(":Address Book.Add Address_Button"));
    tapObject(waitForObject(":Edit Address.Forename_Edit"));
    type(waitForObject(":Edit Address.Forename_Edit"), $forname);
    tapObject(waitForObject(":Edit Address.Surname_Edit"));
    type(waitForObject(":Edit Address.Surname_Edit"), $surname);
    tapObject(waitForObject(":Edit Address.Phone_Edit"));
    type(waitForObject(":Edit Address.Phone_Edit"), $phone);
    tapObject(waitForObject(":Edit Address.Email_Edit"));
    type(waitForObject(":Edit Address.Email_Edit"), $email);
    tapObject(waitForObject(":Edit Address.Save_Button"));
}
Ruby

def addNameAndAddress(fields)
    tapObject(waitForObject(":Address Book.Add Address_Button"))
    tapObject(waitForObject(":Edit Address.Forename_Edit"))
    type(waitForObject(":Edit Address.Forename_Edit"), fields[0])
    tapObject(waitForObject(":Edit Address.Surname_Edit"))
    type(waitForObject(":Edit Address.Surname_Edit"), fields[1])
    tapObject(waitForObject(":Edit Address.Phone_Edit"))
    type(waitForObject(":Edit Address.Phone_Edit"), fields[3])
    tapObject(waitForObject(":Edit Address.Email_Edit"))
    type(waitForObject(":Edit Address.Email_Edit"), fields[2])
    tapObject(waitForObject(":Edit Address.Save_Button"))
end
Tcl

proc addNameAndAddress {fields} {
    invoke tapObject [waitForObject ":Address Book.Add Address_Button"]
    invoke tapObject [waitForObject ":Edit Address.Forename_Edit"]
    invoke type [waitForObject ":Edit Address.Forename_Edit"] [lindex $fields 0]
    invoke tapObject [waitForObject ":Edit Address.Surname_Edit"]
    invoke type [waitForObject ":Edit Address.Surname_Edit"] [lindex $fields 1]
    invoke tapObject [waitForObject ":Edit Address.Phone_Edit"]
    invoke type [waitForObject ":Edit Address.Phone_Edit"] [lindex $fields 3]
    invoke tapObject [waitForObject ":Edit Address.Email_Edit"]
    invoke type [waitForObject ":Edit Address.Email_Edit"] [lindex $fields 2]
    invoke tapObject [waitForObject ":Edit Address.Save_Button"]
}

Next we call this function with an array of list of fields from the main function

Python

def main():
    startApplication("com.froglogic.addressbook")
    table = waitForObject(":Address Book_List")
    test.verify(table.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.verify(table.rowCount == len(data))
    openMenu(waitForObject(":Address Book_Activity"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit")
JavaScript

function main()
{
    startApplication("com.froglogic.addressbook");
    var table = waitForObject(":Address Book_List");
    test.verify(table.rowCount == 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(table.rowCount == data.length);
    openMenu(waitForObject(":Address Book_Activity"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit");
}
Perl

sub main() {
    startApplication("com.froglogic.addressbook");
    my $table = waitForObject(":Address Book_List");
    test::verify($table->rowCount == 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 $line (@data) {
        addNameAndAddress(@$line);
    }
    test::verify($table->rowCount == scalar(@data));
    openMenu(waitForObject(":Address Book_Activity"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit");
}
Ruby

def main
    startApplication("com.froglogic.addressbook")
    table = waitForObject(":Address Book_List")
    Test.verify(table.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 |address|
        addNameAndAddress(address)
    end
    Test.verify(table.rowCount == data.length)
    openMenu(waitForObject(":Address Book_Activity"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit")
end
Tcl

proc main {} {
    startApplication "com.froglogic.addressbook"
    set table [waitForObject ":Address Book_List"]
    test compare [property get $table 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 $table rowCount] [llength $data]
    invoke openMenu [waitForObject ":Address Book_Activity"]
    invoke tapMenuItem [waitForObject ":Address Book_Activity"] "Quit"
}

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

4.12.1.5.2. 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), and .xls (Microsoft® Excel™ spreadsheet format—but not .xlsx format). [17]

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 (Section 8.3.3). 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 (Section 8.2.6). The screenshot shows Squish after the test data has been added.

[Note]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 shared. Now make a directory inside the shared directory called testdata. Now copy the data file (in this example, MyAddresses.tsv) into the shared\testdata directory. Now quit the Squish IDE if it is running and start it up again. If you click the Test Suite Resources view's Test Data tab you should see the data file. Click the file's name to see it in an Editor view (Section 8.2.6).

Squish with some imported test data

Although in real life we would modify our tst_add_address 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_add_address 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.

Python

def main():
    startApplication("com.froglogic.addressbook")
    table = waitForObject(":Address Book_List")
    test.verify(table.rowCount == 0)
    limit = 10 # To avoid testing 100s of rows since that would be boring
    for row, record in enumerate(testData.dataset("MyAddresses.tsv")):
        fields = testData.field(record, "Forename"), testData.field(record, "Surname"), testData.field(record, "Email"), testData.field(record, "Phone")
        addNameAndAddress(fields)
        if row > limit:
            break

    test.verify(table.rowCount == row+1)
JavaScript

function main()
{
    startApplication("com.froglogic.addressbook");
    var table = waitForObject(":Address Book_List");
    test.verify(table.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 line = [testData.field(record, "Forename")
                   , testData.field(record, "Surname")
                   , testData.field(record, "Email")
                   , testData.field(record, "Phone")];
        addNameAndAddress(line);
        if (row > limit)
            break;
    }
    test.verify(table.rowCount == row+1);
    openMenu(waitForObject(":Address Book_Activity"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit");
}
Perl

sub main() {
    startApplication("com.froglogic.addressbook");
    my $table = waitForObject(":Address Book_List");
    test::verify($table->rowCount == 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 @line = ( testData::field($record, "Forename")
                   , testData::field($record, "Surname")
                   , testData::field($record, "Email")
                   , testData::field($record, "Phone") );
        addNameAndAddress(@line);
        if ($row > $limit) {
            last;
        }
    }
    test::verify($table->rowCount == $row+1);
    openMenu(waitForObject(":Address Book_Activity"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit");
}
Ruby

def main
    startApplication("com.froglogic.addressbook")
    table = waitForObject(":Address Book_List")
    Test.verify(table.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|
        line = [TestData.field(record, "Forename"),
                TestData.field(record, "Surname"),
                TestData.field(record, "Email"),
                TestData.field(record, "Phone")]
        addNameAndAddress(line)
        break if row > limit
        rows += 1
    end
    Test.verify(table.rowCount == rows+1)
    openMenu(waitForObject(":Address Book_Activity"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit")
end
Tcl

proc main {} {
    startApplication "com.froglogic.addressbook"
    set table [waitForObject ":Address Book_List"]
    test compare [property get $table 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 fields [list \
                    [testData field $record "Forename"]  \
                    [testData field $record "Surname"]   \
                    [testData field $record "Email"]     \
                    [testData field $record "Phone"]]
        addNameAndAddress $fields
        if {$row > $limit} {
            break
        }
    }
    test compare [property get $table rowCount] [expr $row+1]
    invoke openMenu [waitForObject ":Address Book_Activity"]
    invoke tapMenuItem [waitForObject ":Address Book_Activity"] "Quit"
}
4.12.1.5.3. Using the Android native API

In this section we take a quick look at the nativeObject property and use it to rewrite a recorded script that scrolls through the demo list of addresses. This property allows access to the underlying Java™ object of a Squish user interface object. The available properties and methods of these native objects are dynamically created on use in the scripts. The online Android developer reference is the place to get documenation about them.

When recording a test on an item in a list that is only visible when scrolling the list, we probably get a script that contains the touchAndDrag function. E.g. here a small recording in python:

 
def main():
    startApplication("com.froglogic.addressbook")
    openMenu(waitForObject(":Address Book_Activity"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Demo Data")
    touchAndDrag(waitForObject(":Mccullagh_Text"), 62, 16, 30, -531)
    touchAndDrag(waitForObject(":Mattison_Text"), 60, 16, 77, -619)
    touchAndDrag(waitForObject(":Copus_Text"), 85, 9, 19, -574)
    touchAndDrag(waitForObject(":Templeton_Text"), 124, 16, -27, -649)
    tapObject(waitForObject(":Nataniel_Text"), 51, 19)
    openMenu(waitForObject(":Edit Address.Forename_Edit"))
    tapMenuItem(waitForObject(":Edit Address_Activity"), "Cancel")
    openMenu(waitForObject(":Address Book_List"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit")

While this replays well as long as the demo list is unchanged, it is somewhat slow on replay. Also the starting point objects of the touchAndDrag function may not be there when replayed with a device or emulator having a smaller vertical resolution. A more robust approach would be to search through the items of the list, scroll the list to it and then tap on the item.

nativeObject property

We can programmatically scroll the list using the ListView method smoothScrollToPosition. For finding the position to scroll to, we show you three different approaches, one that uses the adapter of this list, one that uses the, during scrolling, changing Squish object hierarchy and one that searches for an object name also during scrolling.

4.12.1.5.3.1. Using the adapter of the list

We can use the adapter of this list because the adapter is a SimpleAdapter. It holds a List of Map objects.

Here an example of a function that uses this approach, given a list and a text to be found:

Python

def scrollListToText1(list, text):
    adapter = list.nativeObject.adapter
    for i in range(adapter.getCount()):
        row = adapter.getItem(i)
        if (row.containsValue(text)):
            list.nativeObject.smoothScrollToPosition(i)
            break
JavaScript

function scrollListToText1(list, text) {
    var adapter = list.nativeObject.adapter;
    var total = adapter.getCount();
    for (var i = 0; i < total; ++i) {
        var row = adapter.getItem(i);
        if (row.containsValue(text)) {
            list.nativeObject.smoothScrollToPosition(i);
            break;
        }
    }
}
Perl

sub scrollListToText1 {
    my ($list, $text) = @_;
    my $adapter = $list->nativeObject->adapter;
    my $total = $adapter->getCount();
    for (my $i = 0; $i < $total; ++$i) {
        my $row = $adapter->getItem($i);
        if ($row->containsValue($text)) {
            $list->nativeObject->smoothScrollToPosition($i);
            last;
        }
    }
}
Ruby

def scrollListToText1(list, text)
    adapter = list.nativeObject.adapter
    total = adapter.getCount();
    i = 0
    while i < total
        row = adapter.getItem(i)
        if (row.containsValue(text))
            list.nativeObject.smoothScrollToPosition(i)
            break
        end
        i += 1
    end
end
Tcl

proc scrollListToText1 {lst text} {
    set adapter [property get [property get $lst nativeObject] adapter]
    set total [invoke $adapter getCount]
    for {set i 0} {$i < $total} {incr i} {
        set row [invoke $adapter getItem $i]
        if {[invoke $row containsValue $text]} {
            invoke [property get $lst nativeObject] smoothScrollToPosition $i
            break
        }
    }
}

Note that although according the API documentation the getItem member returns an java.lang.Object, the actually value is a java.util.Map and thus we can just call containsValue on it.

This is the most simplest approach but only works when a list has this SimpleAdapter type set as data source. For other types, a variation can be written of course. Here the main function using this scrollListToText1 function.

Python

def main():
    startApplication("com.froglogic.addressbook")
    openMenu(waitForObject(":Address Book_Activity"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Demo Data")
    list = waitForObject(":Address Book_List")

    scrollListToText3(list, "Nataniel")
    tapObject(waitForObject(":Nataniel_Text"))

    openMenu(waitForObject(":Edit Address.Forename_Edit"))
    tapMenuItem(waitForObject(":Edit Address_Activity"), "Cancel")
    openMenu(waitForObject(":Address Book_List"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit")
JavaScript

function main() {
    startApplication("com.froglogic.addressbook");
    openMenu(waitForObject(":Address Book_Activity"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Demo Data");
    list = waitForObject(":Address Book_List");

    scrollListToText2(list, "Nataniel");
    tapObject(waitForObject(":Nataniel_Text"));

    openMenu(waitForObject(":Edit Address.Forename_Edit"));
    tapMenuItem(waitForObject(":Edit Address_Activity"), "Cancel");
    openMenu(waitForObject(":Address Book_List"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit");
}
Perl

sub main {
    startApplication("com.froglogic.addressbook");
    openMenu(waitForObject(":Address Book_Activity"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Demo Data");
    my $list = waitForObject(":Address Book_List");

    scrollListToText3($list, "Nataniel");
    tapObject(waitForObject(":Nataniel_Text"));

    openMenu(waitForObject(":Edit Address.Forename_Edit"));
    tapMenuItem(waitForObject(":Edit Address_Activity"), "Cancel");
    openMenu(waitForObject(":Address Book_List"));
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit");
}
Ruby

def main()
    startApplication("com.froglogic.addressbook")
    openMenu(waitForObject(":Address Book_Activity"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Demo Data")
    list = waitForObject(":Address Book_List")

    scrollListToText2(list, "Nataniel")
    tapObject(waitForObject(":Nataniel_Text"))

    openMenu(waitForObject(":Edit Address.Forename_Edit"))
    tapMenuItem(waitForObject(":Edit Address_Activity"), "Cancel")
    openMenu(waitForObject(":Address Book_List"))
    tapMenuItem(waitForObject(":Address Book_Activity"), "Quit")
end
Tcl

proc main {} {
    startApplication "com.froglogic.addressbook"
    invoke openMenu [waitForObject ":Address Book_Activity"]
    invoke tapMenuItem [waitForObject ":Address Book_Activity"] "Demo Data"
    set lst [waitForObject ":Address Book_List"]

    scrollListToText3 $lst "Nataniel"
    invoke tapObject [waitForObject ":Nataniel_Text"]

    invoke openMenu [waitForObject ":Edit Address.Forename_Edit"]
    invoke tapMenuItem [waitForObject ":Edit Address_Activity"] "Cancel"
    invoke openMenu [waitForObject ":Address Book_List"]
    invoke tapMenuItem [waitForObject ":Address Book_Activity"] "Quit"
}
4.12.1.5.3.2. Using the object hierarchy inside the list

Next we try to scroll using the object.children function traversing the Squish object hierarchy. As we can see in the object tree, the list has for each row a Panel object with two Text objects. Only the rows visible are in this hierarchy. So this list changes when scrolling down. With that knowledge we can try to scroll to an item as follows:

Python

def scrollListToText2(list, text):
    total = list.rowCount
    current = 0
    while current < total:
        list.nativeObject.smoothScrollToPosition(current)
        rows = object.children(list)
        for row in rows:
            for textview in object.children(row):
                if textview.text == text:
                    return
        current += len(rows)
JavaScript

function scrollListToText2(list, text) {
    var total = list.rowCount;
    var current = 0;
    while (current < total) {
        list.nativeObject.smoothScrollToPosition(current);
        var rows = object.children(list);
        for (var r = 0; r < rows.length; ++r) {
            var columns = object.children(rows[r]);
            for (var c = 0; c < columns.length; ++c) {
                if (columns[c].text == text)
                    return;
            }
        }
        current += rows.length;
    }
}
Perl

sub scrollListToText2 {
    my ($list, $text) = @_;
    my $total = $list->rowCount;
    my $current = 0;
    while ($current < $total) {
        $list->nativeObject->smoothScrollToPosition($current);
        my @rows = object::children($list);
        for my $row (@rows) {
            for my $textview (object::children($row)) {
                if ($textview->text eq $text) {
                    return;
                }
            }
        }
        $current += $#rows;
    }
}
Ruby

def scrollListToText2(list, text)
    total = list.rowCount
    current = 0
    while current < total
        list.nativeObject.smoothScrollToPosition(current)
        rows = Squish::Object.children(list)
        rows.each do |row|
            for textview in Squish::Object.children(row)
                if textview.text == text
                    return
                end
            end
        end
        current += rows.length
    end
end
Tcl

proc scrollListToText2 {lst text} {
    set total [property get $lst rowCount]
    set current 0
    while {$current < $total} {
        invoke [property get $lst nativeObject] smoothScrollToPosition $current
        set rows [object children $lst]
        foreach row $rows {
            set columns [object children $row]
            foreach textview $columns {
                set tmp [property get $textview text]
                if {$tmp == $text} {
                    return
                }
            }
        }
        incr current [llength $rows]
    }
}

Using this method of scrolling to a certain Text object is very dependent on the exact hierarchy layout and will break when a layer is added or removed.

4.12.1.5.3.3. Using object name search

Just like Section 4.12.1.5.3.2, “Using the object hierarchy inside the list” we let the object hierarchy change with scrolling but this time we just search for an object name using the object.exists function:

Python

def scrollListToText3(list, text):
    objectname = "{container=':Address Book_List' text='" + text + "' type='Text' visible='true'}"
    total = list.rowCount
    current = 0
    page = list.nativeObject.getLastVisiblePosition() - list.nativeObject.getFirstVisiblePosition()
    while current < total:
        list.nativeObject.smoothScrollToPosition(current)
        if object.exists(objectname):
            break
        current += page
JavaScript

function scrollListToText3(list, text) {
    var objectname = "{container=':Address Book_List' text='" + text + "' type='Text' visible='true'}";
    var total = list.rowCount;
    var current = 0;
    var page = list.nativeObject.getLastVisiblePosition() - list.nativeObject.getFirstVisiblePosition();
    while (current < total) {
        list.nativeObject.smoothScrollToPosition(current);
        if (object.exists(objectname))
            break;
        current += page;
    }
}
Perl

sub scrollListToText3 {
    my ($list, $text) = @_;
    my $objectname = "{container=':Address Book_List' text='" . $text . "' type='Text' visible='true'}";
    my $total = $list->rowCount;
    my $current = 0;
    my $page = $list->nativeObject->getLastVisiblePosition() - $list->nativeObject->getFirstVisiblePosition();
    while ($current < $total) {
        $list->nativeObject->smoothScrollToPosition($current);
        if (object::exists($objectname)) {
            last;
        }
        $current += $page;
    }
}
Ruby

def scrollListToText3(list, text)
    objectname = "{container=':Address Book_List' text='" + text + "' type='Text' visible='true'}"
    total = list.rowCount
    current = 0
    page = list.nativeObject.getLastVisiblePosition() - list.nativeObject.getFirstVisiblePosition()
    while current < total
        list.nativeObject.smoothScrollToPosition(current)
        if Squish::Object.exists(objectname)
            break
        end
        current += page
    end
end
Tcl

proc scrollListToText3 {lst text} {
    set objectname "{container=':Address Book_List' text='$text' type='Text' visible='true'}"
    set total [property get $lst rowCount]
    set current 0
    set page [expr [invoke [property get $lst nativeObject] getLastVisiblePosition] - [invoke [property get $lst nativeObject] getFirstVisiblePosition]]
    while {$current < $total} {
        invoke [property get $lst nativeObject] smoothScrollToPosition $current
        if {[object exists '$objectname']} {
            break
        }
        incr current $page
    }
}

This method of scrolling to a certain Text object is the most robust of the three presented. When the object hierarchy changes in a later version of the app, it likely will still work because the object name only requires a Text object in a List object.

More examples for using nativeObject can be found in the section How to Use the nativeObject Property (Section 5.7.1).

4.12.1.6. Learning More

We have now completed the tutorial! Squish can of course 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 User Guide (Chapter 5) provides many more examples, including those that show how tests can interact with particular widgets such as spinners, comboboxes, and line editors, and of course with view widgets and their underlying models.

The API Reference Manual (Chapter 6) and Tools Reference Manual (Chapter 7) 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 User Guide (Chapter 5) and at least skimming the API Reference Manual (Chapter 6) and Tools Reference Manual (Chapter 7)—especially since the time 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.

Squish for Android supports recording and replaying of gestures. As well as a powerful API to create or manipulate them in your scripts. See How to Use the GestureBuilder class (Section 5.7.2) for further reading.

The key Android examples with links to the places they are used are given below.

  • The AddressBook shows how to test some Android widgets such as: Button, TextEdit, ListView, and Menu

  • The WebBrowserHost shows how to test with an embedded Android WebView widget.

4.12.2. Tutorial: Designing Behavior Driven Development (BDD) Tests

This tutorial will show you how to create, run, and modify Behavior Driven Development (BDD) tests for an example application. You will learn about Squish's most frequently used features. By the end of the tutorial you will be able to write your own tests for your own applications.

For this chapter we will use a simple Address Book application as our Application Under Test (AUT). This is a very basic application that allows users to add, edit, and remove entries. The screenshot shows the application in action with a user adding a new entry.

The Android addressbook example.

4.12.2.1. Introduction to Behavior Driven Development

Behavior-Driven Development (BDD) is an extension of the Test-Driven Development approach which puts the definition of acceptance criteria at the beginning of the development process as opposed to writing tests after the software has been developed. With possible cycles of code changes done after testing.

BDD process

Behavior Driven Tests are built out of a set of Feature files, which describe product features through the expected application behavior in one or many Scenarios. Each Scenario is built out of a sequence of Steps which represent actions or verifications that need to be tested for that Scenario.

BDD focuses on expected application behavior, not on implementation details. Therefore BDD tests are described in a human-readable Domain Specific Language (DSL). As this language is not technical, such tests can be created not only by programmers, but also by product owners, testers or business analysts. Additionally, during the product development, such tests serve as living product documentation. For Squish usage, BDD tests shall be created using Gherkin syntax. The previously written product specification (BDD tests) can be turned into executable tests. This step by step tutorial presents automating BDD tests with Squish IDE support.

4.12.2.2. Gherkin syntax

Gherkin files describe product features through the expected application behavior in one or many Scenarios. An example showing the "Filling of addressbook" feature of the addressbook example application.

Feature: Filling of addressbook
    As a user I want to fill the addressbook with entries
 
    Scenario: Initial state of created address book
        Given addressbook application is running
        Then addressbook should have zero entries
 
    Scenario: State after adding one entry
        Given addressbook application is running
        When I add a new person 'John','Doe','john@m.com','500600700' to address book
        Then '1' entries should be present
 
    Scenario: State after adding two entries
        Given addressbook application is running
        When I add new persons to address book
            | forename  | surname  | email        | phone  |
            | John      | Smith    | john@m.com   | 123123 |
            | Alice     | Thomson  | alice@m.com  | 234234 |
        Then '2' entries should be present
 
    Scenario: Forename and surname is added to table
        Given addressbook application is running
        When I add a new person 'Bob','Doe','Bob@m.com','123321231' to address book
        Then previously entered forename and surname shall be at the top

Most of the above is free form text (does not have to be English). It's just the Feature/Scenario structure and the leading keywords like "Given", "And", "When" and "Then" that are fixed. Each of those keywords marks a step defining preconditions, user actions and expected results. Above application behavior description can be passed to software developers to implement this features and at the same time the same description can be passed to software testers to implement automated tests.

4.12.2.3. Test implementation

4.12.2.3.1. Creating Test Suite

First, we need to create a Test Suite, which is a container for all Test Cases. Start the squishide and select File|New Test Suite.... Please follow the New Test Suite wizard, provide a Test Suite name, choose the Android Toolkit and scripting language of your choice and finally register Address Book application as AUT. Please refer to Creating a Test Suite (Section 4.12.1.2) for more details about creating new Test Suite.

4.12.2.3.2. Creating Test Case

Squish offers two types of Test Cases: "Script Test Case" and "BDD Test Case". As "Script Test Case" is the default one, in order to create new "BDD Test Case" we need to use the context menu by clicking on the expander next to New Test Case button and choosing the option "New BDD Test Case". The Squish IDE will remember your choice and the "BDD Test Case" will become the default when clicking on the button in the future.

Creating new BDD Test Case

The newly created BDD Test Case consists of a test.feature file (filled with a Gherkin template while creating a new BDD test case), a file named test.(py|js|pl|rb|tcl) which will drive the execution (there is no need to edit this file), and a file in Test Suite Resources named steps/steps.(py|js|pl|rb|tcl) where Step implementation code will be placed.

We need to replace the Gherkin template with a Feature for the addressbook example application. To do this, copy the Feature description below and paste it into the Feature file.

Feature: Filling of addressbook
    As a user I want to fill the addressbook with entries

    Scenario: Initial state of created address book
        Given addressbook application is running
        Then addressbook should have zero entries

After saving the test.feature file, a Feature file warning "No implementation found" is displayed for each Step. This means that no Step implementation was found in the steps subdirectory, in Test Case Resources, or in Test Suite Resources. Running our Feature test now will currently fail at the first step with a "No Matching Step Definition" error and the following Steps will be skipped.

4.12.2.3.3. Recording Step implementation

In order to record the Scenario, press the Record button next to the respective Scenario that is listed in the Scenarios tab in Test Case Resources view.

Record Scenario

This will cause Squish to run the AUT so that we can interact with it. Additionally, the Control Bar is displayed with a list of all Steps that need to be recorded. Now all interaction with the AUT or any verification points added to the script will be recorded under the first step Given addressbook application is running (which is bolded in the Step list on the Control Bar). In order to verify that this precondition is met, we will add a Verification Point. To do this, click on Insert Verifications in the Control Bar and select Properties.

Control Bar

As a result the Squish IDE is put into Spy mode which displays all Application Objects and their properties. In the Application Objects list, select the addressbook main window item. Selecting it will update the Properties view on the right side. Next click on the checkbox in front of the property "enabled" in the Properties View. Finally, click on the button Save and Insert Verifications. The Squish IDE disappears and the Control Bar is shown again.

Inserting Verification Point

When we are done with the first step, we can move to the next Step in the sequence by clicking on the button in the Control Bar that is located right in front of the current Step ().

Next, for the step "Then addressbook should have zero entries" verify that the table containing the address entries is empty. To record this verification, click on Insert Verifications while recording, select Properties and use the Picker tool to point at the table containing the address book entries (in our case this table is empty). Choose the rowCount property from Object Properties list and click on Insert Verifications. Finally, click on the Stop recording button in the Control Bar.

As a result, Squish will generate the following Step definitions in the steps.* file (at Test Suites+Test Suite Resources):

Python
@Given("addressbook application is running")
def step(context):
    startApplication("com.froglogic.addressbook")
    waitFor("object.exists(':Address Book_Activity')", 20000)
    test.compare(findObject(":Address Book_Activity").enabled, True)

@Then("addressbook should have zero entries")
def step(context):
    waitFor("object.exists(':Address Book.addressList_List')", 20000)
    test.compare(findObject(":Address Book.addressList_List").rowCount, 0)
JavaScript
Given("addressbook application is running", function(context) {
    startApplication("com.froglogic.addressbook");
    waitFor("object.exists(':Address Book_Activity')", 20000);
    test.compare(findObject(":Address Book_Activity").enabled, true);
});

Then("addressbook should have zero entries", function(context) {
    waitFor("object.exists(':Address Book.addressList_List')", 20000);
    test.compare(findObject(":Address Book.addressList_List").rowCount, 0);
});
Perl
Given(
    "addressbook application is running",
    sub {
        my $context = shift;
        startApplication("com.froglogic.addressbook");
        waitFor( "object::exists(':Address Book_Activity')", 20000 );
        test::compare( findObject(":Address Book_Activity")->enabled, 1 );
    }
);

Then(
    "addressbook should have zero entries",
    sub {
        my $context = shift;
        waitFor( "object::exists(':Address Book.addressList_List')",20000 );
        test::compare(findObject(":Address Book.addressList_List")->rowCount,0);
    }
);
Ruby
Given("addressbook application is running") do |context|
  startApplication("com.froglogic.addressbook")
  waitFor("Squish::Object.exists(':Address Book_Activity')", 20000)
  Test.compare(findObject(":Address Book_Activity").enabled, true)
end

Then("addressbook should have zero entries") do |context|
  waitFor("Squish::Object.exists(':Address Book.addressList_List')", 20000)
  Test.compare(findObject(":Address Book.addressList_List").rowCount, 0)
end
Tcl
Given "addressbook application is running" {context} {
    startApplication "com.froglogic.addressbook"
    waitFor {object exists ":Address Book_Activity"} 20000
    test compare [property get [findObject ":Address Book_Activity"] enabled] true
}

Then "addressbook should have zero entries" {context} {
    waitFor {object exists ":Address Book.addressList_List"} 20000
    test compare [property get [findObject ":Address Book.addressList_List"] rowCount] 0
}

The application is automatically started at the beginning of the first step due to the recorded startApplication call. At the end of each Step, Squish detaches from the application, but leaves it running. Detaching is done in function called OnScenarioEnd. This function is a so-called hook and you can find it in the file bdd_hooks.(py|js|pl|rb|tcl), which is located in the Scripts tab of the Test Suite Resources view. You can define additional hooks. For a list of all available hooks please refer to Performing Actions During Test Execution Via Hooks (Section 6.19.9).

Python
@OnScenarioEnd
def OnScenarioEnd():
    for ctx in applicationContextList():
        ctx.detach()

JavaScript
OnScenarioEnd(function(context) {
    applicationContextList().forEach(function(ctx) { ctx.detach(); });
});

Perl
OnScenarioEnd(sub {
    foreach (applicationContextList()) {
        $_->detach();
    }
});
Ruby
OnScenarioEnd do |context|
    applicationContextList().each { |ctx| ctx.detach() }
end
Tcl
OnScenarioEnd {context} {
    foreach ctx [applicationContextList] {
        applicationContext $ctx detach
    }
}
4.12.2.3.4. Manual Step implementation

An alternative approach to recording Step implementations is to manually implement them. This gives us the opportunity to modularize our test scripts (i.e. put common code into shared functions, keep test data separate from test scripts). Squish can help with that by creating skeletons of step definitions. To generate a Step implementation, right-click on the given Scenario in the Feature file and choose the option Create Missing Step Implementations from context menu.

Python
@Given("addressbook application is running")
def step(context):
    test.warning("TODO implement addressbook application is running")

@Then("addressbook should have zero entries")
def step(context):
    test.warning("TODO implement addressbook should have zero entries")
JavaScript
Given("addressbook application is running", function(context) {
    test.warning("TODO implement addressbook application is running");
});

Then("addressbook should have zero entries", function(context) {
    test.warning("TODO implement addressbook should have zero entries");
});
Perl
Given("addressbook application is running", sub {
    my $context = shift;
    test::warning("TODO implement addressbook application is running");
});

Then("addressbook should have zero entries", sub {
    my $context = shift;
    test::warning("TODO implement addressbook should have zero entries");
});
Ruby
Given("addressbook application is running") do |context|
  Test.warning "TODO implement addressbook application is running"
end

Then("addressbook should have zero entries") do |context|
  Test.warning "TODO implement addressbook should have zero entries"
end
Tcl
Given "addressbook application is running" {context} {
    test warning "TODO implement addressbook application is running"
}

Then "addressbook should have zero entries" {context} {
    test warning "TODO implement addressbook should have zero entries"
}

Next implement Steps definition taking full advantage of Squish API (API Reference Manual (Chapter 6)) and remove the generated test.warning calls when you are done.

4.12.2.3.5. Step parameterization

So far, our Steps did not use any parameters and all values were hardcoded. Squish has different types of parameters like any, integer or word, allowing our Step definitions to be more reusable. Let us add a new Scenario to our Feature file which will provide Step parameters for both the Test Data and the expected results. Copy the below section into your Feature file.

Scenario: State after adding one entry
    Given addressbook application is running
    When I add a new person 'John','Doe','john@m.com','500600700' to address book
    Then '1' entries should be present

After auto-saving the Feature file, the Squish IDE provides a hint that only 2 Steps need to be implemented: When I add a new person 'John', 'Doe','john@m.com','500600700' to address book and Then '1' entries should be present. The remaining Steps already have a matching Step implementation.

To record the missing Steps, hit the record button next to the test case name in the Test Suites view. The script will play until it gets to the missing Step and then prompt you to implement it. If you select the Add button, then you can type in the information for a new entry. Click on the button to move to the next step. For the second missing step, we could record an object property verification like we did with the Step Then addressbook should have zero entries. Or we could copy that step's implmentation in the steps.(py|js|pl|rb|tcl) file and increment the number at the end of the test.compare line. Instead of testing for zero items, we are testing for one item.

Now we paramaterize the generated step implementation by replacing the values with parameter types. Since we want to be able to add different names, replace 'John' with '|word|'. Note that each parameter will be passed to the step implementation function in the order of appearance in the descriptive name of the step. Finish paramaterizing by editing the typed values into keywords, to look like this example Step When I add a new person 'John', 'Doe','john@m.com','500600700' to address book:

Python
@When("I add a new person '|word|','|word|','|any|','|integer|' to address book")
def step(context, forename, surname, email, phone):
    tapObject(waitForObject(":Address Book.Add Address_Button"))
    tapObject(waitForObject(":Edit Address.Forename_Edit"))
    type(waitForObject(":Edit Address.Forename_Edit"), forename)
    tapObject(waitForObject(":Edit Address.Surname_Edit"))
    type(waitForObject(":Edit Address.Surname_Edit"), surname)
    tapObject(waitForObject(":Edit Address.Phone_Edit"))
    type(waitForObject(":Edit Address.Phone_Edit"), phone)
    tapObject(waitForObject(":Edit Address.Email_Edit"))
    type(waitForObject(":Edit Address.Email_Edit"), email)
    tapObject(waitForObject(":Edit Address.Save_Button"))
JavaScript
When("I add a new person '|word|','|word|','|any|','|integer|' to address book",
    function (context, forename, surname, email, phone){
        tapObject(waitForObject(":Address Book.Add Address_Button"));
        tapObject(waitForObject(":Edit Address.Forename_Edit"));
        type(waitForObject(":Edit Address.Forename_Edit"), forename);
        tapObject(waitForObject(":Edit Address.Surname_Edit"));
        type(waitForObject(":Edit Address.Surname_Edit"), surname);
        tapObject(waitForObject(":Edit Address.Phone_Edit"));
        type(waitForObject(":Edit Address.Phone_Edit"), phone);
        tapObject(waitForObject(":Edit Address.Email_Edit"));
        type(waitForObject(":Edit Address.Email_Edit"), email);
        tapObject(waitForObject(":Edit Address.Save_Button"));
});
Perl
When("I add a new person '|word|','|word|','|any|','|integer|' to address book",
  sub {
      my $context = shift;
      my ($forename, $surname, $email, $phone) = @_;
      tapObject( waitForObject(":Address Book.Add Address_Button") )
      tapObject( waitForObject(":Edit Address.Forename_Edit") )
      type( waitForObject(":Edit Address.Forename_Edit"), $forename )
      tapObject( waitForObject(":Edit Address.Surname_Edit") )
      type( waitForObject(":Edit Address.Surname_Edit"), $surname )
      tapObject( waitForObject(":Edit Address.Phone_Edit") )
      type( waitForObject(":Edit Address.Phone_Edit"), $phone )
      tapObject( waitForObject(":Edit Address.Email_Edit") )
      type( waitForObject(":Edit Address.Email_Edit"), $email )
      tapObject( waitForObject(":Edit Address.Save_Button") )
    });
Ruby
When("I add a new person '|word|','|word|','|any|','|integer|' to address book") do |context, forename, surname, email, phone|
  tapObject(waitForObject(":Address Book.Add Address_Button"))
  tapObject(waitForObject(":Edit Address.Forename_Edit"))
  type(waitForObject(":Edit Address.Forename_Edit"), forename)
  tapObject(waitForObject(":Edit Address.Surname_Edit"))
  type(waitForObject(":Edit Address.Surname_Edit"), surname)
  tapObject(waitForObject(":Edit Address.Phone_Edit"))
  type(waitForObject(":Edit Address.Phone_Edit"), phone)
  tapObject(waitForObject(":Edit Address.Email_Edit"))
  type(waitForObject(":Edit Address.Email_Edit"), email)
  tapObject(waitForObject(":Edit Address.Save_Button"))
end
Tcl
When "I add a new person '|word|','|word|','|any|','|integer|' to address book" {context forename surname email phone} {
    invoke tapObject [waitForObject ":Address Book.Add Address_Button"]
    invoke tapObject [waitForObject ":Edit Address.Forename_Edit"]
    invoke type [waitForObject ":Edit Address.Forename_Edit"] $forename
    invoke tapObject [waitForObject ":Edit Address.Surname_Edit"]
    invoke type [waitForObject ":Edit Address.Surname_Edit"] $surname
    invoke tapObject [waitForObject ":Edit Address.Phone_Edit"]
    invoke type [waitForObject ":Edit Address.Phone_Edit"] $phone
    invoke tapObject [waitForObject ":Edit Address.Email_Edit"]
    invoke type [waitForObject ":Edit Address.Email_Edit"] $email
    invoke tapObject [waitForObject ":Edit Address.Save_Button"]
}
4.12.2.3.6. Provide parameters for Step in table

The next Scenario will test adding multiple entries to the address book. We could use step When I add a new person John','Doe','john@m.com','500600700' to address book multiple times just with different data. But lets instead define a new Step called When I add a new person to address book which will handle data from a table.

Scenario: State after adding two entries
    Given addressbook application is running
    When I add new persons to address book
        | forename  | surname  | email        | phone  |
        | John      | Smith    | john@m.com   | 123123 |
        | Alice     | Thomson  | alice@m.com  | 234234 |
    Then '2' entries should be present

The Step implementation to handle such tables looks like this:

Python
@When("I add new persons to address book")
def step(context):
    table = context.table

    # Drop initial row with column headers
    table.pop(0)

    for (forename, surname, email, phone) in table:
        tapObject(waitForObject(":Address Book.Add Address_Button"))
        tapObject(waitForObject(":Edit Address.Forename_Edit"))
        type(waitForObject(":Edit Address.Forename_Edit"), forename)
        tapObject(waitForObject(":Edit Address.Surname_Edit"))
        type(waitForObject(":Edit Address.Surname_Edit"), surname)
        tapObject(waitForObject(":Edit Address.Phone_Edit"))
        type(waitForObject(":Edit Address.Phone_Edit"), phone)
        tapObject(waitForObject(":Edit Address.Email_Edit"))
        type(waitForObject(":Edit Address.Email_Edit"), email)
        tapObject(waitForObject(":Edit Address.Save_Button"))
JavaScript
When("I add new persons to address book", function(context) {
    var table = context.table;

    // Skip initial row with column headers by starting at index 1
    for (var i = 1; i < table.length; ++i) {
        var forename = table[i][0];
        var surname = table[i][1];
        var email = table[i][2];
        var phone = table[i][3];
        tapObject(waitForObject(":Address Book.Add Address_Button"));
        tapObject(waitForObject(":Edit Address.Forename_Edit"));
        type(waitForObject(":Edit Address.Forename_Edit"), forename);
        tapObject(waitForObject(":Edit Address.Surname_Edit"));
        type(waitForObject(":Edit Address.Surname_Edit"), surname);
        tapObject(waitForObject(":Edit Address.Phone_Edit"));
        type(waitForObject(":Edit Address.Phone_Edit"), phone);
        tapObject(waitForObject(":Edit Address.Email_Edit"));
        type(waitForObject(":Edit Address.Email_Edit"), email);
        tapObject(waitForObject(":Edit Address.Save_Button"));
    }
});
Perl
When("I add new persons to address book", sub {
    my %context = %{shift()};
    my @table = @{$context{'table'}};

    # Drop initial row with column headers
    shift(@table);

    for my $row (@table) {
      my ($forename, $surname, $email, $phone) = @{$row};

      tapObject( waitForObject(":Address Book.Add Address_Button") );
      tapObject( waitForObject(":Edit Address.Forename_Edit") );
      type( waitForObject(":Edit Address.Forename_Edit"), $forename );
      tapObject( waitForObject(":Edit Address.Surname_Edit") );
      type( waitForObject(":Edit Address.Surname_Edit"), $surname );
      tapObject( waitForObject(":Edit Address.Phone_Edit") );
      type( waitForObject(":Edit Address.Phone_Edit"), $phone );
      tapObject( waitForObject(":Edit Address.Email_Edit") );
      type( waitForObject(":Edit Address.Email_Edit"), $email );
      tapObject( waitForObject(":Edit Address.Save_Button") );
    }
});
Ruby
When("I add new persons to address book") do |context|
  table = context.table

  # Drop initial row with column headers
  table.shift

  for forename, surname, email, phone in table do
      tapObject(waitForObject(":Address Book.Add Address_Button"))
      tapObject(waitForObject(":Edit Address.Forename_Edit"))
      type(waitForObject(":Edit Address.Forename_Edit"), forename)
      tapObject(waitForObject(":Edit Address.Surname_Edit"))
      type(waitForObject(":Edit Address.Surname_Edit"), surname)
      tapObject(waitForObject(":Edit Address.Phone_Edit"))
      type(waitForObject(":Edit Address.Phone_Edit"), phone)
      tapObject(waitForObject(":Edit Address.Email_Edit"))
      type(waitForObject(":Edit Address.Email_Edit"), email)
      tapObject(waitForObject(":Edit Address.Save_Button"))
  end
end
Tcl
When "I add new persons to address book" {context} {
    set table [$context table]

    # Drop initial row with column headers
    foreach row [lreplace $table 0 0] {
      foreach {forename surname email phone} $row break

      invoke tapObject [waitForObject(":Address Book.Add Address_Button"]
      invoke tapObject [waitForObject ":Edit Address.Forename_Edit"]
      invoke type [waitForObject ":Edit Address.Forename_Edit"] $forename)
      invoke tapObject [waitForObject ":Edit Address.Surname_Edit"]
      invoke type [waitForObject ":Edit Address.Surname_Edit"] $surname)
      invoke tapObject [waitForObject ":Edit Address.Phone_Edit"]
      invoke type [waitForObject ":Edit Address.Phone_Edit"] $phone
      invoke tapObject [waitForObject ":Edit Address.Email_Edit"]
      invoke type [waitForObject ":Edit Address.Email_Edit"] $email
      invoke tapObject [waitForObject ":Edit Address.Save_Button"]
    }
}
4.12.2.3.7. Sharing data between Steps and Scenarios

Lets add a new Scenario to the Feature file. This time we would like to check not the number of entries in address book list, but if this list contains proper data. Because we enter data into the address book in one Step and verify them in another, we must share information about entered data among those Steps in order to perform a verification.

Scenario: Forename and surname is added to table
    Given addressbook application is running
    When I add a new person 'Bob','Doe','Bob@m.com','123321231' to address book
    Then previously entered forename and surname shall be at the top

To share this data, the userData property of context object can be used.

Python
@When("I add a new person '|word|','|word|','|any|','|integer|' to address book")
def step(context, forename, surname, email, phone):
    tapObject(waitForObject(":Address Book.Add Address_Button"))
    tapObject(waitForObject(":Edit Address.Forename_Edit"))
    type(waitForObject(":Edit Address.Forename_Edit"), forename)
    tapObject(waitForObject(":Edit Address.Surname_Edit"))
    type(waitForObject(":Edit Address.Surname_Edit"), surname)
    tapObject(waitForObject(":Edit Address.Phone_Edit"))
    type(waitForObject(":Edit Address.Phone_Edit"), phone)
    tapObject(waitForObject(":Edit Address.Email_Edit"))
    type(waitForObject(":Edit Address.Email_Edit"), email)
    tapObject(waitForObject(":Edit Address.Save_Button"))
    context.userData = {}
    context.userData['forename'] = forename
    context.userData['surname'] = surname
JavaScript
When("I add a new person '|word|','|word|','|any|','|integer|' to address book",
    function (context, forename, surname, email, phone){
        tapObject(waitForObject(":Address Book.Add Address_Button"));
        tapObject(waitForObject(":Edit Address.Forename_Edit"));
        type(waitForObject(":Edit Address.Forename_Edit"), forename);
        tapObject(waitForObject(":Edit Address.Surname_Edit"));
        type(waitForObject(":Edit Address.Surname_Edit"), surname);
        tapObject(waitForObject(":Edit Address.Phone_Edit"));
        type(waitForObject(":Edit Address.Phone_Edit"), phone);
        tapObject(waitForObject(":Edit Address.Email_Edit"));
        type(waitForObject(":Edit Address.Email_Edit"), email);
        tapObject(waitForObject(":Edit Address.Save_Button"));
        context.userData["forename"] = forename;
        context.userData["surname"] = surname;
});
Perl
When("I add a new person '|word|','|word|','|any|','|integer|' to address book",
  sub {
      my $context = shift;
      my ($forename, $surname, $email, $phone) = @_;

      tapObject( waitForObject(":Address Book.Add Address_Button") );
      tapObject( waitForObject(":Edit Address.Forename_Edit") );
      type( waitForObject(":Edit Address.Forename_Edit"), $forename );
      tapObject( waitForObject(":Edit Address.Surname_Edit") );
      type( waitForObject(":Edit Address.Surname_Edit"), $surname );
      tapObject( waitForObject(":Edit Address.Phone_Edit") );
      type( waitForObject(":Edit Address.Phone_Edit"), $phone );
      tapObject( waitForObject(":Edit Address.Email_Edit") );
      type( waitForObject(":Edit Address.Email_Edit"), $email );
      tapObject( waitForObject(":Edit Address.Save_Button") );
      $context->{userData}{'forename'} = $forename;
      $context->{userData}{'surname'} = $surname;
    });
Ruby
When("I add a new person '|word|','|word|','|any|','|integer|' to address book") do |context, forename, surname, email, phone|
  tapObject(waitForObject(":Address Book.Add Address_Button"))
  tapObject(waitForObject(":Edit Address.Forename_Edit"))
  type(waitForObject(":Edit Address.Forename_Edit"), forename)
  tapObject(waitForObject(":Edit Address.Surname_Edit"))
  type(waitForObject(":Edit Address.Surname_Edit"), surname)
  tapObject(waitForObject(":Edit Address.Phone_Edit"))
  type(waitForObject(":Edit Address.Phone_Edit"), phone)
  tapObject(waitForObject(":Edit Address.Email_Edit"))
  type(waitForObject(":Edit Address.Email_Edit"), email)
  tapObject(waitForObject(":Edit Address.Save_Button"))
  context.userData = Hash.new
  context.userData[:forename] = forename
  context.userData[:surname] = surname
end
Tcl
When "I add a new person '|word|','|word|','|any|','|integer|' to address book" {context forename surname email phone} {
    invoke tapObject [waitForObject(":Address Book.Add Address_Button"]
    invoke tapObject [waitForObject ":Edit Address.Forename_Edit"]
    invoke type [waitForObject ":Edit Address.Forename_Edit"] $forename)
    invoke tapObject [waitForObject ":Edit Address.Surname_Edit"]
    invoke type [waitForObject ":Edit Address.Surname_Edit"] $surname)
    invoke tapObject [waitForObject ":Edit Address.Phone_Edit"]
    invoke type [waitForObject ":Edit Address.Phone_Edit"] $phone
    invoke tapObject [waitForObject ":Edit Address.Email_Edit"]
    invoke type [waitForObject ":Edit Address.Email_Edit"] $email
    invoke tapObject [waitForObject ":Edit Address.Save_Button"]
    $context userData [dict create forename $forename surname $surname]
}

All data stored in context.userData can be accessed in all Steps and Hooks in all Scenarios of the given Feature. Finally, we need to implement the Step Then previously entered forename and surname shall be at the top.

Python
@Then("previously entered forename and surname shall be at the top")
def step(context):
    list = waitForObject(":Address Book.addressList_List")
    rows = object.children(list)
    row_0 = rows[0]
    columns_row_0 = object.children(row_0)
    test.compare(columns_row_0[0].text, context.userData["forename"])
    test.compare(columns_row_0[1].text, context.userData["surname"])
JavaScript
Then("previously entered forename and surname shall be at the top", function(context){
    var list = waitForObject(":Address Book.addressList_List");
    var rows = object.children(list);
    var row_0 = rows[0];
    var columns_row_0 = object.children(row_0);
    test.compare(columns_row_0[0].text, context.userData["forename"]);
    test.compare(columns_row_0[1].text, context.userData["surname"]);
});
Perl
Then("previously entered forename and surname shall be at the top", sub {
    my $context = shift;
    my $list = waitForObject(":Address Book.addressList_List");
    my $rows = object::children(list);
    my $row_0 = rows[0];
    my $columns_row_0 = object::children(row_0);
    test.compare(columns_row_0[0].text, $context->{userData}{'forename'});
    test.compare(columns_row_0[1].text, $context->{userData}{'surname'});
});
Ruby
Then("previously entered forename and surname shall be at the top") do |context|
  list = waitForObject(":Address Book.addressList_List")
  rows = Object.children(list)
  row_0 = rows[0]
  columns_row_0 = Object.children(row_0)
  Test.compare(columns_row_0[0].text, context.userData[:forename])
  Test.compare(columns_row_0[1].text, context.userData[:surname])
end

Tcl
Then "previously entered forename and surname shall be at the top" {context} {
    set list [waitForObject ":Address Book.addressList_List"]
    set rows [object children $list]
    set row_0 [$rows[0]];
    set columns_row_0 [object children $row_0]
    test compare [property get [$columns_row_0[0]] text] [dict get [$context userData] $forename]
    test compare [property get [$columns_row_0[1]] text] [dict get [$context userData] $surname]
}
4.12.2.3.8. Scenario Outline

Assume our Feature contains the following two Scenarios:

Scenario: State after adding one entry
    Given addressbook application is running
    When I add a new person 'John','Doe','john@m.com','500600700' to address book
    Then "1" entries should be present

Scenario: State after adding one entry
    Given addressbook application is running
    When I add a new person 'Bob','Koo','bob@m.com','500600800' to address book
    Then "1" entries should be present

As we can see, those Scenarios perform the same actions using different test data. The same can be achieved by using a Scenario Outline (a Scenario template with placeholders) and Examples (a table with parameters).

Scenario Outline: Adding single entries multiple time
    Given addressbook application is running
    When I add a new person '<forename>','<surname>','<email>','<phone>' to address book
    Then '1' entries should be present
    Examples:
        | forename | surname  | email       | phone     |
        | John     | Doe      | john@m.com  | 500600700 |
        | Bob      | Koo      | bob@m.com   | 500600800 |

Please note that the OnScenarioEnd hook will be executed at the end of each loop iteration in a Scenario Outline.

4.12.2.4. Test execution

In the Squish IDE, users can execute all Scenarios in a Feature, or execute only one selected Scenario. In order to execute all Scenarios, the proper Test Case has to be executed by clicking on the Play button in the Test Suites view.

Execute all Scenarios from Feature

In order to execute only one Scenario, you need to open the Feature file, right-click on the given Scenario and choose Run Scenario. An alternative approach is to click on the Play button next to the respective Scenario in the Scenarios tab in Test Case Resources.

Execute one Scenario from Feature

After a Scenario is executed, the Feature file is colored according to the execution results. More detailed information (like logs) can be found in the Test Results View.

Execution results in Feature file

4.12.2.5. Test debugging

Squish offers the possibility to pause an execution of a Test Case at any point in order to check script variables, spy application objects or run custom code in the Squish script console. To do this, a breakpoint has to be placed before starting the execution, either in the Feature file at any line containing a Step or at any line of executed code (i.e. in middle of step definition code).

Breakpoint in Feature file

After the breakpoint is reached, you can inspect all application objects and their properties. If a breakpoint is placed at a Step definition or a hook is reached, then you can additionally add Verification Points or record code snippets.

4.12.2.6. Re-using Step definitions

BDD test maintainability can be increased by reusing Step definitions. For example, the following line imports all Step definitions first from the step directories in the Test Case Resources and Test Suite Resources.

Python
collectStepDefinitions('./steps', '../shared/steps')
JavaScript
collectStepDefinitions('./steps', '../shared/steps');
Perl
use Squish::BDD;
collectStepDefinitions("./steps", "../shared/steps");
Ruby
include Squish::BDD
collectStepDefinitions "./steps", "../shared/steps"
Tcl
source [findFile "scripts" "tcl/bdd.tcl"]
Squish::BDD::collectStepDefinitions "./steps" "../shared/steps"

If the same Step definition is provided in multiple directories, then the first Step definition occurrence is used. Hence in above example, the definition from the Test Case Resources will be used. If no definition is found in that directory, the definition from the Test Suite Resources will be used.

4.12.3. Tutorial: Migration of existing tests to BDD

This chapter is aimed for users that have existing standard Squish tests and who would like to introduce Behavior Driven Testing. The first section describes how to keep the existing tests and just create new tests with the BDD approach. The second section describes how to convert existing standard tests to BDD tests.

4.12.3.1. Extend existing tests to BDD

The first option is to keep any existing Squish standard tests and extend them by adding new BDD tests. It's possible to have a Test Suite containing both standard Test Cases and BDD Test Cases. Simply open existing Test Suite with standard Test Cases and choose "New BDD Test Case" option from drop down list.

Creating new BDD Test Case

Assuming your existing standard Test Cases make use of a library and you are calling shared functions to interact with the AUT, those functions can still be used in both, existing standard Test Cases and newly created BDD Test Cases. In the example below, a function is used in multiple standard Test Cases:

Python
def deleteFirstEntry():
    #...
    pass
JavaScript
function deleteFirstEntry(){
    //...
}
Perl
sub deleteFirstEntry{
    #...
}
Ruby
def deleteFirstEntry
  #...
end
Tcl
proc deleteFirstEntry {} {
    #...
}

New BDD Test Cases can easily use the same function:

Python
@When("I delete the first entry")
def step(context):
    deleteFirstEntry()
JavaScript
When("I delete the first entry", function(context){
    deleteFirstEntry()
});
Perl
When("I delete the first entry", sub {
    deleteFirstEntry();
});
Ruby
When("I delete the first entry") do |context|
  deleteFirstEntry
end
Tcl
When "I delete the first entry" {context} {
    deleteFirstEntry
}

4.12.3.2. Convert existing tests to BDD

The second option is to convert an existing Test Suite that contains standard Test Cases into behavior driven tests. Since a Test Suite can contain both, standard Test Cases and BDD Test Cases, migration can be done gradually. A Test Suite containing a mix of both Test Case types can be executed and results analyzed without any extra effort required.

The first step is to review all Test Cases of the existing Test Suite and group them by the Feature they test. Each standard Test Case will be transformed into a Scenario, which is a part of a Feature. For example, assume we have 5 standard Test Cases. After review, we realize that those standard Test Cases examine two Features. Therefore, when migration is completed, our Test Suite will contain two BDD Test Cases, each of them containing one Feature. Each Feature will contain multiple Scenarios. In our example the first Feature contains three Scenarios and the second Feature contains two Scenarios.

Conversion Chart

At the beginning, open a Test Suite in the Squish IDE that contains standard Squish tests that are planned to be migrated to BDD tests. Next, create a new Test Case by choosing New BDD Test Case option from the context menu. Each BDD Test Case contains a test.feature file that can be filled with maximum one Feature. Next, open the test.feature file to describe the Features using the Gherkin language. Following the syntax from the template, edit the Feature name and optionally provide a short description. Next, analyze which actions and verifications are performed in the standard Test Case that is going to be migrated. This is how an example Test Case for the addressbook application could look like:

Python
def main():
    startApplication("com.froglogic.addressbook")
    waitFor("object.exists(':Address Book.addressList_List')", 20000)
    test.compare(findObject(":Address Book.addressList_List").rowCount, 0, "Addressbook is empty?")
JavaScript
function main(){
    startApplication("com.froglogic.addressbook");
    waitFor("object.exists(':Address Book.addressList_List')", 20000);
    test.compare(findObject(":Address Book.addressList_List").rowCount, 0, "Addressbook is empty?");
}
Perl
sub main {
	startApplication("com.froglogic.addressbook");
	waitFor( "object::exists(':Address Book.addressList_List')",20000 );
    test::compare(findObject(":Address Book.addressList_List")->rowCount,0, "Addressbook is empty?");
}
Ruby
def main
  startApplication("com.froglogic.addressbook")
  waitFor("Squish::Object.exists(':Address Book.addressList_List')", 20000)
  Test.compare(findObject(":Address Book.addressList_List").rowCount, 0, "Addressbook is empty?")
end
Tcl
proc main {} {
    startApplication "com.froglogic.addressbook"
    waitFor {object exists ":Address Book.addressList_List"} 20000
    test compare [property get [findObject ":Address Book.addressList_List"] rowCount] 0
}

After analyzing the above standard Test Case we can create the following Scenario and add it to test.feature file:

Scenario: Initial state of created address book
   Given addressbook application is running
   Then addressbook should be empty

Next, right-click on the Scenario and choose the option Create Missing Step Implementations from the context menu. This will create a skeleton of Steps definitions:

Python
@Given("addressbook application is running")
def step(context):
    test.warning("TODO implement addressbook application is running")

@Then("addressbook should be empty")
def step(context):
    test.warning("TODO implement addressbook should be empty")
JavaScript
Given("addressbook application is running", function(context) {
    test.warning("TODO implement addressbook application is running");
});

Then("addressbook should be empty", function(context) {
    test.warning("TODO implement addressbook should be empty");
});
Perl
Given("addressbook application is running", sub {
    my $context = shift;
    test::warning("TODO implement addressbook application is running");
});

Then("addressbook should be empty", sub {
    my $context = shift;
    test::warning("TODO implement addressbook should be empty");
});
Ruby
Given("addressbook application is running") do |context|
  Test.warning "TODO implement addressbook application is running"
end

Then("addressbook should be empty") do |context|
  Test.warning "TODO implement addressbook should be empty"
end
Tcl
Given "addressbook application is running" {context} {
    test warning "TODO implement addressbook application is running"
}

Then "addressbook should be empty" {context} {
    test warning "TODO implement addressbook should be empty"
}

Now we put code snippets from the standard Test Case into respective Step definitions and remove the lines containing test.warning. If your standard Test Cases make use of shared scripts, you can call those functions inside of the Step definition as well. For example, the final result could look like this:

Python
@Given("addressbook application is running")
def step(context):
    startApplication("com.froglogic.addressbook")

@Then("addressbook should be empty")
def step(context):
    waitFor("object.exists(':Address Book.addressList_List')", 20000)
    test.compare(findObject(":Address Book.addressList_List").rowCount, 0,  "Addressbook is empty?")
JavaScript
Given("addressbook application is running", function(context) {
    startApplication("com.froglogic.addressbook");
    });

Then("addressbook should be empty", function(context) {
        waitFor("object.exists(':Address Book.addressList_List')", 20000);
        test.compare(findObject(":Address Book.addressList_List").rowCount, 0);
    });
Perl
Given("addressbook application is running", sub {
    my $context = shift;
	startApplication("com.froglogic.addressbook");
});

Then("addressbook should be empty", sub {
    my $context = shift;
	waitFor( "object::exists(':Address Book.addressList_List')",20000 );
    test::compare(findObject(":Address Book.addressList_List")->rowCount,0);
});
Ruby
Given("addressbook application is running") do |context|
  startApplication("com.froglogic.addressbook")
end

Then("addressbook should be empty") do |context|
  waitFor("Squish::Object.exists(':Address Book.addressList_List')", 20000)
  Test.compare(findObject(":Address Book.addressList_List").rowCount, 0, "Addressbook is empty?")
end
Tcl
Given "addressbook application is running" {context} {
    startApplication "com.froglogic.addressbook"
}

Then "addressbook should be empty" {context} {
    waitFor {object exists ":Address Book.addressList_List"} 20000
    test compare [property get [findObject ":Address Book.addressList_List"] rowCount] 0
}

Additionally, when the Test Case execution ends, Squish terminates the AUT. After converting standard Test Cases into Scenarios, we must ensure that the AUT is terminated at the end of Scenario as well. This can be done by implementing an OnScenarioEnd hook.

Python
@OnScenarioEnd
def hook(context):
    for ctx in applicationContextList():
        ctx.detach()
JavaScript
OnScenarioEnd(function(context) {
    applicationContextList().forEach(function(ctx) { ctx.detach(); });
});
Perl
OnScenarioEnd(sub {
    foreach (applicationContextList()) {
        $_->detach();
    }
});
Ruby
OnScenarioEnd do |context|
    applicationContextList().each { |ctx| ctx.detach() }
end
Tcl
OnScenarioEnd {context} {
    foreach ctx [applicationContextList] {
        applicationContext $ctx detach
    }
}

The above example was simplified for this tutorial. In order to take full advantage of Behavior Driven Testing in Squish, please familiarize yourself with the section Behavior Driven Testing (Section 6.19) in API Reference Manual (Chapter 6).




[16] Run

squishrunner --info androidInstrumentation
to get a list of all possible AUT's when squishserver and your Android emulators or devices are running.

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