How to Create and Use Synchronization Points

When recording a script in Squish, the event recorder must ensure that the AUT and the test script are synchronized. One way of achieving this is for the recorder to automatically insert snooze(seconds) statements into the script. These statements force the script to wait for a specified number of seconds (which might be a fractional amount such as 2.5). This is necessary to ensure that a script is replayed at the same speed as it was recorded. For example, if the user waited for a window to pop up, the script will wait for the same amount of time. This is important to prevent Squish from running the AUT too fast for the AUT's toolkit to keep up.

Using snooze(seconds) statements is the simplest way to synchronize the AUT and a test script. But in many cases, simply waiting for a certain amount of time isn't sufficient. For example, if a script is recorded on a fast machine and later replayed on a slow machine the time waited by snooze(seconds) might not be long enough.

Another way of synchronizing is to use Object waitForObject(objectOrName) statements instead of snooze(seconds) statements. If the Object waitForObject(objectOrName) function is used, before every action that is recorded, a Object waitForObject(objectOrName) statement will be recorded so that the object can be accessed. So on replay, instead of waiting for a specific amount of time, Squish will wait for the given object to exist and be accessible (i.e., visible). Since using the Object waitForObject(objectOrName) function has proved much more reliable than using the snooze(seconds), it is the default method used when recording test cases.

A third alternative is to use the Boolean waitFor(condition) function. This function waits until a given condition becomes true, or optionally, until a specified time out expires. The condition can be anything from a property to a complex script statement. Here is an example that waits for a particular dialog to pop up, and logs a fatal error if the dialog doesn't appear within 5 seconds:

ok = waitFor("object.exists(':Address Book - Save As_QFileDialog')",
if not ok:
    test.fatal("AddressBook Save As dialog didn't appear")
var ok = waitFor("object.exists(':Address Book - Save As_QFileDialog')",
if (!ok)
    test.fatal("AddressBook Save As dialog didn't appear");
my $ok = waitFor("object::exists(':Address Book - Save As_QFileDialog')",
if (!$ok) {
    test::fatal("AddressBook Save As dialog didn't appear");
ok = waitFor("Squish::Object.exists(':Address Book - Save As_QFileDialog')",
if !ok
    Test.fatal("AddressBook Save As dialog didn't appear")
set ok [waitFor {object exists ":Address Book - Save As_QFileDialog"} \
if {!$ok} {
    test fatal "AddressBook Save As dialog didn't appear"

Here is another example, this time one that will wait "forever" since no timeout is specified. So if the expected file doesn't exist and isn't created, the test script will be stuck:

waitFor {invoke QFile exists "addresses.tsv"}

This next example waits up to 2 seconds for an OK button to become enabled. The Boolean waitFor(condition) function repeatedly evaluates the code it has been given as its first argument and returns true as soon as the code evaluates to true; or returns false if the code doesn't evaluate to true within the number of milliseconds specified by its second argument.

button = findObject(":Address Book - Add.OK_QPushButton")
enabled = waitFor("button.enabled", 2000)
if not enabled:
    test.fatal("OK button has not been enabled")
var button = findObject(":Address Book - Add.OK_QPushButton");
var enabled = waitFor("button.enabled", 2000);
if (!enabled)
    test.fatal("OK button has not been enabled");
my $button = findObject(":Address Book - Add.OK_QPushButton");
my $enabled = waitFor("$button->enabled", 2000);
if (!$enabled) {
    test::fatal("OK button has not been enabled");
button = findObject(":Address Book - Add.OK_QPushButton")
enabled = waitFor("button.enabled", 2000)
if !enabled
    Test.fatal("OK button has not been enabled")
set button [findObject ":Address Book - Add.OK_QPushButton"]
set enabled [waitFor {property get $button enabled} 2000]
if {!$enabled} {
    test fatal "OK button has not been enabled"

These examples show different variations of synchronization points. As the condition which is passed to the Boolean waitFor(condition) function can be any script code which can be evaluated, including function calls, there are no limits to creating synchronization points.

More on synchronization for Web applications and advanced AJAX synchronization can be found at How to Synchronize Web Page Loading for Testing.

By combining the Boolean waitFor(condition) function and event handlers (How to Use Event Handlers), it is possible to make the test script execution wait until a specific event has been handled. This can be done by installing an event handler, setting a global variable (e.g. eventHandled) to false, and passing this global variable as condition to the Boolean waitFor(condition) function. In the event handler, just set eventHandled to true and you are done.

Using this approach makes it is possible to create completely event driven tests, where the test's main function just installs event handlers and then starts things off by making a call to Boolean waitFor(condition), with all the testing being done inside the event handlers.