All supported script languages in the automated GUI Testing Tool Squish support closures. In this blog I'll write up a simple example how closures can be used to unify function calls that seem to have different set of arguments.
Assume a test snippet that tests two ways to open a settings dialog, together with two ways to cancel the dialog. The script snippet may look like this.
[code language="javascript" title="Dialog open/close recording"]function main() {
...
activateItem(waitForObjectItem(":MenuBar", "File"));
activateItem(waitForObjectItem(":File_Menu", "Settings"));
type(waitForObject(":Settings_Dialog"), "<Esc>");
type(waitForObject(":MainWindow"), "<Ctrl+Shift+p>");
clickButton(waitForObject(":Settings.Cancel_Button"));
...
}
[/code]
To harden this script, e.g. test the dialog was not open before opening, is really closed after closing, etc, the following refactoring is done.
[code language="javascript" title="Dialog open/close functions"]function openPreferenceDialog(how)
{
try {
findObject(":Settings_Dialog");
test.fail("Dialog is open");
return;
} catch (e) {}
if (how == "menu") {
activateItem(waitForObjectItem(":MenuBar", "File"));
activateItem(waitForObjectItem(":File_Menu", "Settings"));
} else if (how == "shortcut") {
type(waitForObject(":MainWindow"), "<Ctrl+Shift+p>");
}
waitForObject(":Settings_Dialog");
}
function closePreferenceDialog(how)
{
findObject(":Settings_Dialog");
if (how == "cancelButton") {
clickButton(waitForObject(":Settings.Cancel_Button"));
} else if (how == "esc") {
type(waitForObject(":Settings_Dialog"), "<Esc>");
}
for (var i = 0; i < 10; ++i) {
try {
findObject(":Settings_Dialog");
snooze(.5);
} catch (e) {
break; // good, not found anymore
}
}
}
function main() {
...
openPreferenceDialog("menu");
closePreferenceDialog("esc");
openPreferenceDialog("shortcut");
closePreferenceDialog("cancelButton");
...
}
[/code]
The how
argument selects between the two open/close methods.
The openPreferenceDialog
/closePreferenceDialog
functions hard-codes the objects it targets. Therefore refactor it further so that the how
string argument is a function instead. So something like this.
[code language="javascript" title="Dialog open function"]function openDialog(dialogName, openFunction)
{
try {
findObject(dialogName);
test.fail("Dialog is open");
return;
} catch (e) {}
openFunction();
waitForObject(dialogName);
}
function main()
{
...
openDialog(":Settings_Dialog", function() {
activateItem(waitForObjectItem(":MenuBar", "File"));
activateItem(waitForObjectItem(":File_Menu", "Settings"));
});
...
[/code]
B.t.w. for this particular example, I wouldn't further generalize the script code in practice.
But for the purpose of this blog, using the scripts closure feature, lets make these openFunction
/closeFunction
functions re-usable.
Somehow the object names must be passed to these functions. And intuitively pass these names to openDialog
which then pass them further to openFunction
. Both activateItem
and type
take an object name and a second item or text argument but, for closing, clickButton
and type
don't match in number of arguments.
Here closures provide a rather simple solution. Use a function that provides a function. The returned function can use the arguments passed to outer-function1.
[code language="javascript" title="Dialog open/close generalization"]function typeFunction(objectName, text) {
return function() {
type(waitForObject(objectName), text);
};
}
function clickMenuFunction(menuBar, menuBarItem, menu, item) {
return function() {
activateItem(waitForObjectItem(menuBar, menuBarItem));
activateItem(waitForObjectItem(menu, item));
}
}
function clickButtonFunction(objectName) {
return function() {
clickButton(waitForObject(objectName));
};
}
function openDialog(dialogName, openFunction)
{
try {
findObject(dialogName);
test.fail("Dialog is open");
return;
} catch (e) {}
openFunction();
waitForObject(dialogName);
}
function closeDialog(dialogName, closeFunction)
{
findObject(dialogName);
closeFunction()
for (var i = 0; i < 10; ++i) {
try {
findObject(dialogName);
snooze(.5);
} catch (e) {
break; //good, not found anymore
}
}
}
function main() {
...
openDialog(":Settings_Dialog", clickMenuFunction(":MenuBar", "File", ":File_Menu", "Settings"));
closeDialog(":Settings_Dialog", typeFunction(":Settings_Dialog", "<Esc>"));
openDialog(":Settings_Dialog", typeFunction(":MainWindow", "<Ctrl+Shift+p>"));
closeDialog(":Settings_Dialog", clickButtonFunction(":Settings.Cancel_Button"));
...
}
[/code]
Closures are often used for creating so called callback functions to prevent the need for global variables. The Squish API has waitFor and all the Squish editions have an installEventHandler
function, which can be called with a callback function.
Closure reference
Closures in Perl
Closures in Python
Closures in Ruby
Closures in TCL
WikiPedia Closure page